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

/**
 * @defgroup SAL State abstraction layer
 * @{
 */

/**
 * @file nfs4_state_id.c
 * @brief NFSv4 state ids
 */

#include "config.h"
#include <stdio.h>
#include <sys/types.h>
#include <ctype.h> /* for having isalnum */
#include <stdlib.h> /* for having atoi */
#include <dirent.h> /* for having MAXNAMLEN */
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/file.h> /* for having FNDELAY */
#include <pwd.h>
#include <grp.h>
#include <pthread.h>
#include "log.h"
#include "gsh_rpc.h"
#include "hashtable.h"
#include "nfs_core.h"
#include "nfs4.h"
#include "fsal.h"
#include "nfs_exports.h"
#include "nfs_file_handle.h"
#include "sal_functions.h"
#include "nfs_proto_tools.h"
#include "city.h"

/**
 * @brief Hash table for stateids.
 */
hash_table_t *ht_state_id;
hash_table_t *ht_state_obj;

/**
 * @brief All-zeroes stateid4.other
 */
char all_zero[OTHERSIZE];

/**
 * @brief All-zeroes stateid4.other
 */
char all_ones[OTHERSIZE];
#define seqid_all_one 0xFFFFFFFF

/**
 * @brief Display a stateid other
 *
 * @param[in/out] dspbuf display_buffer describing output string
 * @param[in]     other  The other component of the stateid
 *
 * @return the bytes remaining in the buffer.
 */
int display_stateid_other(struct display_buffer *dspbuf, char *other)
{
	uint64_t clientid = *((uint64_t *)other);
	uint32_t count = *((uint32_t *)(other + sizeof(uint64_t)));
	int b_left = display_cat(dspbuf, "OTHER=");

	if (b_left <= 0)
		return b_left;

	b_left = display_opaque_bytes(dspbuf, other, OTHERSIZE);

	if (b_left <= 0)
		return b_left;

	b_left = display_cat(dspbuf, " {{CLIENTID ");

	if (b_left <= 0)
		return b_left;

	b_left = display_clientid(dspbuf, clientid);

	if (b_left <= 0)
		return b_left;

	return display_printf(dspbuf, "} StateIdCounter=0x%08" PRIx32 "}",
			      count);
}

/**
 * @brief Display a stateid other in the hash table
 *
 * @param[in]  dspbuf display buffer to display into
 * @param[in]  buff The key
 */
int display_state_id_key(struct display_buffer *dspbuf,
			 struct gsh_buffdesc *buff)
{
	return display_stateid_other(dspbuf, buff->addr);
}

/**
 * @brief Display a stateid4 from the wire
 *
 * @param[in/out] dspbuf display_buffer describing output string
 * @param[in]     state  The stateid
 *
 * @return the bytes remaining in the buffer.
 */
int display_stateid4(struct display_buffer *dspbuf, stateid4 *stateid)
{
	int b_left = display_stateid_other(dspbuf, stateid->other);

	if (b_left <= 0)
		return b_left;

	return display_printf(dspbuf, " seqid=%" PRIu32, stateid->seqid);
}

const char *str_state_type(state_t *state)
{
	switch (state->state_type) {
	case STATE_TYPE_NONE:
		return "NONE";
	case STATE_TYPE_SHARE:
		return "SHARE";
	case STATE_TYPE_DELEG:
		return "DELEGATION";
	case STATE_TYPE_LOCK:
		return "LOCK";
	case STATE_TYPE_LAYOUT:
		return "LAYOUT";
	case STATE_TYPE_NLM_LOCK:
		return "NLM_LOCK";
	case STATE_TYPE_NLM_SHARE:
		return "NLM_SHARE";
	case STATE_TYPE_9P_FID:
		return "9P_FID";
	default:
		return "UNKNOWN";
	}

	return "UNKNOWN";
}

/**
 * @brief Display a stateid
 *
 * @param[in/out] dspbuf display_buffer describing output string
 * @param[in]     state  The stateid
 *
 * @return the bytes remaining in the buffer.
 */
int display_stateid(struct display_buffer *dspbuf, state_t *state)
{
	int b_left;

	if (state == NULL)
		return display_cat(dspbuf, "STATE <NULL>");

	b_left = display_printf(dspbuf, "STATE %p ", state);

	if (b_left <= 0)
		return b_left;

	b_left = display_stateid_other(dspbuf, state->stateid_other);

	if (b_left <= 0)
		return b_left;

	b_left = display_printf(dspbuf,
				" obj=%p type=%s seqid=%" PRIu32 " owner={",
				state->state_obj, str_state_type(state),
				state->state_seqid);

	if (b_left <= 0)
		return b_left;

	b_left = display_nfs4_owner(dspbuf, state->state_owner);

	if (b_left <= 0)
		return b_left;

	return display_printf(dspbuf, "} state_refcount=%" PRId32,
			      atomic_fetch_int32_t(&state->state_refcount));
}

