// 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 "fsal_types.h"

#ifdef LINUX
#include <linux/falloc.h>  /* for fallocate  */
#include <sys/sysmacros.h> /* for makedev(3) */
#endif

#include "fsal.h"
#include "fsal_convert.h"
#include "FSAL/fsal_commonlib.h"

#include "config.h"
#include "errors/saunafs_error_codes.h"
#include "nfs-ganesha/context_wrap.h"
#include "nfs-ganesha/saunafs_fsal_types.h"
#include "nfs-ganesha/saunafs_internal.h"

/**
 * @brief Clean up a filehandle.
 *
 * This function cleans up private resources associated with a filehandle
 * and deallocates it.
 *
 * Implement this method or you will leak. Refcount (if used) should be 1.
 *
 * @param[in] objectHandle     Handle to release
 */
static void release(struct fsal_obj_handle *objectHandle) {
	struct SaunaFSHandle *handle = NULL;
	handle = container_of(objectHandle, struct SaunaFSHandle, handle);

	if (handle->handle.type == REGULAR_FILE) {
		fsal_status_t status = close_fsal_fd(objectHandle, &handle->fd.fsalFd, false);

		if (FSAL_IS_ERROR(status)) {
			LogCrit(COMPONENT_FSAL, "Could not close handle 0x%p, status %s error %s(%d)",
			        objectHandle, fsal_err_txt(status), strerror(status.minor), status.minor);
		}

		destroy_fsal_fd(&handle->fd.fsalFd);
	}

	if (handle != handle->export->root) {
		deleteHandle(handle);
	}
}

/**
 * @brief Look up a filename.
 *
 * Directory operations.
 * This function looks up the given name in the supplied directory.
 *
 * @param [in]     dirHandle        Directory to search
 * @param [in]     path             Name to look up
 * @param [out]    objectHandle     Object found
 * @param [in,out] attributes       Optional attributes for newly created object
 *
 * \see fsal_api.h for more information
 *
 * @returns: FSAL status
 */
