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

/**
 * @file    nfs4_op_open.c
 * @brief   NFS4_OP_OPEN
 *
 * Function implementing the NFS4_OP_OPEN operation and support code.
 */

#include "config.h"
#include "log.h"
#include "fsal.h"
#include "nfs_core.h"
#include "nfs_exports.h"
#include "sal_functions.h"
#include "nfs_proto_functions.h"
#include "nfs_proto_tools.h"
#include "nfs_convert.h"
#include "fsal_convert.h"
#include "nfs_creds.h"
#include "export_mgr.h"
#include "nfs_rpc_callback.h"

#include "gsh_lttng/gsh_lttng.h"
#if defined(USE_LTTNG) && !defined(LTTNG_PARSING)
#include "gsh_lttng/generated_traces/nfs4.h"
#endif

static const char *open_tag = "OPEN";

/**
 * @brief Copy an OPEN result
 *
 * This function copies an open result to the supplied destination.
 *
 * @param[out] res_dst Buffer to which to copy the result
 * @param[in]  res_src The result to copy
 */
void nfs4_op_open_CopyRes(OPEN4res *res_dst, OPEN4res *res_src)
{
	res_dst->OPEN4res_u.resok4.attrset = res_src->OPEN4res_u.resok4.attrset;
}

/**
 * @brief Create an NFSv4 filehandle
 *
 * This function creates an NFSv4 filehandle from the supplied file
 * and sets it to be the current filehandle.
 *
 * @note This calls @ref set_current_entry which takes a ref; this then drops
 * it's ref.
 *
 * @param[in,out] data   Compound's data
 * @param[in]     obj    File
 *
 * @retval NFS4_OK on success.
 * @retval Valid errors for NFS4_OP_OPEN.
 */

static nfsstat4 open4_create_fh(compound_data_t *data,
				struct fsal_obj_handle *obj)
{
	/* Building a new fh */
	if (!nfs4_FSALToFhandle(false, &data->currentFH, obj,
				op_ctx->ctx_export)) {
		obj->obj_ops->put_ref(obj);
		return NFS4ERR_SERVERFAULT;
	}

	/* Update the current entry */
	set_current_entry(data, obj);

	/* Put our ref */
	obj->obj_ops->put_ref(obj);

	return NFS4_OK;
}

/**
 * @brief Validate claim type
 *
 * Check that the claim type specified is allowed and return the
 * appropriate error code if not.
 *
 * @param[in]  data         Compound's data
 * @param[in]  claim        Claim type
 * @param[out] grace_ref    Did this function acquire a grace reference?
 *
 * @retval NFS4_OK claim is valid.
 * @retval NFS4ERR_GRACE new open not allowed in grace period.
 * @retval NFS4ERR_NO_GRACE reclaim not allowed after grace period or
 *         reclaim complete.
 * @retval NFS4ERR_NOTSUPP claim type not supported by minor version.
 * @retval NFS4ERR_INVAL unknown claim type.
 */

static nfsstat4 open4_validate_claim(compound_data_t *data,
				     open_claim_type4 claim,
				     nfs_client_id_t *clientid, bool *grace_ref)
{
	/* Return code */
	nfsstat4 status = NFS4_OK;
	/* does this claim require a grace period? */
	bool want_grace = false;
	bool fsal_grace_support =
		op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
							 fso_grace_method);
	/* do we need a reference on the current state of grace? */
	bool take_ref = !fsal_grace_support;

	switch (claim) {
	case CLAIM_NULL:
		if ((data->minorversion > 0) &&
		    !clientid->cid_cb.v41.cid_reclaim_complete)
			status = NFS4ERR_GRACE;
		break;

	case CLAIM_FH:
		if (data->minorversion == 0)
			status = NFS4ERR_NOTSUPP;
		break;

	case CLAIM_DELEGATE_PREV:
		status = NFS4ERR_NOTSUPP;
		break;

	case CLAIM_PREVIOUS:
		want_grace = true;
		if ((!clientid->cid_allow_reclaim && !fsal_grace_support) ||
		    ((data->minorversion > 0) &&
		     clientid->cid_cb.v41.cid_reclaim_complete))
			status = NFS4ERR_NO_GRACE;
		break;

	case CLAIM_DELEGATE_CUR:
		take_ref = false;
		break;

	case CLAIM_DELEG_CUR_FH:
		take_ref = false;
		if (data->minorversion == 0)
			status = NFS4ERR_NOTSUPP;
		break;

	case CLAIM_DELEG_PREV_FH:
		status = NFS4ERR_NOTSUPP;
		break;

	default:
		status = NFS4ERR_INVAL;
	}

	if (status == NFS4_OK) {
		if (take_ref) {
			if (nfs_get_grace_status(want_grace)) {
				*grace_ref = true;
			} else {
				status = want_grace ? NFS4ERR_NO_GRACE
						    : NFS4ERR_GRACE;
			}
		} else {
			*grace_ref = false;
		}
	}
	return status;
}

/**
 * @brief Validate and create an open owner
 *
 * This function finds or creates an owner to be associated with the
 * requested open state.
 *
 * @param[in]     op       Arguments to OPEN4 operation
 * @param[in,out] data     Compound's data
 * @param[out]    res      Response to OPEN4 operation
 * @param[in]     clientid The cientid record for this request
 * @param[out]    owner    The found/created owner
 *
 * @return false if error or replay (res_OPEN4->status is already set),
 *         true otherwise.
 */

bool open4_open_owner(struct nfs_argop4 *op, compound_data_t *data,
		      struct nfs_resop4 *res, nfs_client_id_t *clientid,
		      state_owner_t **owner)
{
	/* Shortcut to open args */
	OPEN4args *const arg_OPEN4 = &(op->nfs_argop4_u.opopen);
	/* Shortcut to open args */
	OPEN4res *const res_OPEN4 = &(res->nfs_resop4_u.opopen);
	/* The parsed-out name of the open owner */
	state_nfs4_owner_name_t owner_name;
	/* Indicates if the owner is new */
	bool_t isnew;
	/* Return value of FSAL operations */
	fsal_status_t status = { 0, 0 };
	struct fsal_obj_handle *obj_lookup = NULL;

	/* Is this open_owner known? If so, get it so we can use
	 * replay cache
	 */
	convert_nfs4_open_owner(&arg_OPEN4->owner, &owner_name);

	/* If this open owner is not known yet, allocate and set up a new one */
	*owner = create_nfs4_owner(&owner_name, clientid,
				   STATE_OPEN_OWNER_NFSV4, NULL, 0, &isnew,
				   CARE_ALWAYS, data->minorversion != 0);

	LogStateOwner("Open: ", *owner);

	if (*owner == NULL) {
		res_OPEN4->status = NFS4ERR_RESOURCE;
		LogEvent(
			COMPONENT_STATE,
			"NFS4 OPEN returning NFS4ERR_RESOURCE for CLAIM_NULL (could not create NFS4 Owner");
		return false;
	}

	/* Seqid checking is only proper for reused NFSv4.0 owner */
	if (isnew || (data->minorversion != 0))
		return true;

	if (arg_OPEN4->seqid == 0) {
		LogDebug(
			COMPONENT_STATE,
			"Previously known open_owner is used with seqid=0, ask the client to confirm it again");
		(*owner)->so_owner.so_nfs4_owner.so_confirmed = false;
		return true;
	}

	/* Check for replay */
	if (Check_nfs4_seqid(*owner, arg_OPEN4->seqid, op, data->current_obj,
			     res, open_tag)) {
		/* No replay */
		return true;
	}

	/* Response is setup for us and LogDebug told what was
	 * wrong.
	 *
	 * Or if this is a seqid replay, find the file entry
	 * and update currentFH
	 */
	if (res_OPEN4->status == NFS4_OK) {
		utf8string *utfile;
		open_claim4 *oc = &arg_OPEN4->claim;

		/* Load up CLAIM_DELEGATE_CUR file */

		switch (oc->claim) {
		case CLAIM_NULL:
			utfile = &oc->open_claim4_u.file;
			break;
		case CLAIM_DELEGATE_CUR:
			utfile = &oc->open_claim4_u.delegate_cur_info.file;
			break;
		case CLAIM_DELEGATE_PREV:
		default:
			return false;
		}
		/* Check if filename is correct */
		res_OPEN4->status =
			nfs4_utf8string_scan(utfile, UTF8_SCAN_PATH_COMP);

		if (res_OPEN4->status != NFS4_OK)
			return false;

		status = fsal_lookup(data->current_obj, utfile->utf8string_val,
				     &obj_lookup, NULL);

		if (obj_lookup == NULL) {
			res_OPEN4->status = nfs4_Errno_status(status);
			return false;
		}
		res_OPEN4->status = open4_create_fh(data, obj_lookup);
	}

	return false;
}

