// SPDX-License-Identifier: LGPL-3.0-or-later
/*
 * vim:noexpandtab:shiftwidth=8:tabstop=8:
 *
 * Copyright CEA/DAM/DIF  (2011)
 * contributeur : Philippe DENIEL   philippe.deniel@cea.fr
 *                Thomas LEIBOVICI  thomas.leibovici@cea.fr
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * ---------------------------------------
 */

/**
 * \file    9p_lock.c
 * \brief   9P version
 *
 * 9p_lock.c : _9P_interpretor, request LOCK
 *
 *
 */

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <netdb.h>
#include "nfs_core.h"
#include "log.h"
#include "export_mgr.h"
#include "sal_functions.h"
#include "fsal.h"
#include "9p.h"

/*
 * Reminder:
 * LOCK_TYPE_RDLCK = 0
 * LOCK_TYPE_WRLCK = 1
 * LOCK_TYPE_UNLCK = 2
 */
char *strtype[] = { "RDLOCK", "WRLOCK", "UNLOCK" };

/*
 * Reminder:
 * LOCK_SUCCESS = 0
 * LOCK_BLOCKED = 1
 * LOCK_ERROR   = 2
 * LOCK_GRACE   = 3
 */
char *strstatus[] = { "SUCCESS", "BLOCKED", "ERROR", "GRACE" };

int _9p_lock(struct _9p_request_data *req9p, u32 *plenout, char *preply)
{
	char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE;
	u16 *msgtag = NULL;
	u32 *fid = NULL;
	u8 *type = NULL;
	u32 *flags = NULL;
	u64 *start = NULL;
	u64 *length = NULL;
	u32 *proc_id = NULL;
	u16 *client_id_len = NULL;
	char *client_id_str = NULL;
	fsal_openflags_t openflags;

	u8 status = _9P_LOCK_SUCCESS;

	state_status_t state_status = STATE_SUCCESS;
	state_owner_t *powner;
	fsal_lock_param_t lock;

	memset(&lock, 0, sizeof(lock));

	char name[MAXNAMLEN + 1];

	struct addrinfo hints, *result;
	sockaddr_t client_addr;

	struct _9p_fid *pfid = NULL;

	/* Get data */
	_9p_getptr(cursor, msgtag, u16);

	_9p_getptr(cursor, fid, u32);
	_9p_getptr(cursor, type, u8);
	_9p_getptr(cursor, flags, u32);
	_9p_getptr(cursor, start, u64);
	_9p_getptr(cursor, length, u64);
	_9p_getptr(cursor, proc_id, u32);
	_9p_getstr(cursor, client_id_len, client_id_str);

	LogDebug(
		COMPONENT_9P,
		"TLOCK: tag=%u fid=%u type=%u|%s flags=0x%x start=%llu length=%llu proc_id=%u client=%.*s",
		(u32)*msgtag, *fid, *type, strtype[*type], *flags,
		(unsigned long long)*start, (unsigned long long)*length,
		*proc_id, *client_id_len, client_id_str);

	if (*fid >= _9P_FID_PER_CONN)
		return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply);

	pfid = req9p->pconn->fids[*fid];

	/* Check that it is a valid fid */
	if (pfid == NULL || pfid->pentry == NULL) {
		LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid);
		return _9p_rerror(req9p, msgtag, EIO, plenout, preply);
	}
	_9p_init_opctx(pfid, req9p);

	/* Tmp hook to avoid lock issue when compiling kernels.
	 * This should not impact ONE client only
	 * get the client's ip addr */
	if (*client_id_len >= sizeof(name)) {
		LogDebug(COMPONENT_9P, "request with name too long (%u)",
			 *client_id_len);
		return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply);
	}

	_9p_get_fname(name, *client_id_len, client_id_str);

	memset((char *)&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_INET;
	if (getaddrinfo(name, NULL, &hints, &result))
		return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply);

	memcpy((char *)&client_addr, (char *)result->ai_addr,
	       result->ai_addrlen);

	/* variable result is not needed anymore, let's free it */
	freeaddrinfo(result);

	powner = get_9p_owner(&client_addr, *proc_id);
	if (powner == NULL)
		return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply);

	/* Do the job */
	switch (*type) {
	case _9P_LOCK_TYPE_RDLCK:
	case _9P_LOCK_TYPE_WRLCK:
		/* Fill in plock */
		lock.lock_type = (*type == _9P_LOCK_TYPE_WRLCK) ? FSAL_LOCK_W
								: FSAL_LOCK_R;
		lock.lock_start = *start;
		lock.lock_length = *length;

		if (!nfs_get_grace_status(false)) {
			status = _9P_LOCK_GRACE;
			break;
		}

		STATELOCK_lock(pfid->pentry);

		/* Test that the file is open in the right mode for the lock */
		openflags = pfid->pentry->obj_ops->status2(pfid->pentry,
							   pfid->state);

		if ((*type == _9P_LOCK_TYPE_RDLCK &&
		     ((openflags & FSAL_O_READ) == 0)) ||
		    (*type == _9P_LOCK_TYPE_WRLCK &&
		     ((openflags & FSAL_O_WRITE) == 0))) {
			/* Open in wrong mode - return error */
			STATELOCK_unlock(pfid->pentry);
			nfs_put_grace_status();
			status = _9P_LOCK_ERROR;
			break;
		}

		state_status = state_lock(pfid->pentry, powner, pfid->state,
					  STATE_NON_BLOCKING, LOCK_9P, NULL,
					  &lock, NULL, NULL);
		STATELOCK_unlock(pfid->pentry);
		nfs_put_grace_status();

		if (state_status == STATE_SUCCESS)
			status = _9P_LOCK_SUCCESS;
		else if (state_status == STATE_LOCK_BLOCKED ||
			 state_status == STATE_LOCK_CONFLICT)
			/**
			 * Should handle _9P_LOCK_FLAGS_BLOCK in *flags,
			 * but linux client replays blocking requests
			 */
			status = _9P_LOCK_BLOCKED;
		else
			status = _9P_LOCK_ERROR;

		break;

	case _9P_LOCK_TYPE_UNLCK:
		if (state_unlock(pfid->pentry, pfid->state, powner, false, 0,
				 &lock) != STATE_SUCCESS)
			status = _9P_LOCK_ERROR;
		else
			status = _9P_LOCK_SUCCESS;

		break;

	default:
		return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply);
	} /* switch( *type ) */

	/* Build the reply */
	_9p_setinitptr(cursor, preply, _9P_RLOCK);
	_9p_setptr(cursor, msgtag, u16);

	_9p_setvalue(cursor, status, u8);

	_9p_setendptr(cursor, preply);
	_9p_checkbound(cursor, preply, plenout);

	LogDebug(
		COMPONENT_9P,
		"RLOCK: tag=%u fid=%u type=%u|%s flags=0x%x start=%llu length=%llu proc_id=%u client=%.*s status=%u|%s",
		(u32)*msgtag, *fid, *type, strtype[*type], *flags,
		(unsigned long long)*start, (unsigned long long)*length,
		*proc_id, *client_id_len, client_id_str, status,
		strstatus[status]);

	return 1;
}