static fsal_status_t lookup(struct fsal_obj_handle *dirHandle, const char *path,
                            struct fsal_obj_handle **objectHandle,
                            struct fsal_attrlist *attributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *directory = NULL;
	struct sau_entry node;

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

	int status = saunafs_lookup(export->fsInstance, &op_ctx->creds, directory->inode, path, &node);

	if (status < 0) {
		return fsalLastError();
	}

	if (attributes != NULL) {
		posix2fsal_attributes_all(&node.attr, attributes);
	}

	struct SaunaFSHandle *handle = allocateHandle(&node.attr, export);
	*objectHandle = &handle->handle;

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Read a directory.
 *
 * This function reads directory entries from the FSAL and supplies them to
 * a callback.
 *
 * @param [in]  dirHandle          Directory to read
 * @param [in]  whence             Point at which to start reading. NULL to
 *                                 start at beginning
 * @param [in]  dirState           Opaque pointer to be passed to callback
 * @param [in]  readdirCb          Callback to receive names
 * @param [in]  attributesMask     Indicate which attributes the caller is
 *                                 interested in
 * @param [out] eof                true if the last entry was reached
 *
 * @returns: FSAL status
 */
static fsal_status_t readdir_(struct fsal_obj_handle *dirHandle, fsal_cookie_t *whence,
                              void *dirState, fsal_readdir_cb readdirCb, attrmask_t attributesMask,
                              bool *eof) {
	static const int batchSize = 100;
	struct sau_direntry buffer[batchSize];

	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *directory = NULL;
	struct SaunaFSHandle *handle = NULL;

	struct fsal_attrlist attributes;
	off_t direntryOffset = 0;

	enum fsal_dir_result result = DIR_CONTINUE;
	int status = 0;

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

	sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL;
	context = createContext(export->fsInstance, &op_ctx->creds);

	struct sau_fileinfo *saunafsFd = NULL;
	saunafsFd = sau_opendir(export->fsInstance, context, directory->inode);

	if (!saunafsFd) {
		return fsalLastError();
	}

	if (whence != NULL) {
		direntryOffset = *whence;
	}

	while (true) {
		size_t totalEntries = 0;
		size_t entry = 0;

		status = sau_readdir(export->fsInstance, context, saunafsFd, direntryOffset, batchSize,
		                     buffer, &totalEntries);

		if (status < 0) {
			return fsalLastError();
		}

		result = DIR_CONTINUE;
		for (entry = 0; entry < totalEntries && result != DIR_TERMINATE; ++entry) {
			if (strcmp(buffer[entry].name, ".") == 0 || strcmp(buffer[entry].name, "..") == 0) {
				continue;
			}

			handle = allocateHandle(&buffer[entry].attr, export);

			fsal_prepare_attrs(&attributes, attributesMask);
			posix2fsal_attributes_all(&buffer[entry].attr, &attributes);

			direntryOffset = buffer[entry].next_entry_offset;

			result = readdirCb(buffer[entry].name, &handle->handle, &attributes, dirState,
			                   direntryOffset + 1);

			fsal_release_attrs(&attributes);
		}

		sau_destroy_direntry(buffer, totalEntries);
		*eof = (totalEntries < batchSize) && (entry == totalEntries);

		if (result != DIR_CONTINUE || totalEntries < batchSize) {
			break;
		}
	}

	status = sau_releasedir(export->fsInstance, saunafsFd);

	if (status < 0) {
		return fsalLastError();
	}

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Get attributes.
 *
 * This function fetches the attributes for the object.
 * The attributes requested in the mask are copied out
 * (though other attributes might be copied out).
 *
 * @param [in]  objectHandle       Object to query
 * @param [out] attributes         Attribute list for file
 *
 * \see fsal_api.h for more information
 *
 * @returns: FSAL status
 */
static fsal_status_t getattrs(struct fsal_obj_handle *objectHandle,
                              struct fsal_attrlist *attributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;
	struct sau_attr_reply posixAttributes;

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

	LogFullDebug(COMPONENT_FSAL, " export = %" PRIu16 " inode = %" PRIiNode,
	             export->export.export_id, handle->inode);

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

	if (status < 0) {
		if (attributes->request_mask & ATTR_RDATTR_ERR) {
			attributes->valid_mask = ATTR_RDATTR_ERR;
		}
		return fsalLastError();
	}

	posix2fsal_attributes_all(&posixAttributes.attr, attributes);

#ifdef ENABLE_NFS_ACL_SUPPORT
	if (attributes->request_mask & (attrmask_t)ATTR_ACL) {
		fsal_status_t status =
		    getACL(export, handle->inode, posixAttributes.attr.st_uid, &attributes->acl);

		if (!FSAL_IS_ERROR(status)) {
			attributes->valid_mask |= (attrmask_t)ATTR_ACL;
		}
	}
#endif

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Write wire handle.
 *
 * Handle operations.
 * This function writes a "wire" handle or file ID to the given buffer.
 *
 * @param [in]     objectHandle       The handle to digest
 * @param [in]     outputType         The type of digest to write
 * @param [in,out] buffer             Buffer descriptor to which to write digest.
 *                                    Set fh_desc->len to final output length.
 *
 * @returns: FSAL status
 */
static fsal_status_t handle_to_wire(const struct fsal_obj_handle *objectHandle, uint32_t outputType,
                                    struct gsh_buffdesc *buffer) {
	(void)outputType;

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

	sau_inode_t inode = handle->inode;

	if (buffer->len < sizeof(sau_inode_t)) {
		LogMajor(COMPONENT_FSAL, "Space too small for handle. Need  %zu, have %zu",
		         sizeof(sau_inode_t), buffer->len);
		return fsalstat(ERR_FSAL_TOOSMALL, 0);
	}

	memcpy(buffer->addr, &inode, sizeof(sau_inode_t));
	buffer->len = sizeof(inode);

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Get key for handle.
 *
 * Indicate the unique part of the handle that should be used for hashing.
 *
 * @param [in]  objectHandle     Handle whose key is to be got
 * @param [out] buffer           Address and length giving sub-region of handle to be used as key.
 *
 * @returns: FSAL status
 */
static void handle_to_key(struct fsal_obj_handle *objectHandle, struct gsh_buffdesc *buffer) {
	struct SaunaFSHandle *handle = container_of(objectHandle, struct SaunaFSHandle, handle);

	buffer->addr = &handle->key;
	buffer->len = sizeof(struct SaunaFSHandleKey);
}

/**
 * @brief Close a SaunaFS file descriptor.
 *
 * This function closes a file. This should return ERR_FSAL_NOT_OPENED if
 * the global FD for this obj was not open.
 *
 * @param [in] handle         SaunaFS internal object handle
 * @param [in] saunafsFd      SaunaFS file descriptor to open
 *
 * @returns: FSAL status
 */
static fsal_status_t closeFileDescriptor(struct SaunaFSHandle *handle,
                                         struct SaunaFSFd *saunafsFd) {
	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);

	if (saunafsFd->fd != NULL && saunafsFd->fsalFd.openflags != FSAL_O_CLOSED) {
		int status = sau_release(handle->export->fsInstance, saunafsFd->fd);

		saunafsFd->fd = NULL;
		saunafsFd->fsalFd.openflags = FSAL_O_CLOSED;

		if (status < 0) { return fsalLastError(); }
	} else {
		status = fsalstat(ERR_FSAL_NOT_OPENED, 0);
	}

	return status;
}

/**
 * @brief Function to open or reopen a fsal_fd.
 *
 * @param[in]  objectHandle     File on which to operate
 * @param[in]  openflags        New mode for open
 * @param[out] fsalFd           File descriptor that is to be used
 *
 * @return FSAL status.
 */
fsal_status_t reopen_func(struct fsal_obj_handle *objectHandle, fsal_openflags_t openflags,
                          struct fsal_fd *fsalFd) {
	struct SaunaFSHandle *handle = NULL;
	struct SaunaFSFd *fileDescriptor = NULL;
	struct sau_fileinfo *saunafsFD = NULL;
	struct SaunaFSExport *export = NULL;
	int posixFlags = 0;
	fsal_status_t status = {ERR_FSAL_NO_ERROR, 0};

	handle = container_of(objectHandle, struct SaunaFSHandle, handle);
	fileDescriptor = container_of(fsalFd, struct SaunaFSFd, fsalFd);

	fsal2posix_openflags(openflags, &posixFlags);

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

	LogFullDebug(COMPONENT_FSAL, "fd = %p fd->fd = %p openflags = %x, posixFlags = %x",
	             fileDescriptor, fileDescriptor->fd, openflags, posixFlags);

	assert(fileDescriptor->fd == NULL &&
	       fileDescriptor->fsalFd.openflags == FSAL_O_CLOSED && openflags != 0);

	saunafsFD = saunafs_open(export->fsInstance, &op_ctx->creds, handle->inode, posixFlags);

	if (saunafsFD == NULL) {
		LogFullDebug(COMPONENT_FSAL, "open failed with %s", sau_error_string(sau_last_err()));
		return fsalLastError();
	}

	if (fileDescriptor->fd != NULL && fileDescriptor->fsalFd.openflags != FSAL_O_CLOSED) {
		int retvalue = sau_release(handle->export->fsInstance, fileDescriptor->fd);

		if (retvalue < 0) {
			LogFullDebug(COMPONENT_FSAL, "close failed with %s", sau_error_string(sau_last_err()));
			status = fsalLastError();
		}
	}

	fileDescriptor->fd = saunafsFD;
	fileDescriptor->fsalFd.openflags = FSAL_O_NFS_FLAGS(openflags);

	return status;
}

/**
 * @brief Open a file using its handle.
 *
 * @param [in] objectHandle                  File handle to open
 * @param [in] state                         state_t to use for this operation
 * @param [in] openflags                     Mode for open
 * @param [in] createmode                    Mode for create
 * @param [in] verifier                      Verifier to use for exclusive create
 * @param [in] attributes                    Attributes to set on created file
 *
 * @returns: FSAL status
 */
static fsal_status_t openByHandle(struct fsal_obj_handle *objectHandle, struct state_t *state,
                                  fsal_openflags_t openflags, enum fsal_create_mode createmode,
                                  fsal_verifier_t verifier, struct fsal_attrlist *attributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;
	struct SaunaFSFd *saunafsFd = NULL;
	struct fsal_fd *fsalFd = NULL;
	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);
	int posixFlags = 0;

	fsal_openflags_t oldOpenflags = 0;
	bool truncated = openflags & FSAL_O_TRUNC;

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

	if (state != NULL) {
		saunafsFd = &container_of(state, struct SaunaFSStateFd, state)->saunafsFd;
	} else {
		// We need to use the global file descriptor to continue
		saunafsFd = &handle->fd;
	}

	fsalFd = &saunafsFd->fsalFd;

	// Indicate we want to do fd work (can't fail since not reclaiming)
	fsal_start_fd_work_no_reclaim(fsalFd);

	oldOpenflags = saunafsFd->fsalFd.openflags;

	if (state != NULL) {
		// Prepare to take the share reservation, but only if we are called with a valid state
		// (if state is NULL the caller is a stateless create such as NFS v3 CREATE and we're just
		// going to ignore share reservation stuff).

		// Now that we have the mutex, and no I/O is in progress so we have exclusive access to the
		// share's fsal_fd, we can look at its openflags. We also need to work the share reservation
		// so take the obj_lock.
		// NOTE: This is the ONLY sequence where both a work_mutex and the obj_lock are taken, so
		// there is no opportunity for ABBA deadlock.

		// Note that we do hold the obj_lock over an open and a close which is longer than normal,
		// but the previous iteration of the code held the obj lock (read granted) over whole
		// I/O operations. We don't block over I/O because we've assured that no I/O is in progress
		// or can start before proceeding past the above while loop.
		PTHREAD_RWLOCK_wrlock(&objectHandle->obj_lock);

		// Now check the new share.
		status = check_share_conflict(&handle->share, openflags, false);

		if (FSAL_IS_ERROR(status)) {
			LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status));

			if (state != NULL) {
				if (!FSAL_IS_ERROR(status)) {
					// Success, establish the new share.
					update_share_counters(&handle->share, oldOpenflags, openflags);
				}

				/* Release obj_lock. */
				PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock);
			}

			// Indicate we are done with fd work and signal any waiters.
			fsal_complete_fd_work(fsalFd);

			return status;
		}
	}

	// Check for a genuine no-op open. That means we aren't trying to create, the file is already
	// open in the same mode with the same deny flags, and we aren't trying to truncate.
	// In this case we want to avoid bouncing the fd. In the case of JUST changing the deny mode or
	// a replayed exclusive create, we might bounce the fd when we could have avoided that, but
	// those scenarios are much less common.
	if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(oldOpenflags) &&
	    truncated == false && createmode == FSAL_NO_CREATE) {
		LogFullDebug(COMPONENT_FSAL, "no-op reopen2 saunafsFd->fd = %p openflags = %x",
		             saunafsFd->fd, openflags);

		if (state != NULL) {
			if (!FSAL_IS_ERROR(status)) {
				// Success, establish the new share.
				update_share_counters(&handle->share, oldOpenflags, openflags);
			}

			// Release obj_lock.
			PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock);
		}

		// Indicate we are done with fd work and signal any waiters.
		fsal_complete_fd_work(fsalFd);

		return status;
	}

	// No share conflict, re-open the share fd
	status = reopen_func(objectHandle, openflags, fsalFd);

	if (FSAL_IS_ERROR(status)) {
		LogDebug(COMPONENT_FSAL, "reopen_func returned %s", fsal_err_txt(status));

		if (state != NULL) {
			if (!FSAL_IS_ERROR(status)) {
				// Success, establish the new share.
				update_share_counters(&handle->share, oldOpenflags, openflags);
			}

			// Release obj_lock.
			PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock);
		}

		// Indicate we are done with fd work and signal any waiters.
		fsal_complete_fd_work(fsalFd);

		return status;
	}

	// Inserts to fd_lru only if open succeeds
	if (oldOpenflags == FSAL_O_CLOSED) {
		// This is actually an open, need to increment appropriate counter and insert into LRU.
		insert_fd_lru(fsalFd);
	} else {
		// Bump up the FD in fd_lru as it was already in fd lru.
		bump_fd_lru(fsalFd);
	}

	fsal2posix_openflags(openflags, &posixFlags);

	if (createmode >= FSAL_EXCLUSIVE || attributes) {
		// NOTE: won't come in here when called from saunafs_reopen2...
		//       truncated might be set, but attrs_out will be NULL.
		//       We don't need to look at truncated since other callers
		//       are interested in attrs_out.

		// Refresh the attributes
		struct sau_attr_reply attributesValues;
		int retvalue =
		    saunafs_getattr(export->fsInstance, &op_ctx->creds, handle->inode, &attributesValues);

		if (retvalue == 0) {
			LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64,
			             (int64_t)attributesValues.attr.st_size);
		} else {
			status = fsalLastError();
		}

		if (!FSAL_IS_ERROR(status) && createmode >= FSAL_EXCLUSIVE &&
		    createmode != FSAL_EXCLUSIVE_9P &&
		    !check_verifier_stat(&attributesValues.attr, verifier, false)) {
			// Verifier didn't match, return EEXIST
			status = fsalstat(posix2fsal_error(EEXIST), EEXIST);
		}

		if (!FSAL_IS_ERROR(status) && attributes) {
			posix2fsal_attributes_all(&attributesValues.attr, attributes);
		}
	}

	if (FSAL_IS_ERROR(status)) {
		if (oldOpenflags == FSAL_O_CLOSED) {
			// Now that we have decided to close this FD, let's clean it off from fd_lru and
			// ensure counters are decremented.
			remove_fd_lru(fsalFd);
		}
		// close fd
		(void)closeFileDescriptor(handle, saunafsFd);
	}

	if (state != NULL) {
		if (!FSAL_IS_ERROR(status)) {
			// Success, establish the new share.
			update_share_counters(&handle->share, oldOpenflags, openflags);
		}

		// Release obj_lock.
		PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock);
	}

	// Indicate we are done with fd work and signal any waiters.
	fsal_complete_fd_work(fsalFd);

	return status;
}

/**
 * @brief Open a file using its name.
 *
 * @param [in] objectHandle                  File handle to open
 * @param [in] state                         state_t to use for this operation
 * @param [in] openflags                     Mode for open
 * @param [in] name                          Name of the file
 * @param [in] verifier                      Verifier to use for exclusive create
 * @param [in] attributes                    Attributes to set on created file
 *
 * @returns: FSAL status
 */