/**
 * @brief Display a state in the hash table
 *
 * @param[in]  dspbuf display buffer to display into
 * @param[in]  buff The value
 * @return Length of output string.
 */
int display_state_id_val(struct display_buffer *dspbuf,
			 struct gsh_buffdesc *buff)
{
	return display_stateid(dspbuf, buff->addr);
}

/**
 * @brief Compare two stateids
 *
 * @param[in] buff1 One key
 * @param[in] buff2 Another key
 *
 * @retval 0 if equal.
 * @retval 1 if not equal.
 */
int compare_state_id(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2)
{
	if (isFullDebug(COMPONENT_STATE)) {
		char str1[DISPLAY_STATEID_OTHER_SIZE] = "\0";
		char str2[DISPLAY_STATEID_OTHER_SIZE] = "\0";
		struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 };
		struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 };

		display_stateid_other(&dspbuf1, buff1->addr);
		display_stateid_other(&dspbuf2, buff2->addr);

		if (isDebug(COMPONENT_HASHTABLE))
			LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1,
				     str2);
	}

	return memcmp(buff1->addr, buff2->addr, OTHERSIZE);
} /* compare_state_id */

/**
 * @brief Hash a stateid
 *
 * @param[in] stateid Array aliased to stateid
 */
static inline uint32_t compute_stateid_hash_value(uint32_t *stateid)
{
	return stateid[1] ^ stateid[2];
}

/**
 * @brief Hash index for a stateid
 *
 * @param[in] hparam Hash parameter
 * @param[in] key    Key to hash
 *
 * @return The hash index.
 */
uint32_t state_id_value_hash_func(hash_parameter_t *hparam,
				  struct gsh_buffdesc *key)
{
	uint32_t val =
		compute_stateid_hash_value(key->addr) % hparam->index_size;

	if (isDebug(COMPONENT_HASHTABLE))
		LogFullDebug(COMPONENT_STATE, "val = %" PRIu32, val);

	return val;
}

/**
 * @brief RBT hash for a stateid
 *
 * @param[in] hparam Hash parameter
 * @param[in] key    Key to hash
 *
 * @return The RBT hash.
 */
uint64_t state_id_rbt_hash_func(hash_parameter_t *hparam,
				struct gsh_buffdesc *key)
{
	uint64_t val = compute_stateid_hash_value(key->addr);

	if (isDebug(COMPONENT_HASHTABLE))
		LogFullDebug(COMPONENT_STATE, "rbt = %" PRIu64, val);

	return val;
}

static hash_parameter_t state_id_param = {
	.index_size = PRIME_STATE,
	.hash_func_key = state_id_value_hash_func,
	.hash_func_rbt = state_id_rbt_hash_func,
	.compare_key = compare_state_id,
	.display_key = display_state_id_key,
	.display_val = display_state_id_val,
	.flags = HT_FLAG_CACHE,
	.ht_log_component = COMPONENT_STATE,
	.ht_name = "State ID Table"
};

/**
 * @brief Compare two stateids by entry/owner
 *
 * @param[in] buff1 One key
 * @param[in] buff2 Another key
 *
 * @retval 0 if equal.
 * @retval 1 if not equal.
 */
int compare_state_obj(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2)
{
	state_t *state1 = buff1->addr;
	state_t *state2 = buff2->addr;

	if (state1 == NULL || state2 == NULL)
		return 1;

	if (state1->state_obj != state2->state_obj)
		return 1;

	return compare_nfs4_owner(state1->state_owner, state2->state_owner);
}

/**
 * @brief Hash index for a stateid by entry/owner
 *
 * @param[in] hparam Hash parameter
 * @param[in] key    Key to hash
 *
 * @return The hash index.
 */
uint32_t state_obj_value_hash_func(hash_parameter_t *hparam,
				   struct gsh_buffdesc *key)
{
	unsigned int sum = 0;
	unsigned int i = 0;
	unsigned char c = 0;
	uint32_t res = 0;

	struct gsh_buffdesc fh_desc;
	state_t *pkey = key->addr;

	pkey->state_obj->obj_ops->handle_to_key(pkey->state_obj, &fh_desc);

	/* Compute the sum of all the characters */
	for (i = 0; i < pkey->state_owner->so_owner_len; i++) {
		c = ((char *)pkey->state_owner->so_owner_val)[i];
		sum += c;
	}

	res = ((uint32_t)pkey->state_owner->so_owner.so_nfs4_owner.so_clientid +
	       (uint32_t)sum + pkey->state_owner->so_owner_len +
	       (uint32_t)pkey->state_owner->so_type +
	       (uint64_t)CityHash64WithSeed(fh_desc.addr, fh_desc.len, 557)) %
	      (uint32_t)hparam->index_size;