/**
 * @brief Check delegation claims while opening a file
 *
 * This function implements the CLAIM_DELEGATE_CUR claim.
 *
 * @param[in]     arg   OPEN4 arguments
 * @param[in,out] data  Comopund's data
 */
static nfsstat4 open4_claim_deleg(OPEN4args *arg, compound_data_t *data)
{
	open_claim_type4 claim = arg->claim.claim;
	stateid4 *rcurr_state;
	struct fsal_obj_handle *obj_lookup;
	const utf8string *utfname;
	fsal_status_t fsal_status;
	nfsstat4 status;
	state_t *found_state = NULL;

	if (!(op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
						       fso_delegations_w) ||
	      op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
						       fso_delegations_r))) {
		LogDebug(
			COMPONENT_STATE,
			"NFS4 OPEN returning NFS4ERR_NOTSUPP for CLAIM_DELEGATE");
		return NFS4ERR_NOTSUPP;
	}

	assert((claim == CLAIM_DELEGATE_CUR) || (claim == CLAIM_DELEG_CUR_FH));

	if (claim == CLAIM_DELEG_CUR_FH) {
		rcurr_state = &arg->claim.open_claim4_u.oc_delegate_stateid;
		goto find_state;
	}

	utfname = &arg->claim.open_claim4_u.delegate_cur_info.file;
	rcurr_state =
		&arg->claim.open_claim4_u.delegate_cur_info.delegate_stateid;

	LogDebug(COMPONENT_NFS_V4, "file name: %s", utfname->utf8string_val);

	/* Check if filename is correct */
	status = nfs4_utf8string_scan(utfname, UTF8_SCAN_PATH_COMP);
	if (status != NFS4_OK) {
		LogDebug(COMPONENT_NFS_V4, "Invalid filename");
		return status;
	}

	/* Does a file with this name already exist ? */
	fsal_status = fsal_lookup(data->current_obj, utfname->utf8string_val,
				  &obj_lookup, NULL);

	if (FSAL_IS_ERROR(fsal_status)) {
		LogDebug(COMPONENT_NFS_V4, "%s lookup failed.",
			 utfname->utf8string_val);
		return nfs4_Errno_status(fsal_status);
	}

	status = open4_create_fh(data, obj_lookup);
	if (status != NFS4_OK) {
		LogDebug(COMPONENT_NFS_V4, "open4_create_fh failed");
		return status;
	}

find_state:
	found_state = nfs4_State_Get_Pointer(rcurr_state->other);

	if (found_state == NULL) {
		LogDebug(COMPONENT_NFS_V4,
			 "state not found with CLAIM_DELEGATE_CUR");
		return NFS4ERR_BAD_STATEID;
	} else {
		/* FIXME: vet stateid here */
		if (isFullDebug(COMPONENT_NFS_V4)) {
			char str[LOG_BUFF_LEN] = "\0";
			struct display_buffer dspbuf = { sizeof(str), str,
							 str };

			display_stateid(&dspbuf, found_state);

			LogFullDebug(COMPONENT_NFS_V4, "found matching %s",
				     str);
		}
		dec_state_t_ref(found_state);
	}

	LogFullDebug(COMPONENT_NFS_V4, "done with CLAIM_DELEGATE_CUR");

	return NFS4_OK;
}

/**
 * @brief Create a new delegation state then get the delegation.
 *
 * Create a new delegation state for this client and file.
 * Then attempt to get a LEASE lock to delegate the file
 * according to whether the client opened READ or READ/WRITE.
 *
 * @note st_lock must be held
 *
 * @param[in] data Compound data for this request
 * @param[in] op NFS arguments for the request
 * @param[in] open_state Open state for the inode to be delegated.
 * @param[in] openowner Open owner of the open state.
 * @param[in] client The client that will own the delegation.
 * @param[in/out] resok Delegation attempt result to be returned to client.
 * @param[in] prerecall flag for reclaims.
 */