static fsal_status_t openByName(struct fsal_obj_handle *objectHandle, struct state_t *state,
                                fsal_openflags_t openflags, const char *name,
                                fsal_verifier_t verifier, struct fsal_attrlist *attributes) {
	struct fsal_obj_handle *temp = NULL;
	fsal_status_t status;

	// Ganesha doesn't has open by name, so we need to get the name with lookup
	status = objectHandle->obj_ops->lookup(objectHandle, name, &temp, NULL);

	if (FSAL_IS_ERROR(status)) {
		LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status));
		return status;
	}

	if (temp->type != REGULAR_FILE) {
		if (temp->type == DIRECTORY) {
			// Trying to open2 a directory
			status = fsalstat(ERR_FSAL_ISDIR, 0);
		} else {
			// Trying to open2 any other non-regular file
			status = fsalstat(ERR_FSAL_SYMLINK, 0);
		}

		// Release the object we found by lookup
		temp->obj_ops->release(temp);
		LogFullDebug(COMPONENT_FSAL, "open2 returning %s", fsal_err_txt(status));

		return status;
	}

	status = openByHandle(temp, state, openflags, FSAL_NO_CREATE, verifier, attributes);

	if (FSAL_IS_ERROR(status)) {
		temp->obj_ops->release(temp);
		LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status));
	}

	return status;
}

/**
 * @brief Open a file descriptor for read or write and possibly create.
 *
 * Extended API functions.
 * With these new operations, the FSAL becomes responsible for managing
 * share reservations. The FSAL is also granted more control over the state of
 * a "file descriptor" and has more control of what a "file descriptor" even is.
 * Ultimately, it is whatever the FSAL needs in order to manage the share
 * reservations and lock state.

 * The open2 method also allows atomic create/setattr/open. This function opens
 * a file for read or write, possibly creating it. If the caller is passing a
 * state, it must hold the state_lock exclusive.
 *
 * @param [in]  objectHandle                 File to open or parent directory
 * @param [in]  state                        state_t to use for this operation
 * @param [in]  openflags                    Mode for open
 * @param [in]  createmode                   Mode for create
 * @param [in]  name                         Name for file if being created or opened
 * @param [in]  attributesToSet              Attributes to set on created file
 * @param [in]  verifier                     Verifier to use for exclusive create
 * @param [in,out] createdObject             Newly created object
 * @param [in,out] attributes                Optional attributes for newly created object
 * @param [in,out] callerPermissionCheck     The caller must do a permission check
 * @param[in,out] parentPreAttributes        Optional attributes for parent dir before the operation.
 *                                           Should be atomic.
 * @param[in,out] parentPostAttributes       Optional attributes for parent dir after the operation.
 *                                           Should be atomic.
 *
 * @returns: FSAL status
 */
static fsal_status_t open2(struct fsal_obj_handle *objectHandle, struct state_t *state,
                           fsal_openflags_t openflags, enum fsal_create_mode createmode,
                           const char *name, struct fsal_attrlist *attributesToSet,
                           fsal_verifier_t verifier, struct fsal_obj_handle **createdObject,
                           struct fsal_attrlist *attributes, bool *callerPermissionCheck,
                           struct fsal_attrlist *parentPreAttributes,
                           struct fsal_attrlist *parentPostAttributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;
	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);

	LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attributesToSet, false);

	if (createmode >= FSAL_EXCLUSIVE) {
		// Now fixup attrs for verifier if exclusive create
		set_common_verifier(attributesToSet, verifier, false);
	}

	if (name == NULL) {
		status = openByHandle(objectHandle, state, openflags, createmode, verifier, attributes);
		*callerPermissionCheck = FSAL_IS_SUCCESS(status);
		return status;
	}

	*callerPermissionCheck = (createmode == FSAL_NO_CREATE);

	if (createmode == FSAL_NO_CREATE) {
		return openByName(objectHandle, state, openflags, name, verifier, attributes);
	}

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

	// Fetch the mode attribute to use in the openat system call
	mode_t unix_mode = fsal2unix_mode(attributesToSet->mode) &
	                   ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export);

	// Don't set the mode if we later set the attributes
	FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);

	struct sau_entry posixAttributes;
	int retval = saunafs_mknode(export->fsInstance, &op_ctx->creds, handle->inode, name, unix_mode,
	                            0, &posixAttributes);

	if (retval < 0 && sau_last_err() == SAUNAFS_ERROR_EEXIST && createmode == FSAL_UNCHECKED) {
		return openByName(objectHandle, state, openflags, name, verifier, attributes);
	}

	if (retval < 0) { return fsalLastError(); }

	// File has been created by us.
	*callerPermissionCheck = false;

	struct SaunaFSHandle *newHandle = allocateHandle(&posixAttributes.attr, export);

	if (newHandle == NULL) {
		(*createdObject)->obj_ops->release(*createdObject);
		*createdObject = NULL;

		retval = saunafs_unlink(export->fsInstance, &op_ctx->creds, handle->inode, name);

		if (retval < 0) { return fsalLastError(); }

		return fsalstat(posix2fsal_error(ENOMEM), ENOMEM);
	}

	*createdObject = &newHandle->handle;

	if (attributesToSet->valid_mask != 0) {
		status = (*createdObject)->obj_ops->setattr2(*createdObject, false, state, attributesToSet);
		if (FSAL_IS_ERROR(status)) {
			(*createdObject)->obj_ops->release(*createdObject);
			*createdObject = NULL;

			retval = saunafs_unlink(export->fsInstance, &op_ctx->creds, handle->inode, name);

			if (retval < 0) { return fsalLastError(); }

			return status;
		}

		if (attributes != NULL) {
			status = (*createdObject)->obj_ops->getattrs(*createdObject, attributes);

			if (FSAL_IS_ERROR(status) &&
			    (attributes->request_mask & ATTR_RDATTR_ERR) == 0) {
				(*createdObject)->obj_ops->release(*createdObject);
				*createdObject = NULL;

				retval = saunafs_unlink(export->fsInstance, &op_ctx->creds, handle->inode, name);

				if (retval < 0) { return fsalLastError(); }

				return status;
			}

			attributes = NULL;
		}
	}

	if (attributes != NULL) {
		posix2fsal_attributes_all(&posixAttributes.attr, attributes);
	}

	return openByHandle(*createdObject, state, openflags, createmode, verifier, NULL);
}

/**
 * @brief Read data from a file.
 *
 * This function reads data from the given file. The FSAL must be able to
 * perform the read whether a state is presented or not.
 *
 * This function also is expected to handle properly bypassing or not share
 * reservations. This is an (optionally) asynchronous call. When the I/O is
 * complete, the done callback is called with the results.
 *
 * @param [in]     objectHandle     File on which to operate
 * @param [in]     bypass           If state doesn't indicate a share reservation,
 *                                  bypass any deny read
 * @param [in,out] doneCb           Callback to call when I/O is done
 * @param [in,out] readArg          Info about read, passed back in callback
 * @param [in,out] callerArg        Opaque arg from the caller for callback
 */
static void read2(struct fsal_obj_handle *objectHandle, bool bypass, fsal_async_cb doneCb,
                  struct fsal_io_arg *readArg, void *callerArg) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;

	struct SaunaFSFd *saunafsFd = NULL;
	struct SaunaFSFd emptyFileDescriptor = {FSAL_FD_INIT, NULL};

	struct fsal_fd *outFileDescriptor = NULL;
	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);
	fsal_status_t status2;

	ssize_t bytes = 0;
	uint64_t offset = readArg->offset;

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

	LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIiNode " offset=%" PRIu64,
	             export->export.export_id, handle->inode, offset);

	if (readArg->info != NULL) {
		// Currently we don't support READ_PLUS
		doneCb(objectHandle, fsalstat(ERR_FSAL_NOTSUPP, 0), readArg, callerArg);
		return;
	}

	// Indicate a desire to start io and get a usable file descriptor
	status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd,
	                       &emptyFileDescriptor.fsalFd, readArg->state, FSAL_O_READ, false, NULL,
	                       bypass, &handle->share);

	if (FSAL_IS_ERROR(status)) {
		LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status));

		doneCb(objectHandle, status, readArg, callerArg);
		return;
	}

	saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd);

	readArg->io_amount = 0;
	for (int i = 0; i < readArg->iov_count; i++) {
		bytes = saunafs_read(export->fsInstance, &op_ctx->creds, saunafsFd->fd, offset,
		                     readArg->iov[i].iov_len, readArg->iov[i].iov_base);

		if (bytes == 0) {
			readArg->end_of_file = true;
			break;
		}
		if (bytes < 0) {
			status = fsalLastError();

			status2 = fsal_complete_io(objectHandle, outFileDescriptor);
			LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

			if (readArg->state == NULL) {
				// We did I/O without a state so we need to release the temp share
				// reservation acquired.

				// Release the share reservation now by updating the counters.
				update_share_counters_locked(objectHandle, &handle->share, FSAL_O_READ,
				                             FSAL_O_CLOSED);
			}

			doneCb(objectHandle, status, readArg, callerArg);
			return;
		}

		readArg->io_amount += bytes;
		offset += bytes;
	}

	status2 = fsal_complete_io(objectHandle, outFileDescriptor);
	LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

	if (readArg->state == NULL) {
		// We did I/O without a state so we need to release the temp share reservation acquired.

		// Release the share reservation now by updating the counters.
		update_share_counters_locked(objectHandle, &handle->share, FSAL_O_READ, FSAL_O_CLOSED);
	}

	doneCb(objectHandle, status, readArg, callerArg);
}