	if (isDebug(COMPONENT_HASHTABLE))
		LogFullDebug(COMPONENT_STATE, "value = %" PRIu32, res);

	return res;
}

/**
 * @brief RBT hash for a stateid by entry/owner
 *
 * @param[in] hparam Hash parameter
 * @param[in] key    Key to hash
 *
 * @return The RBT hash.
 */
uint64_t state_obj_rbt_hash_func(hash_parameter_t *hparam,
				 struct gsh_buffdesc *key)
{
	state_t *pkey = key->addr;
	struct gsh_buffdesc fh_desc;

	unsigned int sum = 0;
	unsigned int i = 0;
	unsigned char c = 0;
	uint64_t res = 0;

	pkey->state_obj->obj_ops->handle_to_key(pkey->state_obj, &fh_desc);

	/* Compute the sum of all the characters */
	for (i = 0; i < pkey->state_owner->so_owner_len; i++) {
		c = ((char *)pkey->state_owner->so_owner_val)[i];
		sum += c;
	}

	res = (uint64_t)pkey->state_owner->so_owner.so_nfs4_owner.so_clientid +
	      (uint64_t)sum + pkey->state_owner->so_owner_len +
	      (uint64_t)pkey->state_owner->so_type +
	      (uint64_t)CityHash64WithSeed(fh_desc.addr, fh_desc.len, 557);

	if (isDebug(COMPONENT_HASHTABLE))
		LogFullDebug(COMPONENT_STATE, "rbt = %" PRIu64, res);

	return res;
}

static hash_parameter_t state_obj_param = {
	.index_size = PRIME_STATE,
	.hash_func_key = state_obj_value_hash_func,
	.hash_func_rbt = state_obj_rbt_hash_func,
	.compare_key = compare_state_obj,
	.display_key = display_state_id_val,
	.display_val = display_state_id_val,
	.flags = HT_FLAG_CACHE,
	.ht_log_component = COMPONENT_STATE,
	.ht_name = "State Obj Table"
};

/**
 * @brief Init the hashtable for stateids
 *
 * @retval 0 if successful.
 * @retval -1 on failure.
 */
int nfs4_Init_state_id(void)
{
	/* Init  all_one */
	memset(all_zero, 0, OTHERSIZE);
	memset(all_ones, 0xFF, OTHERSIZE);

	ht_state_id = hashtable_init(&state_id_param);

	if (ht_state_id == NULL) {
		LogCrit(COMPONENT_STATE, "Cannot init State Id cache");
		return -1;
	}

	ht_state_obj = hashtable_init(&state_obj_param);

	if (ht_state_obj == NULL) {
		LogCrit(COMPONENT_STATE, "Cannot init State Entry cache");
		return -1;
	}

	return 0;
}

/**
 * @brief Build the 12 byte "other" portion of a stateid
 *
 * It is built from the ServerEpoch and a 64 bit global counter.
 *
 * @param[in] other stateid.other object (a char[OTHERSIZE] string)
 */
void nfs4_BuildStateId_Other(nfs_client_id_t *clientid, char *other)
{
	uint32_t my_stateid =
		atomic_inc_uint32_t(&clientid->cid_stateid_counter);

	/* The first part of the other is the 64 bit clientid, which
	 * consists of the epoch in the high order 32 bits followed by
	 * the clientid counter in the low order 32 bits.
	 */
	memcpy(other, &clientid->cid_clientid, sizeof(clientid->cid_clientid));

	memcpy(other + sizeof(clientid->cid_clientid), &my_stateid,
	       sizeof(my_stateid));
}

/**
 * @brief Relinquish a reference on a state_t
 *
 * @param[in] state The state_t to release
 */
void dec_nfs4_state_ref(struct state_t *state)
{
	char str[LOG_BUFF_LEN] = "\0";
	struct display_buffer dspbuf = { sizeof(str), str, str };
	bool str_valid = false;
	int32_t refcount;

	if (isFullDebug(COMPONENT_STATE)) {
		display_stateid(&dspbuf, state);
		str_valid = true;
	}

	refcount = atomic_dec_int32_t(&state->state_refcount);

	assert(refcount >= 0);

	if (refcount > 0) {
		if (str_valid)
			LogFullDebug(COMPONENT_STATE,
				     "Decrement state_refcount now=%d {%s}",
				     refcount, str);

		return;
	}

	PTHREAD_MUTEX_destroy(&state->state_mutex);

	free_state(state);

	if (str_valid)
		LogFullDebug(COMPONENT_STATE, "Deleted %s", str);
}

/**
 * @brief Set a state into the stateid hashtable.
 *
 * @param[in] other stateid4.other
 * @param[in] state The state to add
 *
 * @retval STATE_SUCCESS if able to insert the new state.
 * @retval STATE_ENTRY_EXISTS if state is already there.
 */