static void get_delegation(compound_data_t *data, OPEN4args *args,
			   state_t *open_state, state_owner_t *openowner,
			   nfs_client_id_t *client, OPEN4resok *resok,
			   bool prerecall)
{
	state_status_t state_status;
	union state_data state_data;
	open_delegation_type4 deleg_type;
	state_owner_t *clientowner = &client->cid_owner;
	struct state_refer refer;
	state_t *new_state = NULL;
	struct state_hdl *ostate;
	open_write_delegation4 *writeres =
		&resok->delegation.open_delegation4_u.write;
	open_read_delegation4 *readres =
		&resok->delegation.open_delegation4_u.read;
	open_none_delegation4 *whynone =
		&resok->delegation.open_delegation4_u.od_whynone;

	ostate = data->current_obj->state_hdl;
	if (!ostate) {
		LogFullDebug(COMPONENT_STATE, "Could not get file state");
		whynone->ond_why = WND4_RESOURCE;
		/* passing 0 current delegations since we don't have
		 * ostate and file handle */
		DEC_G_Total_Num_Files_Delegated(0);
		return;
	}

	/* Record the sequence info */
	if (data->minorversion > 0) {
		memcpy(refer.session, data->session->session_id,
		       sizeof(sessionid4));
		refer.sequence = data->sequence;
		refer.slot = data->slotid;
	}

	if (args->share_access & OPEN4_SHARE_ACCESS_WRITE) {
		deleg_type = OPEN_DELEGATE_WRITE;
	} else {
		assert(args->share_access & OPEN4_SHARE_ACCESS_READ);
		deleg_type = OPEN_DELEGATE_READ;
	}

	/* Check if client requested delegation timestamps (RFC 9754).
	 * If so, upgrade delegation type to include timestamp support.
	 * Server needs to grant OPEN_DELEGATE_READ_ATTRS_DELEG or
	 * OPEN_DELEGATE_WRITE_ATTRS_DELEG when the client requests
	 * timestamps. Without this, server won't be granting these
	 * delegations and timestamps won't be tracked.
	 */
	if (data->minorversion >= NFS4_MINOR_VERS_2 &&
	    (args->share_access & OPEN4_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS)) {
		if (deleg_type == OPEN_DELEGATE_WRITE) {
			deleg_type = OPEN_DELEGATE_WRITE_ATTRS_DELEG;
			LogDebug(
				COMPONENT_STATE,
				"Client requested delegation timestamps, granting WRITE_ATTRS_DELEG");
		} else if (deleg_type == OPEN_DELEGATE_READ) {
			deleg_type = OPEN_DELEGATE_READ_ATTRS_DELEG;
			LogDebug(
				COMPONENT_STATE,
				"Client requested delegation timestamps, granting READ_ATTRS_DELEG");
		}
	}

	LogDebug(COMPONENT_STATE, "Attempting to grant %s delegation (type=%d)",
		 deleg_type == OPEN_DELEGATE_WRITE  ? "WRITE"
		 : deleg_type == OPEN_DELEGATE_READ ? "READ"
		 : deleg_type == OPEN_DELEGATE_WRITE_ATTRS_DELEG
			 ? "WRITE_ATTRS_DELEG"
		 : deleg_type == OPEN_DELEGATE_READ_ATTRS_DELEG
			 ? "READ_ATTRS_DELEG"
			 : "UNKNOWN",
		 deleg_type);

	init_new_deleg_state(&state_data, deleg_type, client);

	/* Saving the openstate related to delegation. This will be used
	 * in creating delegation-related FD (related_fd) in fsal_start_io()
	 */
	memcpy(state_data.deleg.openstate_key, open_state->stateid_other,
	       OTHERSIZE);

	/* Add the delegation state */
	state_status = state_add_impl(data->current_obj, STATE_TYPE_DELEG,
				      &state_data, clientowner, &new_state,
				      data->minorversion > 0 ? &refer : NULL);
	if (state_status != STATE_SUCCESS) {
		LogDebug(
			COMPONENT_STATE,
			"get delegation call failed to add state with status %s",
			state_err_str(state_status));
		whynone->ond_why = WND4_RESOURCE;
		DEC_G_Total_Num_Files_Delegated(
			ostate->file.fdeleg_stats.fds_curr_delegations);
		return;
	}
	new_state->state_seqid++;

	LogFullDebugOpaque(COMPONENT_STATE,
			   "delegation state added, stateid: %s", 100,
			   new_state->stateid_other, OTHERSIZE);

	/* acquire_lease_lock() gets the delegation from FSAL */
	state_status = acquire_lease_lock(ostate, clientowner, new_state);
	if (state_status != STATE_SUCCESS) {
		/* Decrement total num_files_delegated even if this is a
		 * reclaim if fds_curr_delegations is equal to zero and we
		 * can't acquire the lease lock. This is the convention
		 * followed in update_delegation_stats
		 * following the same here */
		DEC_G_Total_Num_Files_Delegated(
			ostate->file.fdeleg_stats.fds_curr_delegations);
		if (args->claim.claim != CLAIM_PREVIOUS) {
			LogDebug(
				COMPONENT_STATE,
				"get delegation call added state but failed to lock with status %s",
				state_err_str(state_status));
			state_del_locked(new_state);
			dec_state_t_ref(new_state);
			if (state_status == STATE_LOCK_CONFLICT)
				whynone->ond_why = WND4_CONTENTION;
			else
				whynone->ond_why = WND4_RESOURCE;
			return;
		}
		prerecall = true;
	}

	/* Initialize delegation state for both standard and ATTRS delegation
	 * types.WRITE/WRITE_ATTRS use space limits and write permissions,
	 * while READ/READ_ATTRS set read permissions and stateid accordingly.
	 */
	resok->delegation.delegation_type = deleg_type;
	ostate->file.fdeleg_stats.fds_deleg_type = deleg_type;
	if (deleg_type == OPEN_DELEGATE_WRITE ||
	    deleg_type == OPEN_DELEGATE_WRITE_ATTRS_DELEG) {
		nfs_space_limit4 *space_limit = &writeres->space_limit;
		space_limit->limitby = NFS_LIMIT_SIZE;
		space_limit->nfs_space_limit4_u.filesize =
			DELEG_SPACE_LIMIT_FILESZ;
		COPY_STATEID(&writeres->stateid, new_state);
		writeres->recall = prerecall;
		get_deleg_perm(&writeres->permissions, deleg_type);
	} else {
		assert(deleg_type == OPEN_DELEGATE_READ ||
		       deleg_type == OPEN_DELEGATE_READ_ATTRS_DELEG);
		COPY_STATEID(&readres->stateid, new_state);
		readres->recall = prerecall;
		get_deleg_perm(&readres->permissions, deleg_type);
	}

	new_state->state_data.deleg.share_access = args->share_access &
						   OPEN4_SHARE_ACCESS_BOTH;
	new_state->state_data.deleg.share_deny = args->share_deny;

	if (isDebug(COMPONENT_STATE)) {
		char str1[LOG_BUFF_LEN / 2] = "\0";
		char str2[LOG_BUFF_LEN / 2] = "\0";
		struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 };
		struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 };

		display_nfs4_owner(&dspbuf1, openowner);
		display_nfs4_owner(&dspbuf2, clientowner);

		LogDebug(COMPONENT_STATE,
			 "get delegation openowner %s clientowner %s status %s",
			 str1, str2, state_err_str(state_status));
	}

	dec_state_t_ref(new_state);
}

static void do_delegation(OPEN4args *arg_OPEN4, OPEN4res *res_OPEN4,
			  compound_data_t *data, state_owner_t *owner,
			  state_t *open_state, nfs_client_id_t *clientid)
{
	OPEN4resok *resok = &res_OPEN4->OPEN4res_u.resok4;
	bool prerecall;
	struct state_hdl *ostate;

	ostate = data->current_obj->state_hdl;
	if (!ostate) {
		LogFullDebug(COMPONENT_STATE, "Could not get file state");
		return;
	}

	/* This will be updated later if we actually delegate */
	if (clientid->cid_minorversion != 0 &&
	    (arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_WANT_DELEG_MASK) != 0)
		resok->delegation.delegation_type = OPEN_DELEGATE_NONE_EXT;
	else
		resok->delegation.delegation_type = OPEN_DELEGATE_NONE;

	/* Client doesn't want a delegation. */
	if (arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_WANT_NO_DELEG) {
		resok->delegation.open_delegation4_u.od_whynone.ond_why =
			WND4_NOT_WANTED;
		LogFullDebug(COMPONENT_STATE, "Client didn't want delegation.");
		return;
	}

	/* Check if delegations are supported */
	if (!deleg_supported(data->current_obj, op_ctx->fsal_export,
			     &op_ctx->export_perms, arg_OPEN4->share_access)) {
		resok->delegation.open_delegation4_u.od_whynone.ond_why =
			WND4_NOT_SUPP_FTYPE;
		LogFullDebug(COMPONENT_STATE, "Delegation type not supported.");
		return;
	}

	/* Decide if we should delegate, then add it. */
	if (can_we_grant_deleg(ostate, open_state) &&
	    should_we_grant_deleg(ostate, clientid, open_state, arg_OPEN4,
				  resok, owner, &prerecall)) {
		/* Update delegation open stats */
		if (ostate->file.fdeleg_stats.fds_num_opens == 0)
			ostate->file.fdeleg_stats.fds_first_open = time(NULL);
		ostate->file.fdeleg_stats.fds_num_opens++;

		LogDebug(COMPONENT_STATE, "Attempting to grant delegation");
		get_delegation(data, arg_OPEN4, open_state, owner, clientid,
			       resok, prerecall);
	}
}

/**
 * @brief NFS4_OP_OPEN create processing for use with extended FSAL API
 *
 * @param[in]     arg         Arguments for nfs4_op
 * @param[in,out] data        Compound request's data
 * @param[out]    res_OPEN4   Results for nfs4_op
 * @param[in,out] verifier    The verifier for exclusive create
 * @param[in,out] createmode  The method of create
 * @param[in,out] sattr       The attributes to set
 * @param[in,out] attrset     The attributes set on open
 *
 */