/** @brief Create a directory.
 *
 * Creation operations. This function creates a new directory.
 *
 * @param [in]     directoryHandle     Directory in which to create the directory
 * @param [in]     name                Name of directory to create
 * @param [in]     attributesToSet     Attributes to set on newly created object
 * @param [out]    createdObject       Newly created object
 * @param [in,out] attributes          Optional attributes for newly created object
 * @param[in,out] parentPreAttributes  Optional attributes for parent dir before the operation.
 *                                     Should be atomic.
 * @param[in,out] parentPostAttributes Optional attributes for parent dir after the operation.
 *                                     Should be atomic.
 *
 * \see fsal_api.h for more information
 *
 * @returns: FSAL status
 */
static fsal_status_t mkdir_(struct fsal_obj_handle *directoryHandle, const char *name,
                            struct fsal_attrlist *attributesToSet,
                            struct fsal_obj_handle **createdObject,
                            struct fsal_attrlist *attributes,
                            struct fsal_attrlist *parentPreAttributes,
                            struct fsal_attrlist *parentPostAttributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *directory = NULL;
	struct SaunaFSHandle *handle = NULL;
	struct sau_entry directoryEntry;
	fsal_status_t status;

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

	LogFullDebug(COMPONENT_FSAL,
	             "export = %" PRIu16 " parent_inode = %" PRIiNode " mode = %" PRIo32 " name = %s",
	             export->export.export_id, directory->inode, attributesToSet->mode, name);

	mode_t unix_mode = fsal2unix_mode(attributesToSet->mode) &
	                   ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export);

	int retvalue = saunafs_mkdir(export->fsInstance, &op_ctx->creds, directory->inode, name,
	                             unix_mode, &directoryEntry);

	if (retvalue < 0) {
		return fsalLastError();
	}

	handle = allocateHandle(&directoryEntry.attr, export);
	*createdObject = &handle->handle;

	FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);

	if (attributesToSet->valid_mask) {
		status = (*createdObject)->obj_ops->setattr2(*createdObject, false, NULL, attributesToSet);

		if (FSAL_IS_ERROR(status)) {
			LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status));

			// Release the handle we just allocate
			(*createdObject)->obj_ops->release(*createdObject);
			*createdObject = NULL;
		} else if (attributes != NULL) {
			// We ignore errors here. The mkdir and setattr succeeded, so we don't want to return
			// error if the getattrs fails.
			// We'll just return no attributes in that case.
			(*createdObject)->obj_ops->getattrs(*createdObject, attributes);
		}
	} else if (attributes != NULL) {
		// Since we haven't set any attributes other than what was set on create, just use the
		// stat results we used to create the fsal_obj_handle.
		posix2fsal_attributes_all(&directoryEntry.attr, attributes);
	}

	FSAL_SET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);
	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Create a new link.
 *
 * This function creates a new name for an existing object.
 *
 * @param [in] objectHandle             Object to be linked to
 * @param [in] destinationDirHandle     Directory in which to create the link
 * @param [in] name                     Name for link
 *
 * @returns: FSAL status
 */
static fsal_status_t link_(struct fsal_obj_handle *objectHandle,
                           struct fsal_obj_handle *destinationDirHandle, const char *name,
                           struct fsal_attrlist *destdirPreAttributes,
                           struct fsal_attrlist *destdirPostAttributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;
	struct SaunaFSHandle *destinationHandle = NULL;

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

	destinationHandle = container_of(destinationDirHandle, struct SaunaFSHandle, handle);

	LogFullDebug(COMPONENT_FSAL,
	             "export = %" PRIu16 " inode = %" PRIiNode " dest_inode = %" PRIiNode " name = %s",
	             export->export.export_id, handle->inode, destinationHandle->inode, name);

	sau_entry_t entry;
	int retvalue = saunafs_link(export->fsInstance, &op_ctx->creds, handle->inode,
	                            destinationHandle->inode, name, &entry);

	if (retvalue < 0) {
		return fsalLastError();
	}

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Rename a file.
 *
 * This function renames a file (technically it changes the name of one link,
 * which may be the only link to the file.)
 *
 * @param [in] oldParentHandle     Source directory
 * @param [in] oldName             Original name
 * @param [in] newParentHandle     Destination directory
 * @param [in] newName             New name
 * @param[in,out] oldParentPreAttributes  Optional attributes for olddParent
 *                                      before the operation. Should be atomic.
 * @param[in,out] oldParentPostAttributes Optional attributes for oldParent
 *                                      after the operation. Should be atomic.
 * @param[in,out] newParentPreAttributes  Optional attributes for newParent
 *                                      before the operation. Should be atomic.
 * @param[in,out] newParentPostAttributes Optional attributes for newParent
 *                                      after the operation. Should be atomic.
 *
 * @returns: FSAL status
 */
static fsal_status_t rename_(struct fsal_obj_handle *objectHandle,
                             struct fsal_obj_handle *oldParentHandle, const char *oldName,
                             struct fsal_obj_handle *newParentHandle, const char *newName,
                             struct fsal_attrlist *oldParentPreAttributes,
                             struct fsal_attrlist *oldParentPostAttributes,
                             struct fsal_attrlist *newParentPreAttributes,
                             struct fsal_attrlist *newParentPostAttributes) {
	(void)objectHandle;
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *oldDir = NULL;
	struct SaunaFSHandle *newDir = NULL;

	export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export);
	oldDir = container_of(oldParentHandle, struct SaunaFSHandle, handle);
	newDir = container_of(newParentHandle, struct SaunaFSHandle, handle);

	LogFullDebug(COMPONENT_FSAL,
	             "export=%" PRIu16 " old_inode=%" PRIiNode " new_inode=%" PRIiNode
	             " old_name=%s new_name=%s",
	             export->export.export_id, oldDir->inode, newDir->inode, oldName, newName);

	int status = saunafs_rename(export->fsInstance, &op_ctx->creds, oldDir->inode, oldName,
	                            newDir->inode, newName);

	if (status < 0) {
		return fsalLastError();
	}

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Remove a name from a directory.
 *
 * This function removes a name from a directory and possibly deletes the file so named.
 *
 * @param [in] directoryHandle         The directory from which to remove the name
 * @param [in] objectHandle            The object being removed
 * @param [in] name                    The name to remove
 * @param[in,out] parentPreAttributes  Optional attributes for parent dir before the operation.
 *                                     Should be atomic.
 * @param[in,out] parentPostAttributes Optional attributes for parent dir after the operation.
 *                                     Should be atomic.
 *
 * @returns: FSAL status
 */
static fsal_status_t unlink_(struct fsal_obj_handle *directoryHandle,
                             struct fsal_obj_handle *objectHandle, const char *name,
                             struct fsal_attrlist *parentPreAttributes,
                             struct fsal_attrlist *parentPostAttributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *directory = NULL;
	int status = 0;

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

	LogFullDebug(COMPONENT_FSAL,
	             "export = %" PRIu16 " parent_inode = %" PRIiNode " name = %s type = %s",
	             export->export.export_id, directory->inode, name,
	             object_file_type_to_str(objectHandle->type));

	if (objectHandle->type != DIRECTORY) {
		status = saunafs_unlink(export->fsInstance, &op_ctx->creds, directory->inode, name);
	}
	else {
		status = saunafs_rmdir(export->fsInstance, &op_ctx->creds, directory->inode, name);
	}

	if (status < 0) { return fsalLastError(); }

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Close a file.
 *
 * @param [in] objectHandle     File to close
 *
 * @returns: FSAL status
 */
static fsal_status_t close_(struct fsal_obj_handle *objectHandle) {
	struct SaunaFSHandle *handle = container_of(objectHandle, struct SaunaFSHandle, handle);

	LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIiNode, handle->key.exportId,
	             handle->inode);

	return close_fsal_fd(objectHandle, &handle->fd.fsalFd, false);
}

/**
 * @brief Write data to a file.
 *
 * This function writes data to a file. The FSAL must be able to perform
 * the write whether a state is presented or not. This function also is
 * expected to handle properly bypassing or not share reservations.
 *
 * Even with bypass == true, it will enforce a mandatory (NFSv4) deny_write
 * if an appropriate state is not passed).

 * The FSAL is expected to enforce sync if necessary. This is an
 * (optionally) asynchronous call. When the I/O is complete, the done_cb
 * callback is called.
 *
 * @param [in]     objectHandle     File on which to operate
 * @param [in]     bypass           If state doesn't indicate a share reservation,
 *                                  bypass any non-mandatory deny write
 * @param [in,out] doneCb           Callback to call when I/O is done
 * @param [in,out] writeArg         Info about write, passed back in callback
 * @param [in,out] callerArg        Opaque arg from the caller for callback
 */
static void write2(struct fsal_obj_handle *objectHandle, bool bypass, fsal_async_cb doneCb,
                   struct fsal_io_arg *writeArg, void *callerArg) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;

	struct SaunaFSFd *saunafsFd = NULL;
	struct SaunaFSFd emptyFileDescriptor = {FSAL_FD_INIT, NULL};
	struct fsal_fd *outFileDescriptor = NULL;

	fsal_status_t status;
	fsal_status_t status2;

	ssize_t bytes = 0;
	uint64_t offset = writeArg->offset;

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

	LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIiNode " offset=%" PRIu64,
	             export->export.export_id, handle->inode, offset);

	if (writeArg->info) {
		doneCb(objectHandle, fsalstat(ERR_FSAL_NOTSUPP, 0), writeArg, callerArg);
		return;
	}

	// Indicate a desire to start io and get a usable file descriptor
	status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd,
	                       &emptyFileDescriptor.fsalFd, writeArg->state, FSAL_O_WRITE, false, NULL,
	                       bypass, &handle->share);

	if (FSAL_IS_ERROR(status)) {
		LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status));

		doneCb(objectHandle, status, writeArg, callerArg);
		return;
	}

	saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd);

	for (int i = 0; i < writeArg->iov_count; i++) {
		bytes = saunafs_write(export->fsInstance, &op_ctx->creds, saunafsFd->fd, offset,
		                      writeArg->iov[i].iov_len, writeArg->iov[i].iov_base);

		if (bytes == 0) {
			break;
		}
		if (bytes < 0) {
			status = fsalLastError();
			status2 = fsal_complete_io(objectHandle, outFileDescriptor);

			LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

			if (writeArg->state == NULL) {
				// We did I/O without a state so we need to release the temp share reservation
				// acquired.

				// Release the share reservation now by updating the counters.
				update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE,
				                             FSAL_O_CLOSED);
			}

			doneCb(objectHandle, status, writeArg, callerArg);
			return;
		}

		writeArg->io_amount += bytes;
		offset += bytes;
	}

	if (writeArg->fsal_stable) {
		int retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, saunafsFd->fd);

		if (retvalue < 0) {
			status = fsalLastError();
			writeArg->fsal_stable = false;
		}
	}

	status2 = fsal_complete_io(objectHandle, outFileDescriptor);

	LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

	if (writeArg->state == NULL) {
		// We did I/O without a state so we need to release the temp share reservation acquired.

		// Release the share reservation now by updating the counters.
		update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED);
	}

	doneCb(objectHandle, status, writeArg, callerArg);
}