state_status_t nfs4_State_Set(state_t *state)
{
	struct gsh_buffdesc buffkey;
	struct gsh_buffdesc buffval;
	hash_error_t err;

	buffkey.addr = state->stateid_other;
	buffkey.len = OTHERSIZE;

	buffval.addr = state;
	buffval.len = sizeof(state_t);

	err = hashtable_test_and_set(ht_state_id, &buffkey, &buffval,
				     HASHTABLE_SET_HOW_SET_NO_OVERWRITE);

	switch (err) {
	case HASHTABLE_SUCCESS:
		break;
	default:
		LogCrit(COMPONENT_STATE,
			"ht_state_id hashtable_test_and_set failed %s for key %p",
			hash_table_err_to_str(err), buffkey.addr);
		return STATE_ENTRY_EXISTS; /* likely reason */
	}

	/* If stateid is a LOCK or SHARE state, we also index by entry/owner */
	if (state->state_type != STATE_TYPE_LOCK &&
	    state->state_type != STATE_TYPE_SHARE)
		return STATE_SUCCESS;

	buffkey.addr = state;
	buffkey.len = sizeof(state_t);

	buffval.addr = state;
	buffval.len = sizeof(state_t);

	err = hashtable_test_and_set(ht_state_obj, &buffkey, &buffval,
				     HASHTABLE_SET_HOW_SET_NO_OVERWRITE);

	switch (err) {
	case HASHTABLE_SUCCESS:
		return STATE_SUCCESS;

	case HASHTABLE_ERROR_KEY_ALREADY_EXISTS: /* buggy client? */
	default: /* error case */
		LogCrit(COMPONENT_STATE,
			"ht_state_obj hashtable_test_and_set failed %s for key %p",
			hash_table_err_to_str(err), buffkey.addr);

		if (isFullDebug(COMPONENT_STATE)) {
			char str[LOG_BUFF_LEN] = "\0";
			struct display_buffer dspbuf = { sizeof(str), str,
							 str };
			state_t *state2;

			display_stateid(&dspbuf, state);
			LogCrit(COMPONENT_STATE, "State %s", str);
			state2 = nfs4_State_Get_Obj(state->state_obj,
						    state->state_owner);
			if (state2 != NULL) {
				display_reset_buffer(&dspbuf);
				display_stateid(&dspbuf, state2);

				LogCrit(COMPONENT_STATE, "Duplicate State %s",
					str);
			}
		}

		buffkey.addr = state->stateid_other;
		buffkey.len = OTHERSIZE;
		err = HashTable_Del(ht_state_id, &buffkey, NULL, NULL);

		if (err != HASHTABLE_SUCCESS) {
			LogCrit(COMPONENT_STATE, "Failure to delete stateid %s",
				hash_table_err_to_str(err));
		}
		return STATE_ENTRY_EXISTS; /* likely reason */
	}
}

/**
 * @brief Get the state from the stateid
 *
 * @param[in]  other      stateid4.other
 *
 * @returns The found state_t or NULL if not found.
 */
struct state_t *nfs4_State_Get_Pointer(char *other)
{
	struct gsh_buffdesc buffkey;
	struct gsh_buffdesc buffval;
	hash_error_t rc;
	struct hash_latch latch;
	struct state_t *state;

	buffkey.addr = other;
	buffkey.len = OTHERSIZE;

	/* Acquire hashtable latch with may_write == false, as we are not
	 * updating hashtable data structures but rather we are atomically
	 * incrementing state_t refcount below. This allows concurrent reads
	 * from this data structure without contending on hashtable writer
	 * lock and since the state_t refcount itself is atomic, we are
	 * thread safe. Furthermore, all writes to the ht_state_id hashtable
	 * are private to this implementation file and are done under may_write
	 * lock, so that calls to decrement the state reference count would
	 * be done under exclusive writer lock.
	 */
	rc = hashtable_getlatch(ht_state_id, &buffkey, &buffval,
				false /* may_write */, &latch);

	if (rc != HASHTABLE_SUCCESS) {
		if (rc == HASHTABLE_ERROR_NO_SUCH_KEY)
			hashtable_releaselatched(ht_state_id, &latch);
		LogDebug(COMPONENT_STATE, "HashTable_Get returned %d", rc);
		return NULL;
	}

	state = buffval.addr;

	/* Take an atomic reference under latch. Hashtable is currently read
	 * locked, so this state can't be invalidated while this atomic
	 * increment is occurring.
	 */
	inc_state_t_ref(state);

	/* Release latch, which releases hashtable read lock */
	hashtable_releaselatched(ht_state_id, &latch);

	return state;
}

/**
 * @brief Get the state from the stateid by entry/owner
 *
 * @param[in]  obj	Object containing state
 * @param[in]  owner	Owner for state
 *
 * @returns The found state_t or NULL if not found.
 */