static void open4_ex_create_args(OPEN4args *arg, compound_data_t *data,
				 OPEN4res *res_OPEN4, void *verifier,
				 enum fsal_create_mode *createmode,
				 struct fsal_attrlist *sattr,
				 struct bitmap4 *attrset)
{
	createhow4 *createhow = &arg->openhow.openflag4_u.how;
	fattr4 *arg_attrs = NULL;

	*createmode = nfs4_createmode_to_fsal(createhow->mode);

	if (createhow->mode == EXCLUSIVE4_1) {
		memcpy(verifier, createhow->createhow4_u.ch_createboth.cva_verf,
		       sizeof(fsal_verifier_t));
	} else if (createhow->mode == EXCLUSIVE4) {
		memcpy(verifier, createhow->createhow4_u.createverf,
		       sizeof(fsal_verifier_t));
	}

	/* Select the correct attributes */
	if (createhow->mode == GUARDED4 || createhow->mode == UNCHECKED4)
		arg_attrs = &createhow->createhow4_u.createattrs;
	else if (createhow->mode == EXCLUSIVE4_1)
		arg_attrs = &createhow->createhow4_u.ch_createboth.cva_attrs;

	if (arg_attrs != NULL) {
		/* Check if asked attributes are correct */
		if (!nfs4_Fattr_Supported(arg_attrs)) {
			res_OPEN4->status = NFS4ERR_ATTRNOTSUPP;
			return;
		}

		if (!nfs4_Fattr_Check_Access(arg_attrs, FATTR4_ATTR_WRITE)) {
			res_OPEN4->status = NFS4ERR_INVAL;
			return;
		}

		/* Convert the attributes */
		if (arg_attrs->attrmask.bitmap4_len != 0) {
			/* Convert fattr4 so nfs4_sattr */
			res_OPEN4->status =
				nfs4_Fattr_To_FSAL_attr(sattr, arg_attrs, data);

			if (res_OPEN4->status != NFS4_OK)
				return;
		}

		if (createhow->mode == EXCLUSIVE4_1) {
			/** @todo FSF: this needs to be corrected in case FSAL
			 *             uses different attributes for the
			 *             verifier.
			 */
			/* Check that we aren't trying to set the verifier
			 * attributes.
			 */
			if (FSAL_TEST_MASK(sattr->valid_mask, ATTR_ATIME) ||
			    FSAL_TEST_MASK(sattr->valid_mask, ATTR_MTIME)) {
				res_OPEN4->status = NFS4ERR_INVAL;
				return;
			}
		}

		/* Now fill in attrset with the attributes requested to be set.
		 */
		memcpy(attrset, &arg_attrs->attrmask, sizeof(*attrset));

		/* If owner or owner_group are set, and the credential was
		 * squashed, then we must squash the set owner and owner_group.
		 */
		squash_setattr(sattr);
	}

	if (createhow->mode == EXCLUSIVE4 || createhow->mode == EXCLUSIVE4_1) {
		/* For an exclusive create we must indicate atime and mtime have
		 * been set for the verifier. The client will use this
		 * indication to know it must use a separate SETATTR to set
		 * these attributes. This is especially important for
		 * EXCLUSIVE4_1 creates that come with attributes.
		 */
		set_attribute_in_bitmap(attrset, FATTR4_TIME_ACCESS);
		set_attribute_in_bitmap(attrset, FATTR4_TIME_MODIFY);
	}

	if (!(sattr->valid_mask & ATTR_MODE)) {
		/* Make sure mode is set, even for exclusive create. */
		sattr->mode = 0600;
		sattr->valid_mask |= ATTR_MODE;
	}
}

/**
 * @brief NFS4_OP_OPEN processing using extended FSAL API
 *
 * This function implements the NFS4_OP_OPEN operation, which
 * potentially creates and opens a regular file.
 *
 * @param[in]     arg         Arguments for nfs4_op
 * @param[in,out] data        Compound request's data
 * @param[out]    res_OPEN4   Results for nfs4_op
 * @param[out]    file_state  state_t created for this operation
 * @param[out]    new_state   Indicates if the state_t is new
 * @param[in,out] attrset     The attrset bitmap to be passed down
 *
 */

static void open4_ex(OPEN4args *arg, compound_data_t *data, OPEN4res *res_OPEN4,
		     nfs_client_id_t *clientid, state_owner_t *owner,
		     state_t **file_state, bool *new_state,
		     struct bitmap4 *attrset,
		     struct fsal_attrlist *parent_pre_attrs,
		     struct fsal_attrlist *parent_post_attrs)
{
	/* Parent directory in which to open the file. */
	struct fsal_obj_handle *parent = NULL;
	/* The entry we associated with the desired file before open. */
	struct fsal_obj_handle *file_obj = NULL;
	/* Indicator that file_obj came from lookup. */
	bool looked_up_file_obj = false;
	/* The in_obj to pass to fsal_open2. */
	struct fsal_obj_handle *in_obj = NULL;
	/* The entry open associated with the file. */
	struct fsal_obj_handle *out_obj = NULL;
	fsal_openflags_t openflags = 0;
	fsal_openflags_t old_openflags = 0;
	enum fsal_create_mode createmode = FSAL_NO_CREATE;
	/* The filename to create */
	char *filename = NULL;
	/* The supplied claim type */
	open_claim_type4 claim = arg->claim.claim;
	fsal_verifier_t verifier;
	struct fsal_attrlist sattr;
	/* Status for fsal calls */
	fsal_status_t status = { 0, 0 };
	/* The open state for the file */
	bool st_lock_held = false;

	/* Make sure the attributes are initialized */
	memset(&sattr, 0, sizeof(sattr));

	/* Make sure... */
	*file_state = NULL;
	*new_state = false;

