// SPDX-License-Identifier: LGPL-3.0-or-later
/*

   Copyright 2017 Skytechnology sp. z o.o.
   Copyright 2023 Leil Storage OÜ

   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
*/

#include "pnfs_utils.h"

#include "context_wrap.h"
#include "saunafs_internal.h"

/**
 * @brief Grant a layout segment.
 *
 * pNFS functions.
 *
 * This function is called by nfs41_op_layoutget.
 * It may be called multiple times, to satisfy a request with multiple segments.
 * The FSAL may track state (what portion of the request has been or remains to be satisfied
 * or any other information it wishes) in the bookkeeper member of res.
 * Each segment may have FSAL-specific information associated with its segid.
 * This segid will be supplied to the FSAL when the segment is committed or returned.
 *
 * When the granting the last segment it intends to grant, the FSAL must set the
 * last_segment flag in res.
 *
 * @param[in] objectHandle      The handle of the file on which the layout is requested
 * @param[out] xdrStream        An XDR stream to which the FSAL must encode the layout
 *                              specific portion of the granted layout segment
 * @param[in] arguments         Input arguments of the function
 * @param[in,out] output        In/out and output arguments of the function
 *
 * @returns: Valid error codes in RFC 5661, pp. 366-7.
 */
static nfsstat4 layoutget(struct fsal_obj_handle *objectHandle, XDR *xdrStream,
                          const struct fsal_layoutget_arg *arguments,
                          struct fsal_layoutget_res *output) {
	struct SaunaFSHandle *handle = NULL;
	struct DSWire dataServerWire;

	struct gsh_buffdesc dsBuffer = {
		.addr = &dataServerWire,
		.len = sizeof(struct DSWire)
	};

	struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_SAUNAFS);
	nfl_util4 layoutUtil = 0;
	nfsstat4 status = NFS4_OK;

	handle = container_of(objectHandle, struct SaunaFSHandle, handle);

	if (arguments->type != LAYOUT4_NFSV4_1_FILES) {
		LogMajor(COMPONENT_PNFS, "Unsupported layout type: %x", arguments->type);

		return NFS4ERR_UNKNOWN_LAYOUTTYPE;
	}

	LogDebug(COMPONENT_PNFS, "will issue layout offset: %" PRIu64 " length: %" PRIu64,
	         output->segment.offset, output->segment.length);

	deviceid.device_id2 = handle->export->export.export_id;
	deviceid.devid = handle->inode;

	dataServerWire.inode = handle->inode;
	layoutUtil = SFSCHUNKSIZE;

	status = FSAL_encode_file_layout(xdrStream, &deviceid, layoutUtil, 0, 0,
	                                 &op_ctx->ctx_export->export_id, 1, &dsBuffer, false);

	if (status) {
		LogMajor(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout.");
		return status;
	}

	output->return_on_close = true;
	output->last_segment = true;

	return status;
}

/**
 * @brief Potentially return one layout segment.
 *
 * This function is called once on each segment matching the IO mode and
 * intersecting the range specified in a LAYOUTRETURN operation or for all
 * layouts corresponding to a given stateid on last close, lease expiry, or
 * a layoutreturn with a return-type of FSID or ALL. Whether it is called in
 * the former or latter case is indicated by the synthetic flag in the arg
 * structure, with synthetic being true in the case of last-close or lease expiry.
 *
 * If arg->dispose is true, all resources associated with the layout must be freed.
 *
 * @param[in] objectHandle      The object on which a segment is to be returned
 * @param[in] xdrStream         In the case of a non-synthetic return, this is an XDR stream
 *                              corresponding to the layout type-specific argument to LAYOUTRETURN.
 *                              In the case of a synthetic or bulk return, this is a NULL pointer.
 * @param[in] arguments         Input arguments of the function
 *
 * @returns: Valid error codes in RFC 5661, p. 367.
 */
static nfsstat4 layoutreturn(struct fsal_obj_handle *objectHandle, XDR *xdrStream,
                             const struct fsal_layoutreturn_arg *arguments) {
	(void) objectHandle;
	(void) xdrStream;

	if (arguments->lo_type != LAYOUT4_NFSV4_1_FILES) {
		LogDebug(COMPONENT_PNFS, "Unsupported layout type: %x", arguments->lo_type);

		return NFS4ERR_UNKNOWN_LAYOUTTYPE;
	}

	return NFS4_OK;
}