/**
 * @brief Commit written data.
 *
 * This function flushes possibly buffered data to a file. This method
 * differs from commit due to the need to interact with share reservations
 * and the fact that the FSAL manages the state of "file descriptors". The
 * FSAL must be able to perform this operation without being passed a
 * specific state.
 *
 * @param [in] objectHandle     File on which to operate
 * @param [in] offset           Start of range to commit
 * @param [in] length           Length of range to commit
 *
 * @returns: FSAL status
 */
static fsal_status_t commit2(struct fsal_obj_handle *objectHandle, off_t offset, size_t length) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;

	fsal_status_t status;
	fsal_status_t status2;

	struct SaunaFSFd emptyFd = {FSAL_FD_INIT, NULL};
	struct SaunaFSFd *saunafsFd = NULL;
	struct fsal_fd *outFileDescriptor = NULL;

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

	LogFullDebug(COMPONENT_FSAL,
	             "export = %" PRIu16 " inode = %" PRIiNode " offset = %lli len = %zu",
	             export->export.export_id, handle->inode, (long long)offset, length);

	// Make sure file is open in appropriate mode.
	// Do not check share reservation.
	status = fsal_start_global_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd,
	                              &emptyFd.fsalFd, FSAL_O_ANY, false, NULL);

	if (FSAL_IS_ERROR(status)) { return status; }

	saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd);

	int retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, saunafsFd->fd);

	if (retvalue < 0) { status = fsalLastError(); }

	status2 = fsal_complete_io(objectHandle, outFileDescriptor);

	LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

	// We did not do share reservation stuff...
	return status;
}

/**
 * @brief Set attributes on an object.
 *
 * This function sets attributes on an object. Which attributes are set is
 * determined by attrib_set->mask. The FSAL must manage bypass or not of share
 * reservations, and a state may be passed.

 * The caller is expected to invoke fsal_release_attrs to release any
 * resources held by the set attributes. The FSAL layer MAY have added an
 * inherited ACL.
 *
 * @param [in] objectHandle     File on which to operate
 * @param [in] bypass           If state doesn't indicate a share reservation,
 *                              bypass any non-mandatory deny write
 * @param [in] state            state_t to use for this operation
 * @param [in] attributes       Attributes to set
 *
 * @returns: FSAL status
 */
static fsal_status_t setattr2(struct fsal_obj_handle *objectHandle, bool bypass,
                              struct state_t *state, struct fsal_attrlist *attributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;

	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);
	bool hasShare = false;

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

	LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attributes, false);

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MODE)) {
		attributes->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export);
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_SIZE)) {
		if (objectHandle->type != REGULAR_FILE) {
			LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file");
			return fsalstat(ERR_FSAL_INVAL, EINVAL);
		}

		if (state == NULL) {
			// Check share reservation and if OK, update the counters.
			status = check_share_conflict_and_update_locked(objectHandle, &handle->share,
			                                                FSAL_O_CLOSED, FSAL_O_WRITE, bypass);

			if (FSAL_IS_ERROR(status)) {
				LogDebug(COMPONENT_FSAL, "check_share_conflict failed with %s",
				         fsal_err_txt(status));

				return status;
			}

			hasShare = true;
		}
	}

	struct stat posixAttributes;
	uint mask = 0;

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

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_SIZE)) {
		mask |= SAU_SET_ATTR_SIZE;
		posixAttributes.st_size = (__off_t)(attributes->filesize);
		LogFullDebug(COMPONENT_FSAL, "setting size to %lld", (long long)posixAttributes.st_size);
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MODE)) {
		mask |= SAU_SET_ATTR_MODE;
		posixAttributes.st_mode = fsal2unix_mode(attributes->mode);
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_OWNER)) {
		mask |= SAU_SET_ATTR_UID;
		posixAttributes.st_uid = attributes->owner;
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_GROUP)) {
		mask |= SAU_SET_ATTR_GID;
		posixAttributes.st_gid = attributes->group;
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_ATIME)) {
		mask |= SAU_SET_ATTR_ATIME;
		posixAttributes.st_atim = attributes->atime;
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_ATIME_SERVER)) {
		mask |= SAU_SET_ATTR_ATIME_NOW;
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MTIME)) {
		mask |= SAU_SET_ATTR_MTIME;
		posixAttributes.st_mtim = attributes->mtime;
	}

	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MTIME_SERVER)) {
		mask |= SAU_SET_ATTR_MTIME_NOW;
	}

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

	if (retvalue < 0) {
		status = fsalLastError();

		if (hasShare) {
			// Release the share reservation now by updating the counters.
			update_share_counters_locked(objectHandle, &handle->share, FSAL_O_RDWR, FSAL_O_CLOSED);
		}

		return status;
	}

#ifdef ENABLE_NFS_ACL_SUPPORT
	if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_ACL)) {
		status = setACL(export, handle->inode, attributes->acl, reply.attr.st_mode);
	}
#endif

	if (hasShare) {
		// Release the share reservation now by updating the counters.
		update_share_counters_locked(objectHandle, &handle->share, FSAL_O_RDWR, FSAL_O_CLOSED);
	}

	return status;
}

/**
 * @brief Manage closing a file when a state is no longer needed.
 *
 * When the upper layers are ready to dispense with a state, this method is
 * called to allow the FSAL to close any file descriptors or release any
 * other resources associated with the state. A call to free_state should
 * be assumed to follow soon.
 *
 * @param [in] objectHandle     File on which to operate
 * @param [in] state            state_t to use for this operation
 *
 * @returns: FSAL status
 */
static fsal_status_t close2(struct fsal_obj_handle *objectHandle, struct state_t *state) {
	struct SaunaFSHandle *handle = container_of(objectHandle, struct SaunaFSHandle, handle);

	struct SaunaFSFd *fileDescriptor =
	    &container_of(state, struct SaunaFSStateFd, state)->saunafsFd;

	LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIiNode, handle->key.exportId,
	             handle->inode);

	if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE ||
	    state->state_type == STATE_TYPE_9P_FID) {
		update_share_counters_locked(objectHandle, &handle->share, handle->fd.fsalFd.openflags,
		                             FSAL_O_CLOSED);
	}

	return close_fsal_fd(objectHandle, &fileDescriptor->fsalFd, false);
}

/**
 * @brief Create a symbolic link.
 *
 * This function creates a new symbolic link.
 *
 * @param [in] directoryHandle      Directory in which to create the object
 * @param [in] name                 Name of object to create
 * @param [in] symbolicLinkPath     Content of symbolic link
 * @param [in] attributesToSet      Attributes to set on newly created object
 * @param [out] createdObject       Newly created object
 * @param [in,out] attributes       Optional attributes for newly created object
 * @param[in,out] parentPreAttributes  Optional attributes for parent dir before the operation.
 *                                     Should be atomic.
 * @param[in,out] parentPostAttributes Optional attributes for parent dir after the operation.
 *                                     Should be atomic.
 *
 * \see fsal_api.h for more information
 *
 * @returns: FSAL status
 */