	/* Pre-process the claim type */
	switch (claim) {
	case CLAIM_NULL:
		/* Check parent */
		parent = data->current_obj;
		in_obj = parent;

		/* Parent must be a directory */
		if (parent->type != DIRECTORY) {
			if (parent->type == SYMBOLIC_LINK) {
				res_OPEN4->status = NFS4ERR_SYMLINK;
				goto out;
			} else {
				res_OPEN4->status = NFS4ERR_NOTDIR;
				goto out;
			}
		}

		/* Validate the utf8 filename */
		res_OPEN4->status =
			nfs4_utf8string_scan(&arg->claim.open_claim4_u.file,
					     UTF8_SCAN_PATH_COMP);

		if (res_OPEN4->status != NFS4_OK)
			goto out;

		filename = arg->claim.open_claim4_u.file.utf8string_val;

		/* Set the createmode if appropriate), pass down attrset */
		if (arg->openhow.opentype == OPEN4_CREATE) {
			open4_ex_create_args(arg, data, res_OPEN4, verifier,
					     &createmode, &sattr, attrset);

			if (res_OPEN4->status != NFS4_OK)
				goto out;
		}

		status = fsal_lookup(
			parent, arg->claim.open_claim4_u.file.utf8string_val,
			&file_obj, NULL);

		if (!FSAL_IS_ERROR(status)) {
			/* Check create situations. */
			if (arg->openhow.opentype == OPEN4_CREATE) {
				if (createmode >= FSAL_EXCLUSIVE) {
					/* Could be a replay, need to continue.
					 */
					LogFullDebug(
						COMPONENT_STATE,
						"EXCLUSIVE open with existing file %s",
						filename);
				} else if (createmode == FSAL_GUARDED) {
					/* This will be a failure no matter'
					 * what.
					 */
					looked_up_file_obj = true;
					res_OPEN4->status = NFS4ERR_EXIST;
					goto out;
				} else {
					/* FSAL_UNCHECKED, may be a truncate
					 * and we need to pass in the case
					 * of fsal_reopen2 case.
					 */
					if (FSAL_TEST_MASK(sattr.valid_mask,
							   ATTR_SIZE) &&
					    sattr.filesize == 0) {
						LogFullDebug(COMPONENT_STATE,
							     "Truncate");
						openflags |= FSAL_O_TRUNC;
					}
				}
			}

			/* We found the file by lookup, set filename to NULL
			 * and remember that we found the entry by lookup.
			 */
			looked_up_file_obj = true;
			filename = NULL;
		} else if (status.major != ERR_FSAL_NOENT ||
			   arg->openhow.opentype != OPEN4_CREATE) {
			/* A real error occurred */
			res_OPEN4->status = nfs4_Errno_status(status);
			goto out;
		}

		break;

		/* Both of these just use the current filehandle. */
	case CLAIM_PREVIOUS:
		owner->so_owner.so_nfs4_owner.so_confirmed = true;
		if (!nfs4_check_deleg_reclaim(clientid, &data->currentFH)) {
			/* It must have been revoked. Can't reclaim.*/
			LogInfo(COMPONENT_NFS_V4, "Can't reclaim delegation");
			res_OPEN4->status = NFS4ERR_RECLAIM_BAD;
			goto out;
		}
		openflags |= FSAL_O_RECLAIM;
		file_obj = data->current_obj;
		break;

	case CLAIM_FH:
		file_obj = data->current_obj;
		break;

	case CLAIM_DELEGATE_PREV:
		/* FIXME: Remove this when we have full support
		 * for CLAIM_DELEGATE_PREV and delegpurge operations
		 */
		res_OPEN4->status = NFS4ERR_NOTSUPP;
		goto out;

	case CLAIM_DELEGATE_CUR:
	case CLAIM_DELEG_CUR_FH:
		res_OPEN4->status = open4_claim_deleg(arg, data);
		if (res_OPEN4->status != NFS4_OK)
			goto out;
		openflags |= FSAL_O_RECLAIM;
		file_obj = data->current_obj;
		break;

	default:
		LogFatal(COMPONENT_STATE,
			 "Programming error.  Invalid claim after check.");
	}

	if ((arg->share_access & OPEN4_SHARE_ACCESS_READ) != 0)
		openflags |= FSAL_O_READ;