struct state_t *nfs4_State_Get_Obj(struct fsal_obj_handle *obj,
				   state_owner_t *owner)
{
	state_t state_key;
	struct gsh_buffdesc buffkey;
	struct gsh_buffdesc buffval;
	hash_error_t rc;
	struct hash_latch latch;
	struct state_t *state;

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

	buffkey.addr = &state_key;
	buffkey.len = sizeof(state_key);

	state_key.state_owner = owner;
	state_key.state_obj = obj;

	rc = hashtable_getlatch(ht_state_obj, &buffkey, &buffval, true, &latch);

	if (rc != HASHTABLE_SUCCESS) {
		if (rc == HASHTABLE_ERROR_NO_SUCH_KEY)
			hashtable_releaselatched(ht_state_obj, &latch);
		LogDebug(COMPONENT_STATE, "HashTable_Get returned %d", rc);
		return NULL;
	}

	state = buffval.addr;

	/* Take a reference under latch */
	inc_state_t_ref(state);

	/* Release latch */
	hashtable_releaselatched(ht_state_obj, &latch);

	return state;
}

/**
 * @brief Remove a state from the stateid table
 *
 * @param[in] other stateid4.other
 *
 * @retval true if success
 * @retval false if failure
 */
bool nfs4_State_Del(state_t *state)
{
	struct gsh_buffdesc buffkey, old_key, old_value;
	struct hash_latch latch;
	hash_error_t err;

	buffkey.addr = state->stateid_other;
	buffkey.len = OTHERSIZE;

	err = HashTable_Del(ht_state_id, &buffkey, &old_key, &old_value);

	if (err == HASHTABLE_ERROR_NO_SUCH_KEY) {
		/* Already gone */
		return false;
	} else if (err != HASHTABLE_SUCCESS) {
		char str[LOG_BUFF_LEN] = "\0";
		struct display_buffer dspbuf = { sizeof(str), str, str };

		display_stateid(&dspbuf, state);

		LogDebug(COMPONENT_STATE, "Failure to delete stateid %s %s",
			 str, hash_table_err_to_str(err));
		return false;
	}

	assert(state == old_value.addr);

	/* If stateid is a LOCK or SHARE state, we had also indexed by
	 * entry/owner
	 */
	if (state->state_type != STATE_TYPE_LOCK &&
	    state->state_type != STATE_TYPE_SHARE)
		return true;

	/* Delete the stateid hashed by entry/owner.
	 * Use the old_value from above as the key.
	 */
	buffkey.addr = old_value.addr;
	buffkey.len = old_value.len;

	/* Get latch: we need to check we're deleting the right state */
	err = hashtable_getlatch(ht_state_obj, &buffkey, &old_value, true,
				 &latch);
	if (err != HASHTABLE_SUCCESS) {
		if (err == HASHTABLE_ERROR_NO_SUCH_KEY)
			hashtable_releaselatched(ht_state_obj, &latch);

		LogCrit(COMPONENT_STATE, "hashtable get latch failed: %d", err);
		return false;
	}

	if (old_value.addr != state) {
		/* state obj had already been swapped out */
		hashtable_releaselatched(ht_state_obj, &latch);
		return false;
	}

	hashtable_deletelatched(ht_state_obj, &buffkey, &latch, NULL, NULL);
	hashtable_releaselatched(ht_state_obj, &latch);
	return true;
}

/**
 * @brief Check and look up the supplied stateid
 *
 * This function yields the state for the stateid if it is valid.
 *
 * @param[in]  stateid     Stateid to look up
 * @param[in]  fsal_obj    Associated file (if any)
 * @param[out] state       Found state
 * @param[in]  data        Compound data
 * @param[in]  flags       Flags governing special stateids
 * @param[in]  owner_seqid seqid on v4.0 owner
 * @param[in]  check_seqid Whether to validate owner_seqid
 * @param[in]  tag     Arbitrary string for logging/debugging
 *
 * @return NFSv4 status codes
 */