static fsal_status_t symlink_(struct fsal_obj_handle *directoryHandle, const char *name,
                              const char *symbolicLinkPath, struct fsal_attrlist *attributesToSet,
                              struct fsal_obj_handle **createdObject,
                              struct fsal_attrlist *attributes,
                              struct fsal_attrlist *parentPreAttributes,
                              struct fsal_attrlist *parentPostAttributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *directory = NULL;
	struct sau_entry entry;

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

	LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " parent_inode = %" PRIiNode " name = %s",
	             export->export.export_id, directory->inode, name);

	int retvalue = saunafs_symlink(export->fsInstance, &op_ctx->creds, symbolicLinkPath,
	                               directory->inode, name, &entry);

	if (retvalue < 0) {
		return fsalLastError();
	}

	struct SaunaFSHandle *handle = allocateHandle(&entry.attr, export);
	*createdObject = &handle->handle;

	// We handled the mode above
	FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);

	if (attributesToSet->valid_mask) {
		fsal_status_t status;

		// Now per support_ex API, if there are any other attributes set, go ahead and get them set
		// now
		status = (*createdObject)->obj_ops->setattr2(*createdObject, false, NULL, attributesToSet);

		if (FSAL_IS_ERROR(status)) {
			// Release the handle we just allocated
			LogFullDebug(COMPONENT_FSAL, "setattr2 status = %s", fsal_err_txt(status));

			(*createdObject)->obj_ops->release(*createdObject);
			*createdObject = NULL;
		}
	} else if (attributes != NULL) {
		posix2fsal_attributes_all(&entry.attr, attributes);
	}

	FSAL_SET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);
	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Perform a lock operation.
 *
 * This function performs a lock operation (lock, unlock, test) on a file.
 * This method assumes the FSAL is able to support lock owners, though it
 * need not support asynchronous blocking locks. Passing the lock state
 * allows the FSAL to associate information with a specific lock owner
 * for each file (which may include use of a "file descriptor".
 *
 * @param [in]  objectHandle        File on which to operate
 * @param [in]  state               state_t to use for this operation
 * @param [in]  owner               Lock owner
 * @param [in]  lockOperation       Operation to perform
 * @param [in]  requestedLock       Lock to take/release/test
 * @param [out] conflictingLock     Conflicting lock
 *
 * @returns: FSAL status
 */
fsal_status_t lock_op2(struct fsal_obj_handle *objectHandle, struct state_t *state, void *owner,
                       fsal_lock_op_t lockOperation, fsal_lock_param_t *requestedLock,
                       fsal_lock_param_t *conflictingLock) {
	struct SaunaFSHandle *handle = NULL;
	struct SaunaFSExport *export = NULL;

	sau_err_t lastError = 0;
	fileinfo_t *fileinfo = NULL;
	sau_lock_info_t lockInfo;

	fsal_status_t status = {0, 0};
	fsal_status_t status2 = {0, 0};
	int retval = 0;

	struct SaunaFSFd *saunafsFd = NULL;
	fsal_openflags_t openflags = FSAL_O_RDWR;

	struct SaunaFSFd emptyFileDescriptor = { FSAL_FD_INIT, NULL };
	struct fsal_fd *outFileDescriptor = NULL;
	bool bypass = false;

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

	LogFullDebug(COMPONENT_FSAL, "Locking: op:%d type:%d start:%" PRIu64 " length:%" PRIu64 " ",
	             lockOperation, requestedLock->lock_type, requestedLock->lock_start,
	             requestedLock->lock_length);

	if (objectHandle == NULL) {
		LogCrit(COMPONENT_FSAL, "objectHandle arg is NULL.");
		return fsalstat(ERR_FSAL_FAULT, 0);
	}

	if (owner == NULL) {
		LogCrit(COMPONENT_FSAL, "owner arg is NULL.");
		return fsalstat(ERR_FSAL_FAULT, 0);
	}

	if (conflictingLock == NULL && lockOperation == FSAL_OP_LOCKT) {
		LogDebug(COMPONENT_FSAL, "conflictingLock argument can't be NULL for operation LOCKT");
		return fsalstat(ERR_FSAL_FAULT, 0);
	}

	if (lockOperation == FSAL_OP_LOCKT) {
		// We may end up using global fd, don't fail on a deny mode
		bypass = true;
		openflags = FSAL_O_ANY;
	} else if (lockOperation == FSAL_OP_LOCK) {
		if (requestedLock->lock_type == FSAL_LOCK_R) {
			openflags = FSAL_O_READ;
		} else if (requestedLock->lock_type == FSAL_LOCK_W) {
			openflags = FSAL_O_WRITE;
		}
	} else if (lockOperation == FSAL_OP_UNLOCK) {
		openflags = FSAL_O_ANY;
	} else {
		LogFullDebug(COMPONENT_FSAL,
		             "ERROR: Lock operation requested was not TEST, READ, or WRITE.");

		return fsalstat(ERR_FSAL_NOTSUPP, 0);
	}

	if (lockOperation != FSAL_OP_LOCKT && state == NULL) {
		LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state");
		return posix2fsal_status(EINVAL);
	}

	if (requestedLock->lock_type == FSAL_LOCK_R) {
		lockInfo.l_type = F_RDLCK;
	} else if (requestedLock->lock_type == FSAL_LOCK_W) {
		lockInfo.l_type = F_WRLCK;
	} else {
		LogFullDebug(COMPONENT_FSAL, "ERROR: The requested lock type was not read or write.");
		return fsalstat(ERR_FSAL_NOTSUPP, 0);
	}

	if (lockOperation == FSAL_OP_UNLOCK) {
		lockInfo.l_type = F_UNLCK;
	}

	lockInfo.l_pid = 0;
	lockInfo.l_len = requestedLock->lock_length;
	lockInfo.l_start = requestedLock->lock_start;

	// Prevent locking an unintended range in case requested lock length is too large and mapped
	// to negative values, as POSIX locks can accept negative l_len values.
	if (lockInfo.l_len < 0) {
		LogCrit(COMPONENT_FSAL,
		        "Requested lock length is out of range - lockInfo.l_len(%lu), "
		        "requested length(%" PRIu64 ")",
		        lockInfo.l_len, requestedLock->lock_length);
		return fsalstat(ERR_FSAL_BAD_RANGE, 0);
	}

	// Indicate a desire to start io and get a usable file descriptor
	status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd,
	                       &emptyFileDescriptor.fsalFd, state, openflags, true, NULL, bypass,
	                       &handle->share);

	if (FSAL_IS_ERROR(status)) {
		LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status));

		return status;
	}

	saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd);
	fileinfo = saunafsFd->fd;

	// Defensive check: ensure we have a valid file descriptor before proceeding
	if (fileinfo == NULL) {
		LogCrit(COMPONENT_FSAL, "Lock operation called with NULL file descriptor");

		status2 = fsal_complete_io(objectHandle, outFileDescriptor);
		LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

		if (state == NULL) {
			// We did I/O without a state so we need to release the temp share reservation acquired.
			// Release the share reservation now by updating the counters.
			update_share_counters_locked(objectHandle, &handle->share, openflags, FSAL_O_CLOSED);
		}

		return fsalstat(ERR_FSAL_FAULT, EFAULT);
	}

	sau_set_lock_owner(fileinfo, (uint64_t)owner);

	if (lockOperation == FSAL_OP_LOCKT) {
		retval = saunafs_getlock(export->fsInstance, &op_ctx->creds, fileinfo, &lockInfo);
	} else {
		retval = saunafs_setlock(export->fsInstance, &op_ctx->creds, fileinfo, &lockInfo);
	}

	if (retval < 0) {
		lastError = sau_last_err();
		LogFullDebug(COMPONENT_FSAL, "Returning error %d", lastError);

		status2 = fsal_complete_io(objectHandle, outFileDescriptor);
		LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

		if (state == NULL) {
			// We did I/O without a state so we need to release the temp share reservation acquired.
			// Release the share reservation now by updating the counters.
			update_share_counters_locked(objectHandle, &handle->share, openflags, FSAL_O_CLOSED);
		}

		return saunafsToFsalError(lastError);
	}

	// F_UNLCK is returned then the tested operation would be possible
	if (conflictingLock != NULL) {
		if (lockOperation == FSAL_OP_LOCKT && lockInfo.l_type != F_UNLCK) {
			conflictingLock->lock_length = lockInfo.l_len;
			conflictingLock->lock_start = lockInfo.l_start;
			conflictingLock->lock_type = lockInfo.l_type;
		} else {
			conflictingLock->lock_length = 0;
			conflictingLock->lock_start = 0;
			conflictingLock->lock_type = FSAL_NO_LOCK;
		}
	}

	status2 = fsal_complete_io(objectHandle, outFileDescriptor);
	LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

	if (state == NULL) {
		// We did I/O without a state so we need to release the temp share reservation acquired.
		// Release the share reservation now by updating the counters.
		update_share_counters_locked(objectHandle, &handle->share, openflags, FSAL_O_CLOSED);
	}

	return status;
}

/**
 * @brief Re-open a file that may be already opened.
 *
 * This function supports changing the access mode of a share reservation
 * and thus should only be called with a share state. The st_lock must be
 * held.
 *
 * This MAY be used to open a file the first time if there is no need for
 * open by name or create semantics. One example would be 9P lopen.
 *
 * @param [in] objectHandle     File on which to operate
 * @param [in] state            state_t to use for this operation
 * @param [in] openflags        Mode for re-open
 *
 * @returns: FSAL status
 */
static fsal_status_t reopen2(struct fsal_obj_handle *objectHandle, struct state_t *state,
                             fsal_openflags_t openflags) {
	return openByHandle(objectHandle, state, openflags, FSAL_NO_CREATE, NULL, NULL);
}

/**
 * @brief Create a special file.
 *
 * Create a special file. This function creates a new special file.
 *
 * @param [in] directoryHandle     Directory in which to create the object
 * @param [in] name                Name of object to create
 * @param [in] nodeType            Type of special file to create
 * @param [in] attributesToSet     Attributes to set on newly created object
 * @param [in] createdObject       Newly created object
 * @param [in] attributes          Optional attributes for newly created object
 * @param[in,out] parentPreAttributes  Optional attributes for parent dir before the operation.
 *                                     Should be atomic.
 * @param[in,out] parentPostAttributes Optional attributes for parent dir after the operation.
 *                                     Should be atomic.
 *
 * \see fsal_api.h for more information
 *
 * @returns: FSAL status
 */