	if ((arg->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0)
		openflags |= FSAL_O_WRITE;

	if ((arg->share_deny & OPEN4_SHARE_DENY_READ) != 0)
		openflags |= FSAL_O_DENY_READ;

	if ((arg->share_deny & OPEN4_SHARE_DENY_WRITE) != 0)
		openflags |= FSAL_O_DENY_WRITE_MAND;

	/* Check if file_obj a REGULAR_FILE */
	if (file_obj != NULL && file_obj->type != REGULAR_FILE) {
		LogDebug(COMPONENT_NFS_V4,
			 "Wrong file type expected REGULAR_FILE actual %s",
			 object_file_type_to_str(file_obj->type));

		if (file_obj->type == DIRECTORY) {
			res_OPEN4->status = NFS4ERR_ISDIR;
		} else {
			/* All special nodes must return NFS4ERR_SYMLINK for
			 * proper client behavior per this linux-nfs post:
			 * http://marc.info/?l=linux-nfs&m=131342421825436&w=2
			 */
			res_OPEN4->status = NFS4ERR_SYMLINK;
		}

		goto out;
	}

	if (file_obj != NULL) {
		/* Go ahead and take the state lock now. */
		STATELOCK_lock(file_obj);
		st_lock_held = true;

		in_obj = file_obj;

		/* Check if any existing delegations conflict with this open.
		 * Delegation recalls will be scheduled if there is a conflict.
		 */
		if (arg->claim.claim != CLAIM_DELEGATE_CUR &&
		    arg->claim.claim != CLAIM_DELEG_CUR_FH &&
		    state_deleg_conflict_impl(
			    file_obj, (arg->share_access &
				       OPEN4_SHARE_ACCESS_WRITE) != 0)) {
			res_OPEN4->status = NFS4ERR_DELAY;
			goto out;
		}

		/* Check if there is already a state for this entry and owner.
		 */
		*file_state = nfs4_State_Get_Obj(file_obj, owner);

		if (isFullDebug(COMPONENT_STATE) && *file_state != NULL) {
			char str[LOG_BUFF_LEN] = "\0";
			struct display_buffer dspbuf = { sizeof(str), str,
							 str };

			display_stateid(&dspbuf, *file_state);

			LogFullDebug(COMPONENT_STATE, "Found existing state %s",
				     str);
		}

		/* Check if open from another export */
		if (*file_state != NULL &&
		    !state_same_export(*file_state, op_ctx->ctx_export)) {
			LogEvent(
				COMPONENT_STATE,
				"Lock Owner Export Conflict, Lock held for export %" PRIu16
				" request for export %" PRIu16,
				state_export_id(*file_state),
				op_ctx->ctx_export->export_id);
			res_OPEN4->status = NFS4ERR_INVAL;
			goto out;
		}
	}

	/* If that did not succeed, allocate a state from the FSAL. */
	if (*file_state == NULL) {
		*file_state = op_ctx->fsal_export->exp_ops.alloc_state(
			op_ctx->fsal_export, STATE_TYPE_SHARE, NULL);

		/* Remember we allocated a new state */
		*new_state = true;

		/* We are ready to perform the open (with possible create).
		 * in_obj has been set to the file itself or the parent.
		 * filename is NULL if in_obj is the file itself.
		 *
		 * Permission check has been done on directory if appropriate,
		 * otherwise fsal_open2 will do a directory permission
		 * check.
		 *
		 * fsal_open2 handles the permission check on the file
		 * itself and also handles all the share reservation stuff.
		 *
		 * fsal_open2 returns with a ref on out_obj, which should be
		 * passed to the state.
		 */
		LogFullDebug(COMPONENT_STATE, "Calling open2 for %s", filename);
retry_open_file:
		status = fsal_open2(in_obj, *file_state, openflags, createmode,
				    filename, &sattr, verifier, &out_obj, NULL,
				    parent_pre_attrs, parent_post_attrs);

		if (FSAL_IS_ERROR(status)) {
			res_OPEN4->status = nfs4_Errno_status(status);
			LogFullDebug(COMPONENT_STATE,
				     "fsal_open2 for %s, returned %d-%s",
				     filename, res_OPEN4->status,
				     nfsstat4_to_str(res_OPEN4->status));

			/* We have an got an share denied with an existing
			* client. Let's recheck if it was expired.
			*/
			if (res_OPEN4->status == NFS4ERR_SHARE_DENIED) {
				if (st_lock_held)
					STATELOCK_unlock(in_obj);
				if (check_and_remove_conflicting_client(
					    in_obj->state_hdl)) {
					if (st_lock_held)
						STATELOCK_lock(in_obj);
					goto retry_open_file;
				}
				if (st_lock_held)
					STATELOCK_lock(in_obj);
			}
			goto out;
		}
	} else if (createmode >= FSAL_EXCLUSIVE) {
		/* We have an EXCLUSIVE create with an existing
		 * state. We still need to verify it, but no need
		 * to call reopen2.
		 */
		LogFullDebug(COMPONENT_STATE, "Calling verify2 ");

		status = fsal_verify2(file_obj, verifier);

		if (FSAL_IS_ERROR(status)) {
			res_OPEN4->status = nfs4_Errno_status(status);
			goto out;
		}

		/* We need an extra reference below. */
		file_obj->obj_ops->get_ref(file_obj);
	} else {
		/* Check if this is a same-client operation with existing
		 * delegation that would trigger Ceph delegation recall.
		 * If so, skip fsal_reopen2 to prevent the recall, but
		 * still allow delegation granting later.
		 */
		bool has_delegations =
			(file_obj->state_hdl != NULL &&
			 file_obj->state_hdl->file.fdeleg_stats
					 .fds_curr_delegations > 0);
		bool conflict_detected = state_deleg_conflict_impl(
			file_obj,
			(arg->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0);

		LogFullDebug(COMPONENT_STATE,
			     "has_delegations=%d, conflict_detected=%d",
			     has_delegations, conflict_detected);

		/* Only skip fsal_reopen2 if there's an active delegation and
		 * no conflict (same client). Otherwise, call fsal_reopen2 to
		 * get proper capabilities.
		 */
		if (has_delegations && !conflict_detected) {
			LogFullDebug(COMPONENT_STATE,
				     "Skipping fsal_reopen2 to prevent recall");
			status = fsalstat(ERR_FSAL_NO_ERROR, 0);
		} else {
			LogFullDebug(COMPONENT_STATE,
				     "open4_ex: calling fsal_reopen2 normally");
			status = fsal_reopen2(file_obj, *file_state, openflags,
					      true);
		}

		if (FSAL_IS_ERROR(status)) {
			res_OPEN4->status = nfs4_Errno_status(status);
			goto out;
		}

		/* We need an extra reference below. */
		file_obj->obj_ops->get_ref(file_obj);
	}

	if (file_obj == NULL) {
		/* We have a file object, take the state lock. */
		file_obj = out_obj;
		STATELOCK_lock(file_obj);
		st_lock_held = true;
	}

	/* Now the st_lock is held for sure and we have an extra LRU
	 * reference to file_obj, which is the opened file.
	 */

	if (*new_state) {
		/* The state data to be added */
		union state_data candidate_data;
		/* Tracking data for the open state */
		struct state_refer refer, *p_refer = NULL;
		state_status_t state_status;

		candidate_data.share.share_access = arg->share_access &
						    OPEN4_SHARE_ACCESS_BOTH;
		candidate_data.share.share_deny = arg->share_deny;
		candidate_data.share.share_access_prev =
			(1 << candidate_data.share.share_access);
		candidate_data.share.share_deny_prev =
			(1 << candidate_data.share.share_deny);

		LogFullDebug(
			COMPONENT_STATE,
			"Creating new state access=%x deny=%x access_prev=%x deny_prev=%x",
			candidate_data.share.share_access,
			candidate_data.share.share_deny,
			candidate_data.share.share_access_prev,
			candidate_data.share.share_deny_prev);

		/* Record the sequence info */
		if (data->minorversion > 0) {
			memcpy(refer.session, data->session->session_id,
			       sizeof(sessionid4));
			refer.sequence = data->sequence;
			refer.slot = data->slotid;
			p_refer = &refer;
		}

		/* We need to register this state now. */
		state_status = state_add_impl(file_obj, STATE_TYPE_SHARE,
					      &candidate_data, owner,
					      file_state, p_refer);

		if (state_status != STATE_SUCCESS) {
			/* state_add_impl failure closed and freed state.
			 * file_state will also be NULL at this point. Also
			 * release the ref on file_obj, since the state add
			 * failed.
			 */
			file_obj->obj_ops->put_ref(file_obj);
			res_OPEN4->status = nfs4_Errno_state(state_status);
			*new_state = false;
			goto out;
		}

		glist_init(&(*file_state)->state_data.share.share_lockstates);
	}

	res_OPEN4->status = open4_create_fh(data, file_obj);

	if (res_OPEN4->status != NFS4_OK) {
		if (*new_state) {
			/* state_del_locked will close the file. */
			state_del_locked(*file_state);
			*file_state = NULL;
			*new_state = false;
		} else {
			/*Do an open downgrade to the old open flags */
			status = fsal_reopen2(file_obj, *file_state,
					      old_openflags, false);
			if (FSAL_IS_ERROR(status)) {
				LogCrit(COMPONENT_NFS_V4,
					"Failed to allocate handle, reopen2 failed with %s",
					fsal_err_txt(status));
			}

			/* Need to release the st_lock before the put_ref
			 * call.
			 */
			STATELOCK_unlock(file_obj);
			st_lock_held = false;

			/* Release the extra LRU reference on file_obj. */
			file_obj->obj_ops->put_ref(file_obj);
			goto out;
		}
	}

	/* Since open4_create_fh succeeded the LRU reference to file_obj was
	 * consumed by data->current_obj.
	 */

	if (!(*new_state)) {
		LogFullDebug(
			COMPONENT_STATE,
			"Open upgrade old access=%x deny=%x access_prev=%x deny_prev=%x",
			(*file_state)->state_data.share.share_access,
			(*file_state)->state_data.share.share_deny,
			(*file_state)->state_data.share.share_access_prev,
			(*file_state)->state_data.share.share_deny_prev);

		LogFullDebug(COMPONENT_STATE,
			     "Open upgrade to access=%x deny=%x",
			     arg->share_access, arg->share_deny);

		/* Update share_access and share_deny */
		(*file_state)->state_data.share.share_access |=
			arg->share_access & OPEN4_SHARE_ACCESS_BOTH;

		(*file_state)->state_data.share.share_deny |= arg->share_deny;

		/* Update share_access_prev and share_deny_prev */
		(*file_state)->state_data.share.share_access_prev |=
			(1 << (arg->share_access & OPEN4_SHARE_ACCESS_BOTH));

		(*file_state)->state_data.share.share_deny_prev |=
			(1 << arg->share_deny);

		LogFullDebug(
			COMPONENT_STATE,
			"Open upgrade new access=%x deny=%x access_prev=%x deny_prev=%x",
			(*file_state)->state_data.share.share_access,
			(*file_state)->state_data.share.share_deny,
			(*file_state)->state_data.share.share_access_prev,
			(*file_state)->state_data.share.share_deny_prev);
	}

	if (!st_lock_held) {
		/* Acquire st_lock before adding deleg state */
		STATELOCK_lock(file_obj);
		st_lock_held = true;
	}

	/* At this point open has succeeded and we are holding the state
	 * lock.
	 */
	if (arg->share_access & OPEN4_SHARE_ACCESS_WRITE)
		file_obj->state_hdl->file.fdeleg_stats.fds_num_write_opens++;

	do_delegation(arg, res_OPEN4, data, owner, *file_state, clientid);
out:

	/* Release the attributes (may release an inherited ACL) */
	fsal_release_attrs(&sattr);

	if (st_lock_held) {
		STATELOCK_unlock(file_obj);
	}

	if (res_OPEN4->status != NFS4_OK) {
		/* Cleanup state on error */
		if (*new_state)
			free_state(*file_state);
		else if (*file_state != NULL)
			dec_state_t_ref(*file_state);
		*file_state = NULL;
	}

	if (looked_up_file_obj) {
		/* We got file_obj via lookup, we need to unref it. */
		file_obj->obj_ops->put_ref(file_obj);
	}
}

/**
 * @brief NFS4_OP_OPEN
 *
 * This function implements the NFS4_OP_OPEN operation, which
 * potentially creates and opens a regular file.
 *
 * @param[in]     op   Arguments for nfs4_op
 * @param[in,out] data Compound request's data
 * @param[out]    resp Results for nfs4_op
 *
 * @return per RFC5661, pp. 369-70
 */

enum nfs_req_result nfs4_op_open(struct nfs_argop4 *op, compound_data_t *data,
				 struct nfs_resop4 *resp)
{
	/* Shorter alias for OPEN4 arguments */
	OPEN4args *const arg_OPEN4 = &(op->nfs_argop4_u.opopen);
	/* Shorter alias for OPEN4 response */
	OPEN4res *const res_OPEN4 = &(resp->nfs_resop4_u.opopen);
	/* The handle from which the change_info4 is to be
	 * generated.  Every mention of change_info4 in RFC5661
	 * speaks of the parent directory of the file being opened.
	 * However, with CLAIM_FH, CLAIM_DELEG_CUR_FH, and
	 * CLAIM_DELEG_PREV_FH, there is no way to derive the parent
	 * directory from the file handle.  It is Unclear what the
	 * correct behavior is.  In our implementation, we take the
	 * change_info4 of whatever filehandle is current when the
	 * OPEN operation is invoked.
	 */
	struct fsal_obj_handle *obj_change = NULL;
	/* The found client record */
	nfs_client_id_t *clientid = NULL;
	/* The found or created state owner for this open */
	state_owner_t *owner = NULL;
	/* The supplied claim type */
	open_claim_type4 claim = arg_OPEN4->claim.claim;
	/* The open state for the file */
	state_t *file_state = NULL;
	/* True if the state was newly created */
	bool new_state = false;
	/* True if a grace reference was taken */
	bool grace_ref = false;
	int retval;
	struct fsal_attrlist parent_pre_attrs, parent_post_attrs;
	bool is_parent_pre_attrs_valid, is_parent_post_attrs_valid;

	LogDebug(COMPONENT_STATE,
		 "Entering NFS v4 OPEN handler -----------------------------");

	/* What kind of open is it ? */
	LogFullDebug(
		COMPONENT_STATE,
		"OPEN: Claim type = %d, Open Type = %d, Share Deny = %d, Share Access = %d ",
		arg_OPEN4->claim.claim, arg_OPEN4->openhow.opentype,
		arg_OPEN4->share_deny, arg_OPEN4->share_access);

	LogFullDebug(COMPONENT_STATE, "Delegate Type = %d",
		     arg_OPEN4->claim.open_claim4_u.delegate_type);

	GSH_AUTO_TRACEPOINT(
		nfs4, op_open_start, TRACE_INFO,
		"OPEN arg: claim={} opentype={} howmode={} share_deny={} share_access={} seqid={}",
		arg_OPEN4->claim.claim, arg_OPEN4->openhow.opentype,
		arg_OPEN4->openhow.openflag4_u.how.mode, arg_OPEN4->share_deny,
		arg_OPEN4->share_access, arg_OPEN4->seqid);

	resp->resop = NFS4_OP_OPEN;
	res_OPEN4->status = NFS4_OK;
	res_OPEN4->OPEN4res_u.resok4.rflags = 0;

	fsal_prepare_attrs(&parent_pre_attrs, ATTR_CHANGE);
	fsal_prepare_attrs(&parent_post_attrs, ATTR_CHANGE);

	/* Check export permissions if OPEN4_CREATE */
	if ((arg_OPEN4->openhow.opentype == OPEN4_CREATE) &&
	    ((op_ctx->export_perms.options & EXPORT_OPTION_MD_WRITE_ACCESS) ==
	     0)) {
		res_OPEN4->status = NFS4ERR_ROFS;

		LogDebug(COMPONENT_NFS_V4,
			 "Status of OP_OPEN due to export permissions = %s",
			 nfsstat4_to_str(res_OPEN4->status));
		return NFS_REQ_ERROR;
	}

	/* Check export permissions if OPEN4_SHARE_ACCESS_WRITE */
	if (((arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0) &&
	    ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) ==
	     0)) {
		res_OPEN4->status = NFS4ERR_ROFS;

		LogDebug(COMPONENT_NFS_V4,
			 "Status of OP_OPEN due to export permissions = %s",
			 nfsstat4_to_str(res_OPEN4->status));

		return NFS_REQ_ERROR;
	}

	/* Do basic checks on a filehandle */
	res_OPEN4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false);