nfsstat4 nfs4_Check_Stateid(stateid4 *stateid, struct fsal_obj_handle *fsal_obj,
			    state_t **state, compound_data_t *data, int flags,
			    seqid4 owner_seqid, bool check_seqid,
			    const char *tag)
{
	uint32_t client_unique_prefix = 0;
	uint64_t unique_prefix = get_unique_server_id() & 0xFFFFFFFF;
	state_t *state2 = NULL;
	struct fsal_obj_handle *obj2 = NULL;
	state_owner_t *owner2 = NULL;
	char str[DISPLAY_STATEID4_SIZE] = "\0";
	struct display_buffer dspbuf = { sizeof(str), str, str };
	bool str_valid = false;
	int32_t diff;
	clientid4 clientid;
	nfs_client_id_t *pclientid;
	int rc;
	nfsstat4 status;

	if (isDebug(COMPONENT_STATE)) {
		display_stateid4(&dspbuf, stateid);
		str_valid = true;
	}

	LogFullDebug(COMPONENT_STATE, "Check %s stateid flags%s%s%s%s%s%s", tag,
		     flags & STATEID_SPECIAL_ALL_0 ? " ALL_0" : "",
		     flags & STATEID_SPECIAL_ALL_1 ? " ALL_1" : "",
		     flags & STATEID_SPECIAL_CURRENT ? " CURRENT" : "",
		     flags & STATEID_SPECIAL_CLOSE_40 ? " CLOSE_40" : "",
		     flags & STATEID_SPECIAL_CLOSE_41 ? " CLOSE_41" : "",
		     flags == 0 ? " NONE" : "");

	/* Test for OTHER is all zeros */
	if (memcmp(stateid->other, all_zero, OTHERSIZE) == 0) {
		if (stateid->seqid == 0 &&
		    (flags & STATEID_SPECIAL_ALL_0) != 0) {
			/* All 0 stateid */
			LogDebug(COMPONENT_STATE,
				 "Check %s stateid found special all 0 stateid",
				 tag);

			/** @todo FSF: eventually this may want to return an
			 * actual state for use in temporary locks for I/O.
			 */
			data->current_stateid_valid = false;
			goto success;
		}
		if (stateid->seqid == 1 &&
		    (flags & STATEID_SPECIAL_CURRENT) != 0) {
			/* Special current stateid */
			LogDebug(
				COMPONENT_STATE,
				"Check %s stateid found special 'current' stateid",
				tag);
			if (!data->current_stateid_valid) {
				LogDebug(
					COMPONENT_STATE,
					"Check %s stateid STATEID_SPECIAL_CURRENT - current stateid is bad",
					tag);
				status = NFS4ERR_BAD_STATEID;
				goto failure;
			}

			/* Copy current stateid in and proceed to checks */
			*stateid = data->current_stateid;
			goto check_it;
		}

		LogDebug(
			COMPONENT_STATE,
			"Check %s stateid with OTHER all zeros, seqid %u unexpected",
			tag, (unsigned int)stateid->seqid);
		status = NFS4ERR_BAD_STATEID;
		goto failure;
	}

	/* Test for OTHER is all ones */
	if (memcmp(stateid->other, all_ones, OTHERSIZE) == 0) {
		/* Test for special all ones stateid */
		if (stateid->seqid == seqid_all_one &&
		    (flags & STATEID_SPECIAL_ALL_1) != 0) {
			/* All 1 stateid */
			LogDebug(COMPONENT_STATE,
				 "Check %s stateid found special all 1 stateid",
				 tag);

			/** @todo FSF: eventually this may want to return an
			 * actual state for use in temporary locks for I/O.
			 */
			data->current_stateid_valid = false;
			goto success;
		}

		LogDebug(
			COMPONENT_STATE,
			"Check %s stateid with OTHER all ones, seqid %u unexpected",
			tag, (unsigned int)stateid->seqid);
		status = NFS4ERR_BAD_STATEID;
		goto failure;
	}

check_it:

	/* Extract the clientid from the stateid other field */
	memcpy(&clientid, stateid->other, sizeof(clientid));

	/* Extract the epoch from the clientid */
	client_unique_prefix = clientid >> (clientid4)32;

	/* Check if stateid was made from this server instance */
	if (client_unique_prefix != unique_prefix) {
		if (str_valid)
			LogDebug(COMPONENT_STATE,
				 "Check %s stateid found stale stateid %s", tag,
				 str);
		if (data->minorversion == 0)
			status = NFS4ERR_STALE_STATEID;
		else
			status = NFS4ERR_BAD_STATEID;
		goto failure;
	}

	/* Try to get the related state */
	state2 = nfs4_State_Get_Pointer(stateid->other);

	/* Check if the given stateid corresponds to a delegation that has
	 * already been revoked by the server.
	 *
	 * Once a delegation is revoked, the server must reject any subsequent
	 * operation that presents that delegation's stateid.
	 *
	 * This list-based check ensures that if the stateid was recorded in
	 * the revoked_delegations_list at revoke time (via add_to_revoked_
	 * delegations()), we detect it here and immediately return
	 * NFS4ERR_DELEG_REVOKED to the client instead of proceeding with
	 * normal state validation.
	 */

	if (is_stateid_revoked(stateid)) {
		LogDebug(COMPONENT_STATE,
			 "Check %s stateid corresponds to revoked delegation",
			 tag);

		data->current_stateid_valid = false;
		status = NFS4ERR_DELEG_REVOKED;
		goto failure;
	}

	/* We also need a reference to the state_obj and state_owner.
	 * If we can't get them, we will check below for lease invalidity.
	 * Note that calling get_state_obj_export_owner_refs with a NULL
	 * state2 returns false.
	 *
	 * NOTE: Don't ask for obj2 if fsal_obj is NULL. We won't need obj2 in
	 *       that case, and we may not have an op_ctx to release the object
	 *       reference.
	 */
	if (!get_state_obj_export_owner_refs(
		    state2, fsal_obj != NULL ? &obj2 : NULL, NULL, &owner2)) {
		/* We matched this server's epoch, but could not find the
		 * stateid. Chances are, the client was expired and the state
		 * has all been freed.
		 *
		 * We could use another check here for a BAD stateid
		 */
		if (str_valid)
			LogDebug(COMPONENT_STATE,
				 "Check %s stateid could not find %s", tag,
				 str);

		/* Try and find the clientid */
		rc = nfs_client_id_get_confirmed(clientid, &pclientid);

		if (rc != CLIENT_ID_SUCCESS) {
			/* Unknown client id (or other problem),
			 * return that result.
			 */
			status = clientid_error_to_nfsstat(rc);
			goto failure;
		}

		if ((flags & (STATEID_SPECIAL_CLOSE_40 |
			      STATEID_SPECIAL_CLOSE_41)) != 0) {
			/* This is a close with a valid clientid, but invalid
			 * stateid counter. We will assume this is a replayed
			 * close.
			 */
			if (data->preserved_clientid != NULL) {
				/* We don't expect this, but, just in case...
				 * Update and release already reserved lease.
				 */
				update_lease_simple(data->preserved_clientid);

				/* Release the reference we hold on
				 * preserved_clientid */
				dec_client_id_ref(data->preserved_clientid);
				data->preserved_clientid = NULL;
			}

			/* Check if lease is expired and reserve it. If it's
			 * valid and this is STATEID_SPECIAL_CLOSE_40, update
			 * the lease.
			 */
			if (!reserve_lease_or_expire(
				    pclientid,
				    (flags & STATEID_SPECIAL_CLOSE_40) != 0,
				    NULL)) {
				LogDebug(COMPONENT_STATE,
					 "Returning NFS4ERR_EXPIRED");

				/* Release the clientid reference we just
				 * acquired.
				 */
				dec_client_id_ref(pclientid);
				status = NFS4ERR_EXPIRED;
				goto failure;
			}

			if ((flags & STATEID_SPECIAL_CLOSE_40) == 0) {
				/* Not STATEID_SPECIAL_CLOSE_40, remember the
				 * reserved clientid for the rest of the
				 * compound.
				 */
				data->preserved_clientid = pclientid;

				/* Take a reference on the new
				 * preserved_clientid */
				inc_client_id_ref(data->preserved_clientid);
			}

			/* Replayed close, it's ok, but stateid doesn't exist */
			LogDebug(COMPONENT_STATE,
				 "Check %s stateid is a replayed close", tag);
			data->current_stateid_valid = false;
			goto success;
		}

		if (state2 == NULL)
			status = NFS4ERR_BAD_STATEID;
		else {
			/* We had a valid stateid, but the entry was stale.
			 * Check if lease is expired and reserve it so we
			 * can distinguish between the state_t being in the
			 * midst of tear down due to expired lease or if
			 * in fact the entry is actually stale.
			 *
			 * If it's valid, Just update the lease and leave the
			 * reserved clientid NULL.
			 */
			if (!reserve_lease_or_expire(pclientid, true, NULL)) {
				LogDebug(COMPONENT_STATE,
					 "Returning NFS4ERR_EXPIRED");

				/* Release the clientid reference we just
				 * acquired.
				 */
				dec_client_id_ref(pclientid);
				status = NFS4ERR_EXPIRED;
				goto failure;
			}

			/* The lease was valid, so this must be a stale
			 * entry.
			 */
			status = NFS4ERR_STALE;
		}

		/* Release the clientid reference we just acquired. */
		dec_client_id_ref(pclientid);
		goto failure;
	}

	/* Now, if this lease is not already reserved, reserve it */
	if (data->preserved_clientid !=
	    owner2->so_owner.so_nfs4_owner.so_clientrec) {
		if (data->preserved_clientid != NULL) {
			/* We don't expect this to happen, but, just in case...
			 * Update and release already reserved lease.
			 */
			update_lease_simple(data->preserved_clientid);

			/* Release the reference we hold on
			 * preserved_clientid */
			dec_client_id_ref(data->preserved_clientid);
			data->preserved_clientid = NULL;
		}

		/* Check if lease is expired and reserve it */
		if (!reserve_lease_or_expire(
			    owner2->so_owner.so_nfs4_owner.so_clientrec, false,
			    &owner2)) {
			LogDebug(COMPONENT_STATE, "Returning NFS4ERR_EXPIRED");

			status = NFS4ERR_EXPIRED;
			goto failure;
		}

		data->preserved_clientid =
			owner2->so_owner.so_nfs4_owner.so_clientrec;

		/* Take a reference on the new preserved_clientid */
		inc_client_id_ref(data->preserved_clientid);
	}

	/* Sanity check : Is this the right file ? */
	if (fsal_obj && !fsal_obj->obj_ops->handle_cmp(fsal_obj, obj2)) {
		if (str_valid)
			LogDebug(
				COMPONENT_STATE,
				"Check %s stateid found stateid %s has wrong file",
				tag, str);
		status = NFS4ERR_BAD_STATEID;
		goto failure;
	}

	/* Whether stateid.seqid may be zero depends on the state type
	   exclusively, See RFC 5661 pp. 161,287-288. */
	if ((state2->state_type == STATE_TYPE_LAYOUT) ||
	    (stateid->seqid != 0)) {
		/* Check seqid in stateid */

		/**
		 * @todo fsf: maybe change to simple comparison:
		 *            stateid->seqid < state2->state_seqid
		 *            as good enough and maybe makes pynfs happy.
		 */
		diff = stateid->seqid - state2->state_seqid;
		if (diff < 0) {
			/* if this is NFSv4.0 and stateid's seqid is one less
			 * than current AND if owner_seqid is current
			 * pass state back to allow replay check
			 */
			if ((check_seqid) &&
			    ((diff == -1) ||
			     ((state2->state_seqid == 1) &&
			      (stateid->seqid == seqid_all_one))) &&
			    (owner_seqid ==
			     owner2->so_owner.so_nfs4_owner.so_seqid)) {
				LogDebug(COMPONENT_STATE, "possible replay?");
				*state = state2;
				status = NFS4ERR_REPLAY;
				goto replay;
			}
			/* OLD_STATEID */
			if (str_valid)
				LogDebug(
					COMPONENT_STATE,
					"Check %s stateid found OLD stateid %s, expected seqid %" PRIu32,
					tag, str, state2->state_seqid);
			status = NFS4ERR_OLD_STATEID;
			goto failure;
		}

		/* stateid seqid is current and owner seqid is previous,
		 * replay (should be an error condition that did not change
		 * the stateid, no real need to check since the operation
		 * must be the same)
		 */
		else if ((diff == 0) && (check_seqid) &&
			 (owner_seqid ==
			  owner2->so_owner.so_nfs4_owner.so_seqid)) {
			LogDebug(COMPONENT_STATE, "possible replay?");
			*state = state2;
			status = NFS4ERR_REPLAY;
			goto replay;
		} else if (diff > 0) {
			/* BAD_STATEID */
			if (str_valid)
				LogDebug(
					COMPONENT_STATE,
					"Check %s stateid found BAD stateid %s, expected seqid %" PRIu32,
					tag, str, state2->state_seqid);
			status = NFS4ERR_BAD_STATEID;
			goto failure;
		}
	}

	data->current_stateid_valid = true;

	if (str_valid)
		LogFullDebug(COMPONENT_STATE,
			     "Check %s stateid found valid stateid %s - %p",
			     tag, str, state2);

	/* Copy stateid into current for later use */
	data->current_stateid = *stateid;
	data->current_stateid.seqid = state2->state_seqid;

success:

	if (obj2 != NULL)
		obj2->obj_ops->put_ref(obj2);

	if (owner2 != NULL)
		dec_state_owner_ref(owner2);

	*state = state2;
	return NFS4_OK;

failure:

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

	*state = NULL;

replay:

	if (obj2 != NULL)
		obj2->obj_ops->put_ref(obj2);

	if (owner2 != NULL)
		dec_state_owner_ref(owner2);

	data->current_stateid_valid = false;
	return status;
}