static fsal_status_t mknode(struct fsal_obj_handle *directoryHandle, const char *name,
                            object_file_type_t nodeType, struct fsal_attrlist *attributesToSet,
                            struct fsal_obj_handle **createdObject,
                            struct fsal_attrlist *attributes,
                            struct fsal_attrlist *parentPreAttributes,
                            struct fsal_attrlist *parentPostAttributes) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *directory = NULL;
	struct SaunaFSHandle *handle = NULL;
	struct sau_entry entry;
	mode_t unixMode = 0;
	dev_t unixDev = 0;

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

	LogFullDebug(COMPONENT_FSAL,
	             "export = %" PRIu16 " parent_inode = %" PRIiNode " mode = %" PRIo32 " name = %s",
	             export->export.export_id, directory->inode, attributesToSet->mode, name);

	unixMode = fsal2unix_mode(attributesToSet->mode) &
	           ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export);

	switch (nodeType) {
	case BLOCK_FILE:
		unixMode |= S_IFBLK;
		unixDev = makedev(attributesToSet->rawdev.major, attributesToSet->rawdev.minor);
		break;
	case CHARACTER_FILE:
		unixMode |= S_IFCHR;
		unixDev = makedev(attributesToSet->rawdev.major, attributesToSet->rawdev.minor);
		break;
	case FIFO_FILE:
		unixMode |= S_IFIFO;
		break;
	case SOCKET_FILE:
		unixMode |= S_IFSOCK;
		break;
	default:
		LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodeType);

		return fsalstat(ERR_FSAL_INVAL, EINVAL);
	}

	int retvalue = saunafs_mknode(export->fsInstance, &op_ctx->creds, directory->inode, name,
	                              unixMode, unixDev, &entry);

	if (retvalue < 0) {
		return fsalLastError();
	}

	handle = allocateHandle(&entry.attr, export);
	*createdObject = &handle->handle;

	// We handled the mode above
	FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);

	if (attributesToSet->valid_mask) {
		fsal_status_t status;
		// Setting attributes for the created object
		status = (*createdObject)->obj_ops->setattr2(*createdObject, false, NULL, attributesToSet);

		if (FSAL_IS_ERROR(status)) {
			LogFullDebug(COMPONENT_FSAL, "setattr2 status = %s", fsal_err_txt(status));

			// Release the handle we just allocated
			(*createdObject)->obj_ops->release(*createdObject);
			*createdObject = NULL;
		}
	} else if (attributes != NULL) {
		// Since we haven't set any attributes other than what was set on create,
		// just use the stat results we used to create the fsal_obj_handle
		posix2fsal_attributes_all(&entry.attr, attributes);
	}

	FSAL_SET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE);
	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Read the content of a link.
 *
 * File object operations.
 * This function reads the content of a symbolic link. The FSAL will
 * allocate a buffer and store its address and the link length in the
 * link_content gsh_buffdesc. The caller must free this buffer with
 * gsh_free.
 *
 * The symlink content passed back must be null terminated and the length
 * indicated in the buffer description must include the terminator.
 *
 * @param [in]  objectHandle  Link to read
 * @param [out] buffer        Buffer descriptor to which the FSAL will
 *                            store the address of the buffer holding the link
 *                            and the link length
 * @param [out] refresh       true if the content are to be retrieved from
 *                            the underlying filesystem rather than cache
 *
 * @returns: FSAL status
 */
static fsal_status_t readlink_(struct fsal_obj_handle *objectHandle, struct gsh_buffdesc *buffer,
                               bool refresh) {
	(void)refresh;
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;
	char result[SAUNAFS_MAX_READLINK_LENGTH];

	if (objectHandle->type != SYMBOLIC_LINK) {
		return fsalstat(ERR_FSAL_FAULT, 0);
	}

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

	LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIiNode,
	             export->export.export_id, handle->inode);

	int size = saunafs_readlink(export->fsInstance, &op_ctx->creds, handle->inode, result,
	                            SAUNAFS_MAX_READLINK_LENGTH);

	// saunafs_readlink() returns the size of the read link if succeed.
	// Otherwise returns -1 to indicate an error occurred.
	if (size < 0) { return fsalLastError(); }

	size = MIN(size, SAUNAFS_MAX_READLINK_LENGTH);
	buffer->addr = gsh_strldup(result, size, &buffer->len);

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Return open status of a state.
 *
 * This function returns open flags representing the current open status
 * for a state. The st_lock must be held.
 *
 * @param [in] objectHandle     File owning state
 * @param [in] state            File state to interrogate
 *
 * @returns: Flags representing current open status
 */
static fsal_openflags_t status2(struct fsal_obj_handle *objectHandle, struct state_t *state) {
	(void)objectHandle;
	struct SaunaFSFd *sfsFd = &((struct SaunaFSStateFd *)state)->saunafsFd;

	return sfsFd->fsalFd.openflags;
}

/**
 * @brief Merge a duplicate handle with an original handle.
 *
 * This function is used if an upper layer detects that a duplicate object
 * handle has been created. It allows the FSAL to merge anything from the
 * duplicate back into the original.
 *
 * The caller must release the object (the caller may have to close files
 * if the merge is unsuccessful).
 *
 * @param [in] originalHandle     Original handle
 * @param [in] toMergeHandle      Handle to merge into original
 *
 * @returns: FSAL status
 */
static fsal_status_t merge(struct fsal_obj_handle *originalHandle,
                           struct fsal_obj_handle *toMergeHandle) {
	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);

	if (originalHandle->type == REGULAR_FILE && toMergeHandle->type == REGULAR_FILE) {
		// We need to merge the share reservations on this file.
		// This could result in ERR_FSAL_SHARE_DENIED.
		struct SaunaFSHandle *original = NULL;
		struct SaunaFSHandle *toMerge = NULL;

		original = container_of(originalHandle, struct SaunaFSHandle, handle);
		toMerge = container_of(toMergeHandle, struct SaunaFSHandle, handle);

		// This can block over an I/O operation
		status = merge_share(originalHandle, &original->share, &toMerge->share);
	}

	return status;
}

/**
 * @brief Reserve/Deallocate space in a region of a file.
 *
 * @param [in] objectHandle     File to which bytes should be allocated
 * @param [in] state            Open stateid under which to do the allocation
 * @param [in] offset           Offset at which to begin the allocation
 * @param [in] length           Length of the data to be allocated
 * @param [in] allocate         Should space be allocated or deallocated?
 *
 * @returns: FSAL status
 */
static fsal_status_t fallocate(struct fsal_obj_handle *objectHandle, struct state_t *state,
                               uint64_t offset, uint64_t length, bool allocate) {
	struct SaunaFSExport *export = NULL;
	struct SaunaFSHandle *handle = NULL;

	fsal_status_t status = {0, 0};
	fsal_status_t status2 = {0, 0};

	struct SaunaFSFd *fileDescriptor = NULL;
	struct SaunaFSFd emptyFileDescriptor = { FSAL_FD_INIT, NULL };
	struct fsal_fd *outFileDescriptor = NULL;

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

	// Indicate a desire to start io and get a usable file descriptor
	status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd,
	                       &emptyFileDescriptor.fsalFd, state, FSAL_O_WRITE, false, NULL, false,
	                       &handle->share);

	if (FSAL_IS_ERROR(status)) {
		LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status));

		return status;
	}

	struct stat posixAttributes;

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

	posixAttributes.st_mode = allocate ? 0 : FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE;

	// Get stat to obtain the current size
	sau_attr_reply_t currentStats;
	sau_attr_reply_t reply;

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

	if (retvalue < 0) {
		status2 = fsal_complete_io(objectHandle, outFileDescriptor);
		LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

		if (state == NULL) {
			// We did I/O without a state so we need to release the temp share reservation acquired.

			// Release the share reservation now by updating the counters.
			update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED);
		}

		return fsalLastError();
	}

	fileDescriptor = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd);

	if (allocate) {
		// Allocate
		if (offset + length > currentStats.attr.st_size) {
			posixAttributes.st_size = offset + length;

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

			if (retvalue < 0) {
				status2 = fsal_complete_io(objectHandle, outFileDescriptor);
				LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

				if (state == NULL) {
					// We did I/O without a state so we need to release the
					// temp share reservation acquired.

					// Release the share reservation now by updating the counters.
					update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE,
					                             FSAL_O_CLOSED);
				}

				return fsalLastError();
			}

			retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, fileDescriptor->fd);

			if (retvalue < 0) { status = fsalLastError(); }
		}
	} else if (allocate == false) {
		// Deallocate
		// Initialize the zero-buffer
		void *buffer = malloc(length);

		memset(buffer, 0, length);

		// Write the interval [offset..offset + length] with zeros
		ssize_t bytes = saunafs_write(export->fsInstance, &op_ctx->creds, fileDescriptor->fd,
		                              offset, length, buffer);

		free(buffer);

		if (bytes < 0) {
			status2 = fsal_complete_io(objectHandle, outFileDescriptor);
			LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

			if (state == NULL) {
				// We did I/O without a state so we need to release the temp share reservation
				// acquired.

				// Release the share reservation now by updating the counters.
				update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE,
				                             FSAL_O_CLOSED);
			}

			return fsalLastError();
		}

		// Set the original size because deallocation must not change file size
		posixAttributes.st_size = currentStats.attr.st_size;

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

		if (retvalue < 0) {
			status2 = fsal_complete_io(objectHandle, outFileDescriptor);
			LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

			if (state == NULL) {
				// We did I/O without a state so we need to release the temp share reservation
				// acquired.

				// Release the share reservation now by updating the counters.
				update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE,
				                             FSAL_O_CLOSED);
			}

			return fsalLastError();
		}

		retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, fileDescriptor->fd);

		if (retvalue < 0) { status = fsalLastError(); }
	}

	status2 = fsal_complete_io(objectHandle, outFileDescriptor);
	LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2));

	if (state == NULL) {
		// We did I/O without a state so we need to release the temp share reservation acquired.

		// Release the share reservation now by updating the counters.
		update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED);
	}

	return status;
}