	if (res_OPEN4->status != NFS4_OK)
		return NFS_REQ_ERROR;

	if (data->current_obj == NULL) {
		/* This should be impossible, as PUTFH fills in the
		 * current entry and previous checks weed out handles
		 * in the PseudoFS and DS handles.
		 */
		res_OPEN4->status = NFS4ERR_SERVERFAULT;
		LogCrit(COMPONENT_NFS_V4,
			"Impossible condition in compound data at %s:%u.",
			__FILE__, __LINE__);
		return NFS_REQ_ERROR;
	}

	/* It this a known client id? */
	LogDebug(COMPONENT_STATE, "OPEN Client id = %" PRIx64,
		 arg_OPEN4->owner.clientid);

	retval = nfs_client_id_get_confirmed(data->minorversion == 0
						     ? arg_OPEN4->owner.clientid
						     : data->session->clientid,
					     &clientid);

	if (retval != CLIENT_ID_SUCCESS) {
		res_OPEN4->status = clientid_error_to_nfsstat(retval);
		LogDebug(COMPONENT_NFS_V4,
			 "nfs_client_id_get_confirmed failed");
		return NFS_REQ_ERROR;
	}

	/* Check if lease is expired and reserve it */
	if (data->minorversion == 0 &&
	    !reserve_lease_or_expire(clientid, false, NULL)) {
		res_OPEN4->status = NFS4ERR_EXPIRED;
		LogDebug(COMPONENT_NFS_V4, "Lease expired");
		goto out3;
	}

	/* Get the open owner */
	if (!open4_open_owner(op, data, resp, clientid, &owner)) {
		LogDebug(COMPONENT_NFS_V4, "open4_open_owner failed");
		goto out2;
	}

	/* Do the claim check here, so we can save the result in the
	 * owner for NFSv4.0.
	 */
	res_OPEN4->status =
		open4_validate_claim(data, claim, clientid, &grace_ref);
	if (res_OPEN4->status != NFS4_OK) {
		LogDebug(COMPONENT_NFS_V4, "open4_validate_claim failed");
		goto out;
	}

	/* After this point we know we have only CLAIM_NULL,
	 * CLAIM_FH, or CLAIM_PREVIOUS, and that our grace period and
	 * minor version are appropriate for the claim specified.
	 */
	if ((arg_OPEN4->openhow.opentype == OPEN4_CREATE) &&
	    (claim != CLAIM_NULL)) {
		res_OPEN4->status = NFS4ERR_INVAL;
		LogDebug(COMPONENT_NFS_V4, "OPEN4_CREATE but not CLAIM_NULL");
		goto out2;
	}

	/* So we still have a reference even after we replace the
	 * current FH.
	 */
	obj_change = data->current_obj;
	obj_change->obj_ops->get_ref(obj_change);

	/* Update the change info for entry_change. */
	res_OPEN4->OPEN4res_u.resok4.cinfo.before =
		fsal_get_changeid4(obj_change);