/**
 * @brief Function to know if the client set a new offset
 *
 * @param[in] arguments          Input parameters to FSAL layoutcommit
 * @param[in] previousReply      Attributes returned by getattr()
 *
 * @retval true : If the client set a new offset
 * @retval false: Otherwise
 */
bool isOffsetChangedByClient(const struct fsal_layoutcommit_arg *arguments,
                             struct sau_attr_reply previousReply) {
	return arguments->new_offset && previousReply.attr.st_size < (long)arguments->last_write + 1;
}

/**
 * @brief Function to know if the client provided a new value for mtime
 *
 * @param[in] arguments          Input parameters to FSAL layoutcommit
 * @param[in] previousReply      Attributes returned by getattr()
 *
 * @retval true : If the client provided a new value for mtime
 * @retval false: Otherwise
 */
bool hasRecentModificationTime(const struct fsal_layoutcommit_arg *arguments,
                               struct sau_attr_reply previousReply) {
	return arguments->time_changed &&
	       (arguments->new_time.seconds > previousReply.attr.st_mtim.tv_sec ||
	        (arguments->new_time.seconds == previousReply.attr.st_mtim.tv_sec &&
	         arguments->new_time.nseconds > previousReply.attr.st_mtim.tv_nsec));
}

/**
 * @brief Commit a segment of a layout.
 *
 * This function is called once on every segment of a layout.
 * The FSAL may avoid being called again after it has finished all tasks
 * necessary for the commit by setting res->commit_done to true.
 *
 * The calling function does not inspect or act on the value of size_supplied
 * or new_size until after the last call to FSAL_layoutcommit.
 *
 * @param[in] objectHandle      The object on which to commit
 * @param[in] xdrStream         An XDR stream containing the layout type-specific portion of the
 *                              LAYOUTCOMMIT arguments
 * @param[in] arguments         Input arguments of the function
 * @param[in,out] output        In/out and output arguments of the function
 *
 * @returns: Valid error codes in RFC 5661, p. 366.
 */
static nfsstat4 layoutcommit(struct fsal_obj_handle *objectHandle, XDR *xdrStream,
                             const struct fsal_layoutcommit_arg *arguments,
                             struct fsal_layoutcommit_res *output) {
	(void) xdrStream;

	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;
	struct sau_attr_reply previousReply;

	if (arguments->type != LAYOUT4_NFSV4_1_FILES) {
		LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arguments->type);
		return NFS4ERR_UNKNOWN_LAYOUTTYPE;
	}

	export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export);
	handle = container_of(objectHandle, struct SaunaFSHandle, handle);

	int retvalue =
	    saunafs_getattr(export->fsInstance, &op_ctx->creds, handle->inode, &previousReply);

	if (retvalue < 0) {
		LogCrit(COMPONENT_PNFS, "Error '%s' in attempt to get attributes of file %lli.",
		        sau_error_string(sau_last_err()), (long long)handle->inode);

		return nfs4LastError();
	}

	struct stat posixAttributes;
	uint64_t mask = 0;

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

	if (isOffsetChangedByClient(arguments, previousReply)) {
		mask |= SAU_SET_ATTR_SIZE;
		posixAttributes.st_size = (__off_t)arguments->last_write + 1;
		output->size_supplied = true;
		output->new_size = arguments->last_write + 1;
	}

	if (hasRecentModificationTime(arguments, previousReply)) {
		posixAttributes.st_mtim.tv_sec = arguments->new_time.seconds;
		posixAttributes.st_mtim.tv_nsec = arguments->new_time.nseconds;
		mask |= SAU_SET_ATTR_MTIME;
		mask = (unsigned int)mask | SAU_SET_ATTR_MTIME;
	}

	sau_attr_reply_t reply;
	retvalue = saunafs_setattr(export->fsInstance, &op_ctx->creds, handle->inode, &posixAttributes,
	                           (int)mask, &reply);

	if (retvalue < 0) {
		LogCrit(COMPONENT_PNFS, "Error '%s' in attempt to set attributes of file %lli.",
		        sau_error_string(sau_last_err()), (long long)handle->inode);

		return nfs4LastError();
	}

	output->commit_done = true;
	return NFS4_OK;
}

/**
 * @brief Initialize pNFS related operations
 *
 * @param[in] ops      FSAL object operations vector
 */
void handleOperationsPnfs(struct fsal_obj_ops *ops) {
	ops->layoutget = layoutget;
	ops->layoutreturn = layoutreturn;
	ops->layoutcommit = layoutcommit;
}