/**
 * @brief Function to close a file for a given handle.
 *
 * @param[in]  objectHandle     File on which to operate
 * @param[in]  fd               File descriptor to be closed
 *
 * @return FSAL status.
 */
static fsal_status_t close_func(struct fsal_obj_handle *objectHandle, struct fsal_fd *fd) {
	struct SaunaFSHandle *handle = container_of(objectHandle, struct SaunaFSHandle, handle);
	return closeFileDescriptor(handle, (struct SaunaFSFd *)fd);
}

/**
 * @brief Get extended attribute.
 *
 * This function gets an extended attribute of an object.
 *
 * @param [in]  objectHandle        Input object to query
 * @param [in]  xattributeName      Input extended attribute name
 * @param [out] xattributeValue     Output extended attribute value
 *
 * @returns: FSAL status
 */
static fsal_status_t getxattrs(struct fsal_obj_handle *objectHandle, xattrkey4 *xattributeName,
                               xattrvalue4 *xattributeValue) {
	struct SaunaFSExport *export = NULL;
	export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export);

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

	size_t curr_size = 0;
	int retvalue = saunafs_getxattr(export->fsInstance, &op_ctx->creds, handle->inode,
	                                xattributeName->utf8string_val, xattributeValue->utf8string_len,
	                                &curr_size, (uint8_t *)xattributeValue->utf8string_val);

	if (retvalue < 0) {
		LogFullDebug(COMPONENT_FSAL, "GETXATTRS failed returned rc = %d ", retvalue);
		return saunafsToFsalError(retvalue);
	}

	if (curr_size && curr_size <= xattributeValue->utf8string_len) {
		// Updating the real size
		xattributeValue->utf8string_len = curr_size;
		// Make sure utf8string is NULL terminated
		xattributeValue->utf8string_val[curr_size] = '\0';
	}

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief Set extended attribute.
 *
 * This function sets an extended attribute of an object.
 *
 * @param [in] objectHandle        Input object to set
 * @param [in] option              Input extended attribute type
 * @param [in] xattributeName      Input extended attribute name to set
 * @param [in] xattributeValue     Input extended attribute value to set
 *
 * @returns: FSAL status
 */
static fsal_status_t setxattrs(struct fsal_obj_handle *objectHandle, setxattr_option4 option,
                               xattrkey4 *xattributeName, xattrvalue4 *xattributeValue) {
	struct SaunaFSExport *export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export);

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

	int retvalue = saunafs_setxattr(
	    export->fsInstance, &op_ctx->creds, handle->inode, xattributeName->utf8string_val,
	    (const uint8_t *)xattributeValue->utf8string_val, xattributeValue->utf8string_len, option);

	if (retvalue < 0) {
		LogDebug(COMPONENT_FSAL, "SETXATTRS returned rc %d", retvalue);
		return saunafsToFsalError(retvalue);
	}

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

/**
 * @brief List extended attributes.
 *
 * This function lists the extended attributes of an object.
 *
 * @param [in]     objectHandle         Input object to list
 * @param [in]     maximumNameSize      Input maximum number of bytes for names
 * @param [in,out] cookie               In/out cookie
 * @param [out]    eof                  Output eof set if no more extended attributes
 * @param [out]    xattributesNames     Output list of extended attribute names this buffer size is
 *                                      double the size of maximumNameSize to allow
 *                                      for component4 overhead
 *
 * @returns: FSAL status
 */
static fsal_status_t listxattrs(struct fsal_obj_handle *objectHandle, count4 maximumNameSize,
                                nfs_cookie4 *cookie, bool_t *eof, xattrlist4 *xattributesNames) {
	char *buffer = NULL;
	fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0);

	struct SaunaFSExport *export = NULL;
	export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export);

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

	LogFullDebug(COMPONENT_FSAL, "in cookie %llu length %d", (unsigned long long)*cookie,
	             maximumNameSize);

	// Size of list of extended attributes
	size_t curr_size = 0;

	// First time, the function is called to get the size of xattr list
	int retvalue =
	    saunafs_listxattr(export->fsInstance, &op_ctx->creds, handle->inode, 0, &curr_size, NULL);

	if (retvalue < 0) {
		LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d", retvalue);
		return saunafsToFsalError(retvalue);
	}

	// If xattr were retrieved and they can be allocated
	if (curr_size && curr_size < maximumNameSize) {
		buffer = gsh_malloc(curr_size);

		// Second time the function is called to retrieve the xattr list
		retvalue = saunafs_listxattr(export->fsInstance, &op_ctx->creds, handle->inode, curr_size,
		                             &curr_size, buffer);

		if (retvalue < 0) {
			LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d", retvalue);
			gsh_free(buffer);
			return saunafsToFsalError(retvalue);
		}

		// Setting retrieved extended attributes to Ganesha
		status = fsal_listxattr_helper(buffer, curr_size, maximumNameSize, cookie, eof,
		                               xattributesNames);

		// Releasing allocated buffer
		gsh_free(buffer);
	}

	return status;
}

/**
 * @brief Remove extended attribute.
 *
 * This function removes an extended attribute of an object.
 *
 * @param [in] objectHandle       Input object to set
 * @param [in] xattributeName     Input xattr name to remove
 *
 * @returns: FSAL status
 */
static fsal_status_t removexattrs(struct fsal_obj_handle *objectHandle, xattrkey4 *xattributeName) {
	struct SaunaFSExport *export = NULL;
	export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export);

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

	int retvalue = saunafs_removexattr(export->fsInstance, &op_ctx->creds, handle->inode,
	                                   xattributeName->utf8string_val);

	if (retvalue < 0) {
		LogFullDebug(COMPONENT_FSAL, "REMOVEXATTR returned rc %d", retvalue);
		return saunafsToFsalError(retvalue);
	}

	return fsalstat(ERR_FSAL_NO_ERROR, 0);
}

void handleOperationsInit(struct fsal_obj_ops *ops) {
	fsal_default_obj_ops_init(ops);
	ops->release = release;
	ops->lookup = lookup;
	ops->readdir = readdir_;
	ops->getattrs = getattrs;
	ops->handle_to_wire = handle_to_wire;
	ops->handle_to_key = handle_to_key;
	ops->open2 = open2;
	ops->read2 = read2;
	ops->mkdir = mkdir_;
	ops->link = link_;
	ops->rename = rename_;
	ops->unlink = unlink_;
	ops->close = close_;
	ops->write2 = write2;
	ops->commit2 = commit2;
	ops->setattr2 = setattr2;
	ops->close2 = close2;
	ops->symlink = symlink_;
	ops->lock_op2 = lock_op2;
	ops->close_func = close_func;
	ops->reopen_func = reopen_func;
	ops->reopen2 = reopen2;
	ops->mknode = mknode;
	ops->readlink = readlink_;
	ops->status2 = status2;
	ops->merge = merge;
	ops->fallocate = fallocate;
	ops->getxattrs = getxattrs;
	ops->setxattrs = setxattrs;
	ops->listxattrs = listxattrs;
	ops->removexattrs = removexattrs;
}

/**
 * @brief Allocate a new file handle.
 *
 * This function constructs a new SaunaFS FSAL object handle and attaches
 * it to the export. After this call the attributes have been filled
 * in and the handdle is up-to-date and usable.
 *
 * @param[in] attribute     stat attributes for the handle
 * @param[in] export        The export on which the object lives
 *
 * @returns: saunafs_handle instance or NULL.
 */
struct SaunaFSHandle *allocateHandle(const struct stat *attribute, struct SaunaFSExport *export) {
	struct SaunaFSHandle *result = gsh_calloc(1, sizeof(struct SaunaFSHandle));

	result->inode = attribute->st_ino;
	result->key.moduleId = FSAL_ID_SAUNAFS;
	result->key.exportId = export->export.export_id;
	result->key.inode = attribute->st_ino;

	fsal_obj_handle_init(&result->handle, &export->export, posix2fsal_type(attribute->st_mode),
	                     true);

	result->handle.obj_ops = &SaunaFS.handleOperations;
	result->handle.fsid = posix2fsal_fsid(attribute->st_dev);
	result->handle.fileid = attribute->st_ino;
	result->export = export;

	if (result->handle.type == REGULAR_FILE) {
		init_fsal_fd(&result->fd.fsalFd, FSAL_FD_GLOBAL, op_ctx->fsal_export);
	}

	return result;
}

/**
 * @brief Release all resources for a handle.
 *
 * @param[in] object     Handle to release
 */
void deleteHandle(struct SaunaFSHandle *object) {
	fsal_obj_handle_fini(&object->handle, true);
	gsh_free(object);
}