	/* Build valid share_access mask based on protocol version.
	 *
	 * Note: We accept SIGNAL_DELEG_WHEN_RESRC_AVAIL (0x10000) and
	 * PUSH_DELEG_WHEN_UNCONTENDED (0x20000) flags for NFSv4.1+ even though
	 * they are not fully implemented. Per RFC 5661 Section 18.16.3, these
	 * are optional hints that servers MAY ignore. We accept them to
	 * maintain compatibility with clients that send them, and silently
	 * ignore them during delegation processing.
	 *
	 * We do NOT advertise support for these flags in FATTR4_OPEN_ARGUMENTS
	 * (see encode_open_arguments in nfs_proto_tools.c) to avoid misleading
	 * clients into thinking we will honor them.
	 */
	uint32_t valid_share_access_mask = OPEN4_SHARE_ACCESS_BOTH |
					   OPEN4_SHARE_ACCESS_WANT_DELEG_MASK;

	/* Add NFSv4.1+ signal flags */
	if (data->minorversion >= NFS4_MINOR_VERS_1) {
		valid_share_access_mask |=
			OPEN4_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL |
			OPEN4_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED;
	}

	/* Add NFSv4.2 delegation timestamp flag */
	if (data->minorversion >= NFS4_MINOR_VERS_2) {
		valid_share_access_mask |=
			OPEN4_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS;
	}

	/* Check if share_access does not have any access set, or has
	 * invalid bits that are set.  check that share_deny doesn't
	 * have any invalid bits set.
	 */
	if (!(arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_BOTH) ||
	    (data->minorversion == 0 &&
	     arg_OPEN4->share_access & ~OPEN4_SHARE_ACCESS_BOTH) ||
	    (arg_OPEN4->share_access & ~valid_share_access_mask) ||
	    (arg_OPEN4->share_deny & ~OPEN4_SHARE_DENY_BOTH)) {
		res_OPEN4->status = NFS4ERR_INVAL;
		LogDebug(
			COMPONENT_NFS_V4,
			"Invalid SHARE_ACCESS or SHARE_DENY (share_access=0x%x, valid_mask=0x%x)",
			arg_OPEN4->share_access, valid_share_access_mask);
		goto out;
	}

	/* Prepare the attrset attribute */
	memset(&res_OPEN4->OPEN4res_u.resok4.attrset, 0,
	       sizeof(struct bitmap4));

	/* Utilize the extended FSAL API functionality to perform the open.
	 * The usage of atime/mtime for exclusive create verifier will be
	 * handled in open4_ex, so pass in attrset to be handled there.
	 */
	open4_ex(arg_OPEN4, data, res_OPEN4, clientid, owner, &file_state,
		 &new_state, &res_OPEN4->OPEN4res_u.resok4.attrset,
		 &parent_pre_attrs, &parent_post_attrs);

	if (res_OPEN4->status != NFS4_OK)
		goto out;

	/* If server use OPEN_CONFIRM4, set the correct flag,
	 * but not for 4.1 */
	if (owner->so_owner.so_nfs4_owner.so_confirmed == false)
		res_OPEN4->OPEN4res_u.resok4.rflags |= OPEN4_RESULT_CONFIRM;

	res_OPEN4->OPEN4res_u.resok4.rflags |= OPEN4_RESULT_LOCKTYPE_POSIX;
	if (nfs_param.nfsv4_param.preserve_unlinked &&
	    op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
						     fso_preserve_unlinked))
		res_OPEN4->OPEN4res_u.resok4.rflags |=
			OPEN4_RESULT_PRESERVE_UNLINKED;

	if (op_ctx->fsal_export->exp_ops.fs_supports(
		    op_ctx->fsal_export, fso_lock_support_async_block)) {
		res_OPEN4->OPEN4res_u.resok4.rflags |=
			OPEN4_RESULT_MAY_NOTIFY_LOCK;
	}

	LogFullDebug(COMPONENT_STATE, "NFS4 OPEN returning NFS4_OK");

	/* regular exit */
	res_OPEN4->status = NFS4_OK;

	/* Update change_info4 */
	is_parent_pre_attrs_valid =
		FSAL_TEST_MASK(parent_pre_attrs.valid_mask, ATTR_CHANGE);
	if (is_parent_pre_attrs_valid) {
		res_OPEN4->OPEN4res_u.resok4.cinfo.before =
			(changeid4)parent_pre_attrs.change;
	}

	is_parent_post_attrs_valid =
		FSAL_TEST_MASK(parent_post_attrs.valid_mask, ATTR_CHANGE);
	if (is_parent_post_attrs_valid) {
		res_OPEN4->OPEN4res_u.resok4.cinfo.after =
			(changeid4)parent_post_attrs.change;
	} else {
		res_OPEN4->OPEN4res_u.resok4.cinfo.after =
			fsal_get_changeid4(obj_change);
	}

	res_OPEN4->OPEN4res_u.resok4.cinfo.atomic =
		is_parent_pre_attrs_valid && is_parent_post_attrs_valid ? TRUE
									: FALSE;

	/* Handle open stateid/seqid for success */
	update_stateid(file_state, &res_OPEN4->OPEN4res_u.resok4.stateid, data,
		       open_tag);

out:
	fsal_release_attrs(&parent_pre_attrs);
	fsal_release_attrs(&parent_post_attrs);

	if (res_OPEN4->status != NFS4_OK) {
		LogDebug(COMPONENT_STATE, "failed with status %s",
			 nfsstat4_to_str(res_OPEN4->status));
	}

	/* Save the response in the open owner.
	 * obj_change is either the parent directory or for a CLAIM_PREV is
	 * the entry itself. In either case, it's the right entry to use in
	 * saving the request results.
	 */
	if (data->minorversion == 0) {
		Copy_nfs4_state_req(owner, arg_OPEN4->seqid, op, obj_change,
				    resp, open_tag);
	}

out2:
	if (grace_ref)
		nfs_put_grace_status();

	/* Update the lease before exit */
	if (data->minorversion == 0)
		update_lease_simple(clientid);

	if (file_state != NULL)
		dec_state_t_ref(file_state);

	/* Clean up if we have an error exit */
	if ((file_state != NULL) && new_state &&
	    (res_OPEN4->status != NFS4_OK)) {
		/* Need to destroy open owner and state */
		state_del(file_state);
	}

	if (obj_change)
		obj_change->obj_ops->put_ref(obj_change);

	if (owner != NULL) {
		/* Need to release the open owner for this call */
		dec_state_owner_ref(owner);
	}

out3:

	dec_client_id_ref(clientid);

	const OPEN4resok *const resok = &res_OPEN4->OPEN4res_u.resok4;

	LogFullDebug(
		COMPONENT_STATE,
		"END OF nfs4_op_open: res: status %s, stateid= %d, rflags %d, delegation_type %d ",
		nfsstat4_to_str(res_OPEN4->status), resok->stateid.seqid,
		resok->rflags, resok->delegation.delegation_type);

	GSH_AUTO_TRACEPOINT(nfs4, op_open_end, TRACE_INFO,
			    "OPEN res: status={} stateid={} " TP_CINFO_FORMAT
			    " rflags={} delegation={}",
			    res_OPEN4->status, resok->stateid.seqid,
			    TP_CINFO_ARGS_EXPAND(resok->cinfo), resok->rflags,
			    resok->delegation.delegation_type);

	return nfsstat4_to_nfs_req_result(res_OPEN4->status);
} /* nfs4_op_open */

/**
 * @brief Free memory allocated for OPEN result
 *
 * This function frees any memory allocated for the result of the
 * NFS4_OP_OPEN function.
 *
 * @param[in,out] resp nfs4_op results
 */
void nfs4_op_open_Free(nfs_resop4 *resp)
{
	/* Nothing to be done */
}