/**
 * @brief Display the stateid table
 */

void nfs_State_PrintAll(void)
{
	if (isFullDebug(COMPONENT_STATE))
		hashtable_log(COMPONENT_STATE, ht_state_id);
}

/**
 * @brief Update stateid and set current
 *
 * We increment the seqid, handling wraparound, and copy the id into
 * the response.
 *
 * @param[in,out] state The state to update
 * @param[out]    resp  Stateid in response
 * @param[in,out] data  Compound data to upddate with current stateid
 *                      (may be NULL)
 * @param[in]     tag   Arbitrary text for debug/log
 */
void update_stateid(state_t *state, stateid4 *resp, compound_data_t *data,
		    const char *tag)
{
	/* Increment state_seqid, handling wraparound */
	state->state_seqid += 1;
	if (state->state_seqid == 0)
		state->state_seqid = 1;

	/* Copy stateid into current for later use */
	if (data) {
		COPY_STATEID(&data->current_stateid, state);
		data->current_stateid_valid = true;
	}

	/* Copy stateid into response */
	COPY_STATEID(resp, state);

	if (isFullDebug(COMPONENT_STATE)) {
		char str[DISPLAY_STATEID4_SIZE] = "\0";
		struct display_buffer dspbuf = { sizeof(str), str, str };

		display_stateid4(&dspbuf, resp);

		LogDebug(COMPONENT_STATE,
			 "Update %s stateid to %s for response", tag, str);
	}
}

/** @} */
