// 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    state_lock.c
 * @brief   Functions used in lock management.
 */

#include "config.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <time.h>
#include <pthread.h>
#include <string.h>

#include "log.h"
#include "hashtable.h"
#include "fsal.h"
#include "nfs_core.h"
#include "nfs4.h"
#include "sal_functions.h"
/*#include "nlm_util.h"*/
#include "export_mgr.h"
#include "gsh_lttng/gsh_lttng.h"
#if defined(USE_LTTNG) && !defined(LTTNG_PARSING)
#include "gsh_lttng/generated_traces/lock.h"
#endif
#include "sal_metrics.h"

/**
 * @page state_lock_entry_locking state_lock_entry_t locking rule
 *
 * The value is always updated/read with @c nlm_lock_entry->lock held If
 * we have @c nlm_lock_list mutex held we can read it safely, because the
 * value is always updated while walking the list with @c entry->state_lock
 * held.
 *
 * The update happens as below:
 * @code{.c}
 *  pthread_rwlock_wrlock(&entry->state_mutex)
 *  pthread_mutex_lock(lock_entry->sle_mutex)
 *  update the lock_entry value
 *  ........
 * @endcode
 *
 * The value is ref counted with nlm_lock_entry->sle_ref_count so that
 * a parallel cancel/unlock won't endup freeing the datastructure. The
 * last release on the data structure ensure that it is freed.
 */

#ifdef DEBUG_SAL
/**
 * @brief All locks.
 */
static struct glist_head state_all_locks = GLIST_HEAD_INIT(state_all_locks);
/**
 * @brief All locks mutex
 */
pthread_mutex_t all_locks_mutex;
#endif

/**
 * @brief All locks blocked in FSAL
 */
struct glist_head state_blocked_locks = GLIST_HEAD_INIT(state_blocked_locks);

/**
 * @brief Mutex to protect lock lists
 */
pthread_mutex_t blocked_locks_mutex;

/**
 * @brief Owner of state with no defined owner
 */
state_owner_t unknown_owner = {
	.so_owner_val = "ganesha_unknown_owner",
	.so_type = STATE_LOCK_OWNER_UNKNOWN,
	.so_refcount = 1,
	.so_owner_len = 21,
	.so_lock_list = GLIST_HEAD_INIT(unknown_owner.so_lock_list),
};

static state_status_t do_lock_op(struct fsal_obj_handle *obj, state_t *state,
				 fsal_lock_op_t lock_op, state_owner_t *owner,
				 fsal_lock_param_t *lock,
				 state_owner_t **holder,
				 fsal_lock_param_t *conflict, bool overlap);
/**
 * @brief Blocking lock cookies
 */

/**
 * @brief Check whether a lock is from NLM
 *
 * @param[in] lock_entry Lock to check
 *
 * @retval true if the lock is from NLM.
 * @retval false if the lock is not from NLM.
 */
bool lock_owner_is_nlm(state_lock_entry_t *lock_entry)
{
#ifdef _USE_NLM
	return lock_entry->sle_owner->so_type == STATE_LOCK_OWNER_NLM;
#else /* _USE_NLM */
	return false;
#endif /* _USE_NLM */
}

/******************************************************************************
 *
 * Functions to display various aspects of a lock
 *
 ******************************************************************************/
/**
 * @brief Find the end of lock range
 *
 * @param[in] lock The lock to check
 *
 * @return Last byte of lock.
 */
static inline uint64_t lock_end(fsal_lock_param_t *lock)
{
	if (lock->lock_length == 0)
		return UINT64_MAX;
	else
		return lock->lock_start + lock->lock_length - 1;
}

/**
 * @brief String for lock type
 *
 * @param[in] ltype Lock type
 *
 * @return Readable string.
 */
const char *str_lockt(fsal_lock_t ltype)
{
	switch (ltype) {
	case FSAL_LOCK_R:
		return "READ ";
	case FSAL_LOCK_W:
		return "WRITE";
	case FSAL_NO_LOCK:
		return "NO LOCK";
	}
	return "?????";
}

/**
 * @brief Return string for blocking status
 *
 * @param[in] blocked Blocking status
 *
 * @return String for blocking status.
 */
const char *str_blocked(state_blocking_t blocked)
{
	switch (blocked) {
	case STATE_NON_BLOCKING:
		return "NON_BLOCKING";
	case STATE_BLOCKING:
		return "BLOCKING    ";
	case STATE_AVAILABLE:
		return "AVAILABLE   ";
	case STATE_CANCELED:
		return "CANCELED    ";
	}
	return "unknown     ";
}

/**
 * @brief Return string for protocol type
 *
 * @param[in] lock protocol type
 *
 * @return String for lock protocol type.
 */
const char *str_protocol(lock_protocol_t protocol)
{
	switch (protocol) {
	case LOCK_NLM:
		return "LOCK_NLM  ";
	case LOCK_NFSv4:
		return "LOCK_NFSv4";
	case LOCK_9P:
		return "LOCK_9P   ";
	}
	return "unknown   ";
}

const char *str_block_type(state_block_type_t btype)
{
	switch (btype) {
	case STATE_BLOCK_NONE:
		return "STATE_BLOCK_NONE    ";
	case STATE_BLOCK_INTERNAL:
		return "STATE_BLOCK_INTERNAL";
	case STATE_BLOCK_ASYNC:
		return "STATE_BLOCK_ASYNC   ";
	case STATE_BLOCK_POLL:
		return "STATE_BLOCK_POLL    ";
	}

	return "unknown             ";
}

/******************************************************************************
 *
 * Function to compare lock parameters
 *
 ******************************************************************************/

/**
 * @brief Check if locks differ
 *
 * @note This is not complete, it doesn't check the owner's IP
 *       address.
 *
 * @param[in] lock1 A lock
 * @param[in] lock2 Another lock
 *
 * @retval true if locks differ.
 * @retval false if locks are the same.
 */
static inline bool different_lock(fsal_lock_param_t *lock1,
				  fsal_lock_param_t *lock2)
{
	return (lock1->lock_type != lock2->lock_type) ||
	       (lock1->lock_start != lock2->lock_start) ||
	       (lock1->lock_length != lock2->lock_length);
}

/******************************************************************************
 *
 * Functions to log locks in various ways
 *
 ******************************************************************************/

/**
 * @brief Log a lock entry with a passed refcount
 *
 * @param[in] reason Arbitrary string
 * @param[in] le     Entry to log
 */
static void log_entry_ref_count(const char *reason, state_lock_entry_t *le,
				int32_t refcount, char *file, int line,
				char *function)
{
	if (isFullDebug(COMPONENT_STATE)) {
		char owner[LOG_BUFF_LEN] = "\0";
		struct display_buffer dspbuf = { sizeof(owner), owner, owner };

		display_owner(&dspbuf, le->sle_owner);

		DisplayLogComponentLevel(
			COMPONENT_STATE, file, line, function, NIV_FULL_DEBUG,
			"%s Entry: %p obj=%p, fileid=%" PRIu64
			", export=%u, type=%s, start=0x%" PRIx64
			", end=0x%" PRIx64 ", protocol %s, blocked=%s/%p/%s"
			", state=%p, sle_refcount=%" PRIu32 ", owner={%s}",
			reason, le, le->sle_obj, (uint64_t)le->sle_obj->fileid,
			(unsigned int)le->sle_export->export_id,
			str_lockt(le->sle_lock.lock_type),
			le->sle_lock.lock_start, lock_end(&le->sle_lock),
			str_protocol(le->sle_protocol),
			str_blocked(le->sle_blocked), le->sle_block_data,
			le->sle_block_data
				? str_block_type(
					  le->sle_block_data->sbd_block_type)
				: str_block_type(STATE_BLOCK_NONE),
			le->sle_state, refcount, owner);
	}
}

#define LogEntryRefCount(reason, le, refcount)                                \
	log_entry_ref_count(reason, le, refcount, (char *)__FILE__, __LINE__, \
			    (char *)__func__)

/**
 * @brief Log a lock entry
 *
 * @param[in] reason Arbitrary string
 * @param[in] le     Entry to log
 */
#define LogEntry(reason, le)                                          \
	log_entry_ref_count(reason, le,                               \
			    atomic_fetch_int32_t(&le->sle_ref_count), \
			    (char *)__FILE__, __LINE__, (char *)__func__)

/**
 * @brief Log a list of locks
 *
 * @param[in] reason Arbitrary string
 * @param[in] obj  FSAL object (mostly unused)
 * @param[in] list   List of lock entries
 *
 * @retval true if list is empty.
 * @retval false if list is non-empty.
 */
static bool LogList(const char *reason, struct fsal_obj_handle *obj,
		    struct glist_head *list)
{
	if (isFullDebug(COMPONENT_STATE)) {
		struct glist_head *glist;
		state_lock_entry_t *found_entry;

		if (glist_empty(list)) {
			if (obj != NULL)
				LogFullDebug(COMPONENT_STATE,
					     "%s for %p is empty", reason, obj);
			else
				LogFullDebug(COMPONENT_STATE, "%s is empty",
					     reason);
			return true;
		}

		glist_for_each(glist, list) {
			found_entry = glist_entry(glist, state_lock_entry_t,
						  sle_list);

			LogEntry(reason, found_entry);
			if (found_entry->sle_obj == NULL)
				break;
		}
	}

	return false;
}

/**
 * @brief Log blocked locks on list
 *
 * Must hold blocked_locks_mutex.
 *
 * @param[in] reason Arbitrary string
 * @param[in] obj  File
 * @param[in] list   List of lock entries
 *
 * @retval true if list is empty.
 * @retval false if list is non-empty.
 */
static bool LogBlockedList(const char *reason, struct fsal_obj_handle *obj,
			   struct glist_head *list)
{
	if (isFullDebug(COMPONENT_STATE)) {
		struct glist_head *glist;
		state_lock_entry_t *found_entry;
		state_block_data_t *block_entry;

		if (glist_empty(list)) {
			if (obj != NULL)
				LogFullDebug(COMPONENT_STATE,
					     "%s for %p is empty", reason, obj);
			else
				LogFullDebug(COMPONENT_STATE, "%s is empty",
					     reason);
			return true;
		}

		glist_for_each(glist, list) {
			block_entry = glist_entry(glist, state_block_data_t,
						  sbd_list);

			found_entry = block_entry->sbd_lock_entry;
			LogEntry(reason, found_entry);
			if (found_entry->sle_obj == NULL)
				break;
		}
	}

	return false;
}

/**
 * @brief Log a lock
 *
 * @param[in] component The component to log to
 * @param[in] debug     Log level
 * @param[in] reason    Arbitrary string
 * @param[in] obj	File
 * @param[in] owner     Lock owner
 * @param[in] lock      Lock description
 */
void log_lock(log_components_t component, log_levels_t debug,
	      const char *reason, struct fsal_obj_handle *obj,
	      state_owner_t *owner, fsal_lock_param_t *lock, char *file,
	      int line, char *function)
{
	if (isLevel(component, debug)) {
		char str[LOG_BUFF_LEN] = "\0";
		struct display_buffer dspbuf = { sizeof(str), str, str };

		if (owner != NULL)
			display_owner(&dspbuf, owner);
		else
			display_cat(&dspbuf, "NONE");

		DisplayLogComponentLevel(component, file, line, function, debug,
					 "%s Lock: obj=%p, fileid=%" PRIu64
					 ", type=%s, start=0x%" PRIx64
					 ", end=0x%" PRIx64 ", owner={%s}",
					 reason, obj, (uint64_t)obj->fileid,
					 str_lockt(lock->lock_type),
					 lock->lock_start, lock_end(lock), str);
	}
}

/**
 * @brief Log a lock description
 *
 * @param[in] component The component to log to
 * @param[in] debug     Log level
 * @param[in] reason    Arbitrary string
 * @param[in] obj       FSAL obj handle
 * @param[in] owner     Lock owner
 * @param[in] lock      Lock description
 */
void log_lock_desc(log_components_t component, log_levels_t debug,
		   const char *reason, struct fsal_obj_handle *obj, void *owner,
		   fsal_lock_param_t *lock, char *file, int line,
		   char *function)
{
	if (isLevel(component, debug)) {
		DisplayLogComponentLevel(
			component, file, line, function, debug,
			"%s Lock: obj=%p, owner=%p, type=%s, start=0x%llx, end=0x%llx",
			reason, obj, owner, str_lockt(lock->lock_type),
			(unsigned long long)lock->lock_start,
			(unsigned long long)lock_end(lock));
	}
}

#define LogLockDesc(component, debug, reason, obj, owner, lock)   \
	log_lock_desc(component, debug, reason, obj, owner, lock, \
		      (char *)__FILE__, __LINE__, (char *)__func__)

/**
 * @brief Log all locks
 *
 * @param[in] label Arbitrary string
 */
void dump_all_locks(const char *label)
{
#ifdef DEBUG_SAL
	struct glist_head *glist;

	PTHREAD_MUTEX_lock(&all_locks_mutex);

	if (glist_empty(&state_all_locks)) {
		LogFullDebug(COMPONENT_STATE, "All Locks are freed");
		PTHREAD_MUTEX_unlock(&all_locks_mutex);
		return;
	}

	glist_for_each(glist, &state_all_locks)
		LogEntry(label,
			 glist_entry(glist, state_lock_entry_t, sle_all_locks));

	PTHREAD_MUTEX_unlock(&all_locks_mutex);
#else
	return;
#endif
}

/******************************************************************************
 *
 * Functions to manage lock entries and lock list
 *
 ******************************************************************************/
/**
 * @brief Create a lock entry
 *
 * This function aborts (in gsh_malloc) if no memory is available.
 *
 * @param[in] obj      File to lock
 * @param[in] export   Export being accessed
 * @param[in] blocked  Blocking status
 * @param[in] owner    Lock owner
 * @param[in] state    State associated with lock
 * @param[in] lock     Lock description
 *
 * @return The new entry.
 */
static state_lock_entry_t *create_state_lock_entry(
	struct fsal_obj_handle *obj, struct gsh_export *export,
	state_blocking_t blocked, lock_protocol_t protocol,
	state_owner_t *owner, state_t *state, fsal_lock_param_t *lock)
{
	state_lock_entry_t *new_entry;

	new_entry = gsh_calloc(1, sizeof(*new_entry));

	LogFullDebug(COMPONENT_STATE, "new_entry = %p owner %p", new_entry,
		     owner);

	PTHREAD_MUTEX_init(&new_entry->sle_mutex, NULL);

	/* sle_block_data will be filled in later if necessary */
	new_entry->sle_block_data = NULL;
	new_entry->sle_ref_count = 1;
	new_entry->sle_obj = obj;
	new_entry->sle_blocked = blocked;
	new_entry->sle_protocol = protocol;
	new_entry->sle_owner = owner;
	new_entry->sle_state = state;
	new_entry->sle_lock = *lock;
	new_entry->sle_export = export;

#ifdef _USE_NLM
	if (owner->so_type == STATE_LOCK_OWNER_NLM) {
		/* Add to list of locks owned by client that owner belongs to */
		state_nlm_client_t *client =
			owner->so_owner.so_nlm_owner.so_client;

		inc_nsm_client_ref(client->slc_nsm_client);

		PTHREAD_MUTEX_lock(&client->slc_nsm_client->ssc_mutex);
		glist_add_tail(&client->slc_nsm_client->ssc_lock_list,
			       &new_entry->sle_client_locks);

		PTHREAD_MUTEX_unlock(&client->slc_nsm_client->ssc_mutex);
	}
#endif /* _USE_NLM */

	/* Add to list of locks owned by export */
	PTHREAD_RWLOCK_wrlock(&new_entry->sle_export->exp_lock);
	glist_add_tail(&new_entry->sle_export->exp_lock_list,
		       &new_entry->sle_export_locks);
	PTHREAD_RWLOCK_unlock(&new_entry->sle_export->exp_lock);
	get_gsh_export_ref(new_entry->sle_export);

	/* Get ref for sle_obj */
	obj->obj_ops->get_ref(obj);

	/* Add to list of locks owned by owner */
	inc_state_owner_ref(owner);

	PTHREAD_MUTEX_lock(&owner->so_mutex);

	if (state != NULL) {
		glist_add_tail(&state->state_data.lock.state_locklist,
			       &new_entry->sle_state_locks);

		inc_state_t_ref(state);
	}

	glist_add_tail(&owner->so_lock_list, &new_entry->sle_owner_locks);

	PTHREAD_MUTEX_unlock(&owner->so_mutex);

#ifdef DEBUG_SAL
	PTHREAD_MUTEX_lock(&all_locks_mutex);

	glist_add_tail(&state_all_locks, &new_entry->sle_all_locks);

	PTHREAD_MUTEX_unlock(&all_locks_mutex);
#endif

	if (blocked == STATE_NON_BLOCKING)
		sal_metrics__locks_inc(SAL_METRICS_HOLDERS);
	else
		sal_metrics__locks_inc(SAL_METRICS_WAITERS);
	return new_entry;
}

/**
 * @brief Duplicate a lock entry
 *
 * @param[in] orig_entry Entry to duplicate
 *
 * @return New entry.
 */
static inline state_lock_entry_t *
state_lock_entry_t_dup(state_lock_entry_t *orig_entry)
{
	return create_state_lock_entry(
		orig_entry->sle_obj, orig_entry->sle_export,
		orig_entry->sle_blocked, orig_entry->sle_protocol,
		orig_entry->sle_owner, orig_entry->sle_state,
		&orig_entry->sle_lock);
}

/**
 * @brief Take a reference on a lock entry
 *
 * @param[in,out] lock_entry Entry to reference
 */
static inline void lock_entry_inc_ref(state_lock_entry_t *lock_entry)
{
	int32_t refcount = atomic_inc_int32_t(&lock_entry->sle_ref_count);

	LogEntryRefCount("Increment sle_ref_count", lock_entry, refcount);
}

/**
 * @brief Relinquish a reference on a lock entry
 *
 * @param[in,out] lock_entry Entry to release
 */
void lock_entry_dec_ref(state_lock_entry_t *lock_entry)
{
	int32_t refcount = atomic_dec_int32_t(&lock_entry->sle_ref_count);

	LogEntryRefCount(refcount != 0 ? "Decrement sle_ref_count"
				       : "Decrement sle_ref_count and freeing",
			 lock_entry, refcount);

	assert(refcount >= 0);

	if (refcount == 0) {
		/* Release block data if present */
		if (lock_entry->sle_block_data != NULL) {
			/* need to remove from the state_blocked_locks list */
			PTHREAD_MUTEX_lock(&blocked_locks_mutex);

			/* While waiting for the blocked_locks_mutex, refcount
			 * might have increased, and so we can't release the
			 * lock entry */
			refcount = atomic_fetch_int32_t(
				&lock_entry->sle_ref_count);
			if (refcount != 0) {
				LogEntryRefCount(
					"Refcount not zero after acquiring lock. Not freeing entry",
					lock_entry, refcount);
				PTHREAD_MUTEX_unlock(&blocked_locks_mutex);
				return;
			}

			glist_del(&lock_entry->sle_block_data->sbd_list);
			PTHREAD_MUTEX_unlock(&blocked_locks_mutex);

			gsh_free(lock_entry->sle_block_data);
			lock_entry->sle_block_data = NULL;
		}

#ifdef DEBUG_SAL
		PTHREAD_MUTEX_lock(&all_locks_mutex);
		glist_del(&lock_entry->sle_all_locks);
		PTHREAD_MUTEX_unlock(&all_locks_mutex);
#endif

		if (lock_entry->sle_owner != NULL) {
			dec_state_owner_ref(lock_entry->sle_owner);
			lock_entry->sle_owner = NULL;
		}

		if (lock_entry->sle_state != NULL) {
			dec_state_t_ref(lock_entry->sle_state);
			lock_entry->sle_state = NULL;
		}

		if (lock_entry->sle_blocked == STATE_NON_BLOCKING)
			sal_metrics__locks_dec(SAL_METRICS_HOLDERS);
		else
			sal_metrics__locks_dec(SAL_METRICS_WAITERS);

		lock_entry->sle_obj->obj_ops->put_ref(lock_entry->sle_obj);
		put_gsh_export(lock_entry->sle_export);
		PTHREAD_MUTEX_destroy(&lock_entry->sle_mutex);
		gsh_free(lock_entry);
	}
}

/**
 * @brief Remove an entry from the lock lists
 *
 * @param[in,out] lock_entry Entry to remove
 */
static void remove_from_locklist(state_lock_entry_t *lock_entry)
{
	state_owner_t *owner = lock_entry->sle_owner;

	LogEntry("Removing", lock_entry);

	/*
	 * If some other thread is holding a reference to this nlm_lock_entry
	 * don't free the structure. But drop from the lock list
	 */
	if (owner != NULL) {
#ifdef _USE_NLM
		if (owner->so_type == STATE_LOCK_OWNER_NLM) {
			/* Remove from list of locks owned
			 * by client that owner belongs to
			 */
			state_nlm_client_t *client =
				owner->so_owner.so_nlm_owner.so_client;

			PTHREAD_MUTEX_lock(&client->slc_nsm_client->ssc_mutex);

			glist_del(&lock_entry->sle_client_locks);

			PTHREAD_MUTEX_unlock(
				&client->slc_nsm_client->ssc_mutex);

			dec_nsm_client_ref(client->slc_nsm_client);
		}
#endif /* _USE_NLM */

		/* Remove from list of locks owned by export */
		PTHREAD_RWLOCK_wrlock(&lock_entry->sle_export->exp_lock);
		glist_del(&lock_entry->sle_export_locks);
		PTHREAD_RWLOCK_unlock(&lock_entry->sle_export->exp_lock);

		/* Remove from list of locks owned by owner */
		PTHREAD_MUTEX_lock(&owner->so_mutex);

		glist_del(&lock_entry->sle_state_locks);
		glist_del(&lock_entry->sle_owner_locks);

		PTHREAD_MUTEX_unlock(&owner->so_mutex);
	}

	if (lock_entry->sle_blocked != STATE_NON_BLOCKING &&
	    lock_entry->sle_blocked != STATE_CANCELED) {
		/* Removing a blocking lock, mark as canceled in case of
		 * racing with granting. This may be because the lock was
		 * granted and merged with surrounding locks.
		 */
		LogEntry("Removing lock and cancelling block", lock_entry);
		lock_entry->sle_blocked = STATE_CANCELED;
	}

	glist_del(&lock_entry->sle_list);
	lock_entry_dec_ref(lock_entry);
}

/**
 * @brief Find a conflicting entry
 *
 * @note The st_lock MUST be held
 *
 * @param[in] ostate File state to search
 * @param[in] owner The lock owner
 * @param[in] lock  Lock to check
 *
 * @return A conflicting entry or NULL.
 */
static state_lock_entry_t *get_overlapping_entry(struct state_hdl *ostate,
						 state_owner_t *owner,
						 fsal_lock_param_t *lock)
{
	struct glist_head *glist;
	struct glist_head *glist_n;
	state_lock_entry_t *found_entry = NULL;
	uint64_t found_entry_end, range_end = lock_end(lock);

recheck_for_conflicting_entries:
	glist_for_each_safe(glist, glist_n, &ostate->file.lock_list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		LogEntry("Checking", found_entry);

		/* Skip blocked or cancelled locks */
		if (found_entry->sle_blocked == STATE_BLOCKING ||
		    found_entry->sle_blocked == STATE_CANCELED)
			continue;

		found_entry_end = lock_end(&found_entry->sle_lock);

		if ((found_entry_end >= lock->lock_start) &&
		    (found_entry->sle_lock.lock_start <= range_end)) {
			/* lock overlaps see if we can allow:
			 * allow if neither lock is exclusive or
			 * the owner is the same
			 */
			if ((found_entry->sle_lock.lock_type == FSAL_LOCK_W ||
			     lock->lock_type == FSAL_LOCK_W) &&
			    different_owners(found_entry->sle_owner, owner)) {
				/* Recheck for expiry */
				state_owner_t *cf_own = found_entry->sle_owner;
				nfs_client_id_t *client_id =
					cf_own->so_owner.so_nfs4_owner
						.so_clientrec;

				if ((atomic_fetch_uint32_t(
					    &num_of_curr_expired_clients)) &&
				    (cf_own->so_type >=
				     STATE_OPEN_OWNER_NFSV4) &&
				    client_id->marked_for_delayed_cleanup) {
					/* Release the state lock, to clean */
					ostate->no_cleanup = false;
					PTHREAD_MUTEX_unlock(&ostate->st_lock);

					reap_expired_client_list(client_id);
					/* Acquire back the state lock */
					PTHREAD_MUTEX_lock(&ostate->st_lock);
					ostate->no_cleanup = true;

					/* Continue to recheck for conflicts */
					goto recheck_for_conflicting_entries;
				}
				/* found a conflicting lock, return it */
				return found_entry;
			}
		}
	}

	return NULL;
}

/**
 * @brief Add a lock, potentially merging with existing locks
 *
 * We need to iterate over the full lock list and remove
 * any mapping entry. And l_offset = 0 and sle_lock.lock_length = 0 lock_entry
 * implies remove all entries
 *
 * @note The st_lock MUST be held
 *
 * @param[in,out] ostate     File state to operate on
 * @param[in]     lock_entry Lock to add
 */
static void merge_lock_entry(struct state_hdl *ostate,
			     state_lock_entry_t *lock_entry)
{
	state_lock_entry_t *check_entry;
	state_lock_entry_t *check_entry_right;
	uint64_t check_entry_end;
	uint64_t lock_entry_end;
	struct glist_head *glist;
	struct glist_head *glistn;

	/* lock_entry might be STATE_NON_BLOCKING */

	glist_for_each_safe(glist, glistn, &ostate->file.lock_list) {
		check_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		/* Skip entry being merged - it could be in the list */
		if (check_entry == lock_entry)
			continue;

		if (different_owners(check_entry->sle_owner,
				     lock_entry->sle_owner))
			continue;

		/* Only merge fully granted locks */
		if (check_entry->sle_blocked != STATE_NON_BLOCKING)
			continue;

		check_entry_end = lock_end(&check_entry->sle_lock);
		lock_entry_end = lock_end(&lock_entry->sle_lock);

		if ((check_entry_end + 1) < lock_entry->sle_lock.lock_start)
			/* nothing to merge */
			continue;

		if ((lock_entry_end + 1) < check_entry->sle_lock.lock_start)
			/* nothing to merge */
			continue;

		/* Need to handle locks of different types differently, may
		 * split an old lock. If new lock totally overlaps old lock,
		 * the new lock will replace the old lock so no special work
		 * to be done.
		 */
		if ((check_entry->sle_lock.lock_type !=
		     lock_entry->sle_lock.lock_type) &&
		    ((lock_entry_end < check_entry_end) ||
		     (check_entry->sle_lock.lock_start <
		      lock_entry->sle_lock.lock_start))) {
			if (lock_entry_end < check_entry_end &&
			    check_entry->sle_lock.lock_start <
				    lock_entry->sle_lock.lock_start) {
				/* Need to split old lock */
				check_entry_right =
					state_lock_entry_t_dup(check_entry);
				glist_add_tail(&ostate->file.lock_list,
					       &(check_entry_right->sle_list));
			} else {
				/* No split, just shrink, make the logic below
				 * work on original lock
				 */
				check_entry_right = check_entry;
			}
			if (lock_entry_end < check_entry_end) {
				/* Need to shrink old lock from beginning
				 * (right lock if split)
				 */
				LogEntry("Merge shrinking right",
					 check_entry_right);
				check_entry_right->sle_lock.lock_start =
					lock_entry_end + 1;
				check_entry_right->sle_lock.lock_length =
					check_entry_end - lock_entry_end;
				LogEntry("Merge shrunk right",
					 check_entry_right);
			}
			if (check_entry->sle_lock.lock_start <
			    lock_entry->sle_lock.lock_start) {
				/* Need to shrink old lock from end
				 * (left lock if split)
				 */
				LogEntry("Merge shrinking left", check_entry);
				check_entry->sle_lock.lock_length =
					lock_entry->sle_lock.lock_start -
					check_entry->sle_lock.lock_start;
				LogEntry("Merge shrunk left", check_entry);
			}
			/* Done splitting/shrinking old lock */
			continue;
		}

		/* check_entry touches or overlaps lock_entry, expand
		 * lock_entry
		 */
		if (lock_entry_end < check_entry_end)
			/* Expand end of lock_entry */
			lock_entry_end = check_entry_end;

		if (check_entry->sle_lock.lock_start <
		    lock_entry->sle_lock.lock_start)
			/* Expand start of lock_entry */
			lock_entry->sle_lock.lock_start =
				check_entry->sle_lock.lock_start;

		/* Compute new lock length */
		lock_entry->sle_lock.lock_length =
			lock_entry_end - lock_entry->sle_lock.lock_start + 1;

		/* Remove merged entry */
		LogEntry("Merged", lock_entry);
		LogEntry("Merging removing", check_entry);
		remove_from_locklist(check_entry);
	}
}

/**
 * @brief Free a list of lock entries
 *
 * @param[in] list The list of locks to free
 */
static void free_list(struct glist_head *list)
{
	state_lock_entry_t *found_entry;
	struct glist_head *glist, *glistn;

	glist_for_each_safe(glist, glistn, list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		remove_from_locklist(found_entry);
	}
}

/**
 * @brief Subtract a lock from a lock entry.
 *
 * This function places any remaining bits into the split list.
 *
 * @param[in,out] found_entry Lock being modified
 * @param[in]     lock        Lock being removed
 * @param[out]    split_list  Remaining fragments of found_entry
 * @param[out]    remove_list Removed lock entries
 * @param[out]    removed     True if lock is removed
 *
 * @return State status.
 */
static state_status_t subtract_lock_from_entry(state_lock_entry_t *found_entry,
					       fsal_lock_param_t *lock,
					       struct glist_head *split_list,
					       struct glist_head *remove_list,
					       bool *removed)
{
	uint64_t found_entry_end = lock_end(&found_entry->sle_lock);
	uint64_t range_end = lock_end(lock);
	state_lock_entry_t *found_entry_left = NULL;
	state_lock_entry_t *found_entry_right = NULL;
	state_status_t status = STATE_SUCCESS;

	if (range_end < found_entry->sle_lock.lock_start) {
		/* nothing to split */
		*removed = false;
		return status;
	}

	if (found_entry_end < lock->lock_start) {
		/* nothing to split */
		*removed = false;
		return status;
	}

	if ((lock->lock_start <= found_entry->sle_lock.lock_start) &&
	    range_end >= found_entry_end) {
		/* Fully overlap */
		LogEntry("Remove Complete", found_entry);
		goto complete_remove;
	}

	LogEntry("Split", found_entry);

	/* Delete the old entry and add one or two new entries */
	if (lock->lock_start > found_entry->sle_lock.lock_start) {
		found_entry_left = state_lock_entry_t_dup(found_entry);

		found_entry_left->sle_lock.lock_length =
			lock->lock_start - found_entry->sle_lock.lock_start;
		LogEntry("Left split", found_entry_left);
		glist_add_tail(split_list, &(found_entry_left->sle_list));
	}

	if (range_end < found_entry_end) {
		found_entry_right = state_lock_entry_t_dup(found_entry);

		found_entry_right->sle_lock.lock_start = range_end + 1;

		/* found_entry_end being UINT64_MAX indicates that the
		 * sle_lock.lock_length is zero and the lock is held till
		 * the end of the file. In such case assign the split lock
		 * length too to zero to indicate the file end.
		 */
		if (found_entry_end == UINT64_MAX)
			found_entry_right->sle_lock.lock_length = 0;
		else
			found_entry_right->sle_lock.lock_length =
				found_entry_end - range_end;
		LogEntry("Right split", found_entry_right);
		glist_add_tail(split_list, &(found_entry_right->sle_list));
	}

complete_remove:

	/* Remove the lock from the list it's
	 * on and put it on the remove_list
	 */
	glist_move_tail(remove_list, &(found_entry->sle_list));

	*removed = true;
	return status;
}

/**
 * @brief Subtract a lock from a list of locks
 *
 * This function possibly splits entries in the list.
 *
 * @param[in]     owner   Lock owner
 * @param[in]     state   Associated lock state
 * @param[in]     lock    Lock to remove
 * @param[out]    removed True if an entry was removed
 * @param[in,out] list    List of locks to modify
 *
 * @return State status.
 */
static state_status_t subtract_lock_from_list(state_owner_t *owner,
					      bool state_applies, int32_t state,
					      fsal_lock_param_t *lock,
					      bool *removed,
					      struct glist_head *list)
{
	state_lock_entry_t *found_entry;
	struct glist_head split_lock_list, remove_list;
	struct glist_head *glist, *glistn;
	state_status_t status = STATE_SUCCESS;
	bool removed_one = false;

	*removed = false;

	glist_init(&split_lock_list);
	glist_init(&remove_list);

	glist_for_each_safe(glist, glistn, list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		if (owner != NULL &&
		    different_owners(found_entry->sle_owner, owner))
			continue;

		/* Only care about granted locks */
		if (found_entry->sle_blocked != STATE_NON_BLOCKING)
			continue;

		/* Skip locks owned by this NLM state.
		 * This protects NLM locks from the current iteration of an NLM
		 * client from being released by SM_NOTIFY.
		 */
		if (state_applies &&
		    found_entry->sle_state->state_seqid == state)
			continue;

		/* We have matched owner. Even though we are taking a reference
		 * to found_entry, we don't inc the ref count because we want
		 * to drop the lock entry.
		 */
		status = subtract_lock_from_entry(found_entry, lock,
						  &split_lock_list,
						  &remove_list, &removed_one);
		*removed |= removed_one;

		if (status != STATE_SUCCESS) {
			/* We ran out of memory while splitting,
			 * deal with it outside loop
			 */
			break;
		}
	}

	if (status != STATE_SUCCESS) {
		/* We ran out of memory while splitting. split_lock_list
		 * has been freed. For each entry on the remove_list, put
		 * it back on the list.
		 */
		LogDebug(COMPONENT_STATE, "Failed %s", state_err_str(status));
		glist_for_each_safe(glist, glistn, &remove_list) {
			found_entry = glist_entry(glist, state_lock_entry_t,
						  sle_list);
			glist_move_tail(list, &(found_entry->sle_list));
		}
	} else {
		/* free the enttries on the remove_list */
		free_list(&remove_list);

		/* now add the split lock list */
		glist_add_list_tail(list, &split_lock_list);
	}

	LogFullDebug(COMPONENT_STATE,
		     "List of all locks for list=%p returning %d", list,
		     status);

	return status;
}

/******************************************************************************
 *
 * Implement hash table to hash blocked lock entries by cookie
 *
 ******************************************************************************/

static void grant_blocked_locks(struct state_hdl *);

static inline int display_lock_cookie(struct display_buffer *dspbuf,
				      struct gsh_buffdesc *buff)
{
	return display_opaque_value(dspbuf, buff->addr, buff->len);
}

/**
 * @brief Display lock cookie in hash table
 *
 * @param[in]  dspbuf display buffer to display into
 * @param[in]  buff   Key to display
 */
int display_lock_cookie_key(struct display_buffer *dspbuf,
			    struct gsh_buffdesc *buff)
{
	return display_lock_cookie(dspbuf, buff);
}

/**
 * @brief Display lock cookie entry
 *
 * @param[in/out] dspbuf display_buffer describing output string
 * @param[in]     he     Cookie entry to display
 *
 * @return the bytes remaining in the buffer.
 */
int display_lock_cookie_entry(struct display_buffer *dspbuf,
			      state_cookie_entry_t *he)
{
	int b_left = display_printf(dspbuf, "%p: cookie ", he);

	if (b_left <= 0)
		return b_left;

	b_left = display_opaque_value(dspbuf, he->sce_cookie,
				      he->sce_cookie_size);

	if (b_left <= 0)
		return b_left;

	b_left = display_printf(dspbuf, " obj {%p fileid=%" PRIu64 "} lock {",
				he->sce_obj, he->sce_obj->fileid);

	if (b_left <= 0)
		return b_left;

	if (he->sce_lock_entry != NULL) {
		b_left = display_printf(dspbuf, "%p owner {",
					he->sce_lock_entry);

		if (b_left <= 0)
			return b_left;

		b_left = display_owner(dspbuf, he->sce_lock_entry->sle_owner);

		if (b_left <= 0)
			return b_left;

		b_left = display_printf(
			dspbuf,
			"} type=%s start=0x%" PRIx64 " end=0x%" PRIx64
			" protocol=%s, blocked=%s}",
			str_lockt(he->sce_lock_entry->sle_lock.lock_type),
			he->sce_lock_entry->sle_lock.lock_start,
			lock_end(&he->sce_lock_entry->sle_lock),
			str_protocol(he->sce_lock_entry->sle_protocol),
			str_blocked(he->sce_lock_entry->sle_blocked));
	} else {
		b_left = display_printf(dspbuf, "<NULL>}");
	}

	return b_left;
}

/**
 * @brief Display lock cookie entry in hash table
 *
 * @param[in]  dspbuf display buffer to display into
 * @param[in]  buff   Value to display
 */
int display_lock_cookie_val(struct display_buffer *dspbuf,
			    struct gsh_buffdesc *buff)
{
	return display_lock_cookie_entry(dspbuf, buff->addr);
}

/**
 * @brief Compare lock cookie in hash table
 *
 * @param[in] buff1 A key
 * @param[in] buff2 Another key
 *
 * @retval 0 on equality.
 * @retval 1 on inequality.
 */
int compare_lock_cookie_key(struct gsh_buffdesc *buff1,
			    struct gsh_buffdesc *buff2)
{
	if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) {
		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_lock_cookie(&dspbuf1, buff1);
		display_lock_cookie(&dspbuf2, buff2);
		LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2);
	}

	if (buff1->addr == buff2->addr)
		return 0;

	if (buff1->len != buff2->len)
		return 1;

	if (buff1->addr == NULL || buff2->addr == NULL)
		return 1;

	return memcmp(buff1->addr, buff2->addr, buff1->len);
}

/**
 * @brief Hash index for lock cookie
 *
 * @todo Replace with a good hash function.
 *
 * @param[in] hparam Hash parameters
 * @param[in] key    Key to hash
 *
 * @return Hash index.
 */
uint32_t lock_cookie_value_hash_func(hash_parameter_t *hparam,
				     struct gsh_buffdesc *key)
{
	unsigned int sum = 0;
	unsigned int i;
	unsigned long res;
	unsigned char *addr = key->addr;

	/* Compute the sum of all the characters */
	for (i = 0; i < key->len; i++)
		sum += (unsigned char)addr[i];

	res = (unsigned long)sum + (unsigned long)key->len;

	if (isDebug(COMPONENT_HASHTABLE))
		LogFullDebug(COMPONENT_STATE, "value = %lu",
			     res % hparam->index_size);

	return (unsigned long)(res % hparam->index_size);
}

/**
 * @brief RBT hash for lock cookie
 *
 * @todo Replace with a good hash function.
 *
 * @param[in] hparam Hash parameters
 * @param[in] key    Key to hash
 *
 * @return RBT hash.
 */
uint64_t lock_cookie_rbt_hash_func(hash_parameter_t *hparam,
				   struct gsh_buffdesc *key)
{
	unsigned int sum = 0;
	unsigned int i;
	unsigned long res;
	unsigned char *addr = key->addr;

	/* Compute the sum of all the characters */
	for (i = 0; i < key->len; i++)
		sum += (unsigned char)addr[i];

	res = (unsigned long)sum + (unsigned long)key->len;

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

	return res;
}

/**
 * @brief Free a cookie entry
 *
 * @param[in] cookie_entry Entry to free
 * @param[in] unblock      Whether to remove block data
 */
void free_cookie(state_cookie_entry_t *cookie_entry, bool unblock)
{
	char str[LOG_BUFF_LEN] = "\0";
	struct display_buffer dspbuf = { sizeof(str), str, str };
	bool str_valid = false;
	void *cookie = cookie_entry->sce_cookie;
	state_lock_entry_t *lock_entry = cookie_entry->sce_lock_entry;

	if (isFullDebug(COMPONENT_STATE)) {
		display_lock_cookie_entry(&dspbuf, cookie_entry);
		str_valid = true;
	}

	/* Since the cookie is not in the hash table,
	 * we can just free the memory
	 */
	if (str_valid)
		LogFullDebug(COMPONENT_STATE, "Free Lock Cookie {%s}", str);

	/* If block data is still attached to lock entry, remove it */
	if (lock_entry != NULL && unblock) {
		if (lock_entry->sle_block_data != NULL)
			lock_entry->sle_block_data->sbd_blocked_cookie = NULL;

		lock_entry_dec_ref(lock_entry);
		cookie_entry->sce_obj->obj_ops->put_ref(cookie_entry->sce_obj);
	}

	/* Free the memory for the cookie and the cookie entry */
	gsh_free(cookie);
	gsh_free(cookie_entry);
}

/**
 * Parameters used for lock cookie hash table initialization.
 *
 * @todo Switch the cookie table to something else and get rid
 * of this.
 */

static hash_parameter_t cookie_param = {
	.index_size = PRIME_STATE,
	.hash_func_key = lock_cookie_value_hash_func,
	.hash_func_rbt = lock_cookie_rbt_hash_func,
	.compare_key = compare_lock_cookie_key,
	.display_key = display_lock_cookie_key,
	.display_val = display_lock_cookie_val,
	.flags = HT_FLAG_NONE,
};

static hash_table_t *ht_lock_cookies;

/**
 * @brief Add a grant cookie to a blocked lock
 *
 * @param[in]  obj          File to operate on
 * @param[in]  cookie       Cookie to add
 * @param[in]  cookie_size  Cookie length
 * @param[in]  lock_entry   Lock entry
 * @param[out] cookie_entry New cookie entry
 *
 * @return State status.
 */
state_status_t state_add_grant_cookie(struct fsal_obj_handle *obj, void *cookie,
				      int cookie_size,
				      state_lock_entry_t *lock_entry,
				      state_cookie_entry_t **cookie_entry)
{
	struct gsh_buffdesc buffkey, buffval;
	state_cookie_entry_t *hash_entry;
	char str[LOG_BUFF_LEN] = "\0";
	struct display_buffer dspbuf = { sizeof(str), str, str };
	bool str_valid = false;
	state_status_t status = 0;
	/* structure to hold retained state */
	struct hash_latch latch;
	/* Stored return code */
	hash_error_t rc = 0;

	*cookie_entry = NULL;

	if (lock_entry->sle_block_data == NULL || cookie == NULL ||
	    cookie_size == 0 ||
	    lock_entry->sle_block_data->sbd_grant_type == STATE_GRANT_NONE) {
		/* Something's wrong with this entry */
		status = STATE_INCONSISTENT_ENTRY;
		return status;
	}

	if (isFullDebug(COMPONENT_STATE)) {
		display_opaque_value(&dspbuf, cookie, cookie_size);
		str_valid = true;
	}

	hash_entry = gsh_calloc(1, sizeof(*hash_entry));

	buffkey.addr = gsh_malloc(cookie_size);

	hash_entry->sce_obj = obj;
	hash_entry->sce_lock_entry = lock_entry;
	hash_entry->sce_cookie = buffkey.addr;
	hash_entry->sce_cookie_size = cookie_size;

	memcpy(buffkey.addr, cookie, cookie_size);
	buffkey.len = cookie_size;
	buffval.addr = (void *)hash_entry;
	buffval.len = sizeof(*hash_entry);

	if (isFullDebug(COMPONENT_STATE)) {
		display_lock_cookie_entry(&dspbuf, hash_entry);
		str_valid = true;
	}

	rc = hashtable_getlatch(ht_lock_cookies, &buffkey, NULL, true, &latch);

	if (rc != HASHTABLE_ERROR_NO_SUCH_KEY) {
		if (rc == HASHTABLE_SUCCESS) {
			/* The key already exists, no reason to proceed */
			hashtable_releaselatched(ht_lock_cookies, &latch);
		}

		status = STATE_HASH_TABLE_ERROR;
		goto error;
	}

	if (str_valid)
		LogFullDebug(COMPONENT_STATE, "Lock Cookie {%s} Added", str);

	switch (lock_entry->sle_block_data->sbd_grant_type) {
	case STATE_GRANT_NONE:
		/* Can't get here, but handle anyway */
		status = STATE_INCONSISTENT_ENTRY;
		break;

	case STATE_GRANT_POLL:
	case STATE_GRANT_FSAL_AVAILABLE:
		/* A poll triggered by the polling thread actually looks
		 * exactly like a poll triggered by an FSAL upcall...
		 *
		 * Now that we are sure we can continue, acquire the FSAL lock.
		 * If we get STATE_LOCK_BLOCKED we need to return...
		 */
		status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_LOCKB,
				    lock_entry->sle_owner,
				    &lock_entry->sle_lock, NULL, NULL, false);
		break;

	case STATE_GRANT_INTERNAL:
		/* Now that we are sure we can continue, acquire the FSAL lock.
		 * If we get STATE_LOCK_BLOCKED we need to return...
		 */
		status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_LOCK,
				    lock_entry->sle_owner,
				    &lock_entry->sle_lock, NULL, NULL, false);
		break;

	case STATE_GRANT_FSAL:
		/* No need to go to FSAL for lock */
		status = STATE_SUCCESS;
		break;
	}

	if (status != STATE_SUCCESS) {
		/* Lock will be returned to right blocking type if it is
		 * still blocking. We could lose a block if we failed for
		 * any other reason
		 */
		if (status == STATE_LOCK_BLOCKED)
			LogDebug(COMPONENT_STATE,
				 "Unable to lock FSAL for %s/%s lock, error=%s",
				 str_protocol(lock_entry->sle_protocol),
				 str_blocked(lock_entry->sle_blocked),
				 state_err_str(status));
		else
			LogMajor(COMPONENT_STATE,
				 "Unable to lock FSAL for %s/%s lock, error=%s",
				 str_protocol(lock_entry->sle_protocol),
				 str_blocked(lock_entry->sle_blocked),
				 state_err_str(status));

		LogEntry("Entry", lock_entry);

		hashtable_releaselatched(ht_lock_cookies, &latch);
		goto error;
	}

	/* Increment lock entry reference count and link it to the cookie */
	lock_entry_inc_ref(lock_entry);
	lock_entry->sle_block_data->sbd_blocked_cookie = hash_entry;

	/* Also take an obj reference. */
	obj->obj_ops->get_ref(obj);

	/* Whether this succeeds or fails, latch is released. */
	rc = hashtable_setlatched(ht_lock_cookies, &buffkey, &buffval, &latch,
				  false, NULL, NULL);

	if (rc == HASHTABLE_SUCCESS) {
		/* We're done - cookie is all set up */
		*cookie_entry = hash_entry;
		return status;
	}

	/* This really should not fail... */
	if (!str_valid)
		display_lock_cookie_entry(&dspbuf, hash_entry);

	LogCrit(COMPONENT_STATE, "Lock Cookie {%s} unexpected failure %s", str,
		hash_table_err_to_str(rc));
	lock_entry_dec_ref(lock_entry);
	lock_entry->sle_block_data->sbd_blocked_cookie = NULL;
	obj->obj_ops->put_ref(obj);
	status = STATE_HASH_TABLE_ERROR;

error:

	gsh_free(hash_entry);
	gsh_free(buffkey.addr);

	if (str_valid)
		LogFullDebug(COMPONENT_STATE, "Lock Cookie {%s} %s", str,
			     state_err_str(status));
	return status;
}

/**
 * @brief Cancel a lock grant from the FSAL
 *
 * @param[in] cookie_entry Entry for the lock grant
 *
 * @return State status.
 */
state_status_t state_cancel_grant(state_cookie_entry_t *cookie_entry)
{
	state_status_t status = 0;
	/* We had acquired an FSAL lock, need to release it. */
	status = do_lock_op(cookie_entry->sce_obj,
			    cookie_entry->sce_lock_entry->sle_state,
			    FSAL_OP_UNLOCK,
			    cookie_entry->sce_lock_entry->sle_owner,
			    &cookie_entry->sce_lock_entry->sle_lock,
			    NULL, /* no conflict expected */
			    NULL, false);

	if (status != STATE_SUCCESS)
		LogMajor(
			COMPONENT_STATE,
			"Unable to unlock FSAL for canceled GRANTED lock, error=%s",
			state_err_str(status));

	/* And release the cookie and unblock lock
	 * (because lock will be removed)
	 */
	free_cookie(cookie_entry, true);

	return status;
}

/**
 * @brief Find a grant matching a cookie
 *
 * @param[in]  cookie       Cookie to look up
 * @param[in]  cookie_size  Length of cookie
 * @param[out] cookie_entry Found entry
 *
 * @return State status.
 */
state_status_t state_find_grant(void *cookie, int cookie_size,
				state_cookie_entry_t **cookie_entry)
{
	struct gsh_buffdesc buffkey;
	struct gsh_buffdesc buffval;
	struct gsh_buffdesc buffused_key;
	char str[LOG_BUFF_LEN] = "\0";
	struct display_buffer dspbuf = { sizeof(str), str, str };
	bool str_valid = false;
	state_status_t status = 0;

	buffkey.addr = cookie;
	buffkey.len = cookie_size;

	if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) {
		display_lock_cookie(&dspbuf, &buffkey);

		LogFullDebug(COMPONENT_STATE, "KEY {%s}", str);

		str_valid = true;
	}

	if (HashTable_Del(ht_lock_cookies, &buffkey, &buffused_key, &buffval) !=
	    HASHTABLE_SUCCESS) {
		if (str_valid)
			LogFullDebug(COMPONENT_STATE, "KEY {%s} NOTFOUND", str);
		status = STATE_BAD_COOKIE;
		return status;
	}

	*cookie_entry = buffval.addr;

	if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) {
		display_lock_cookie_entry(&dspbuf, *cookie_entry);
		LogFullDebug(COMPONENT_STATE, "Found Lock Cookie {%s}", str);
	}

	status = STATE_SUCCESS;
	return status;
}

/**
 * @brief Grant a blocked lock
 *
 * @param[in] ostate     File state on which to grant it
 * @param[in] lock_entry Lock entry
 */
void grant_blocked_lock_immediate(struct state_hdl *ostate,
				  state_lock_entry_t *lock_entry)
{
	state_cookie_entry_t *cookie = NULL;
	state_status_t state_status;

	/* Try to clean up blocked lock. */
	if (lock_entry->sle_block_data != NULL) {
		if (lock_entry->sle_block_data->sbd_blocked_cookie != NULL) {
			/* Cookie is attached, try to get it */
			cookie = lock_entry->sle_block_data->sbd_blocked_cookie;

			state_status = state_find_grant(cookie->sce_cookie,
							cookie->sce_cookie_size,
							&cookie);
			if (state_status == STATE_SUCCESS) {
				/* We've got the cookie,
				 * free the cookie and the blocked lock
				 */
				free_cookie(cookie, true);
			} else {
				/* Otherwise, another thread has the cookie,
				 * let it do it's business.
				 */
				return;
			}
		} else {
			/* We have block data but no cookie,
			 * so we can just free the block data
			 */
			memset(lock_entry->sle_block_data, 0,
			       sizeof(*lock_entry->sle_block_data));
			gsh_free(lock_entry->sle_block_data);
			lock_entry->sle_block_data = NULL;
		}
	}

	if (lock_entry->sle_blocked != STATE_NON_BLOCKING) {
		/* Lock granted. Increase holders and decrease waiters */
		sal_metrics__locks_inc(SAL_METRICS_HOLDERS);
		sal_metrics__locks_dec(SAL_METRICS_WAITERS);
	}

	/* Mark lock as granted */
	lock_entry->sle_blocked = STATE_NON_BLOCKING;

	/* Merge any touching or overlapping locks into this one. */
	LogEntry("Granted immediate, merging locks for", lock_entry);

	merge_lock_entry(ostate, lock_entry);
	LogEntry("Immediate Granted entry", lock_entry);

	/* A lock downgrade could unblock blocked locks */
	grant_blocked_locks(ostate);
}

static void grant_nfsv4_blocking_lock(struct fsal_obj_handle *obj,
				      state_lock_entry_t *lock_entry)
{
	state_status_t status = do_lock_op(obj, lock_entry->sle_state,
					   FSAL_OP_LOCKB, lock_entry->sle_owner,
					   &lock_entry->sle_lock, NULL, NULL,
					   false);
	if (status != STATE_SUCCESS) {
		LogMajor(COMPONENT_STATE,
			 "Unable to lock a granted lock, error=%s",
			 state_err_str(status));
	}

	PTHREAD_MUTEX_lock(&blocked_locks_mutex);
	glist_del(&lock_entry->sle_block_data->sbd_list);
	PTHREAD_MUTEX_unlock(&blocked_locks_mutex);

	grant_blocked_lock_immediate(obj->state_hdl, lock_entry);
}

/**
 * @brief Finish granting a lock
 *
 * Do bookkeeping and merge the lock into the lock list.
 *
 * @param[in] cookie_entry Entry describing the grant
 */

void state_complete_grant(state_cookie_entry_t *cookie_entry)
{
	state_lock_entry_t *lock_entry;
	struct fsal_obj_handle *obj;

	lock_entry = cookie_entry->sce_lock_entry;
	obj = cookie_entry->sce_obj;

	/* Call obj->obj_ops->get_ref() because even though there MUST be at
	 * least one lock present for there to be a cookie_entry to even allow
	 * this routine to be called, that may be cleaned up by free_cookie
	 * and thus before we release the state lock.
	 */
	obj->obj_ops->get_ref(obj);

	STATELOCK_lock(obj);

	/* We need to make sure lock is ready to be granted */
	if (lock_entry->sle_blocked == STATE_AVAILABLE) {
		/* Mark lock as granted */
		lock_entry->sle_blocked = STATE_NON_BLOCKING;

		/* Merge any touching or overlapping locks into this one. */
		LogEntry("Granted, merging locks for", lock_entry);
		merge_lock_entry(obj->state_hdl, lock_entry);

		LogEntry("Granted entry", lock_entry);

		/* A lock downgrade could unblock blocked locks */
		grant_blocked_locks(obj->state_hdl);

		/* Lock granted. Increase holders and decrease waiters */
		sal_metrics__locks_inc(SAL_METRICS_HOLDERS);
		sal_metrics__locks_dec(SAL_METRICS_WAITERS);
	}

	/* Free cookie and unblock lock.
	 * If somehow the lock was unlocked/canceled while the GRANT
	 * was in progress, this will completely clean up the lock.
	 */
	free_cookie(cookie_entry, true);

	STATELOCK_unlock(obj);

	/* Release the obj reference taken above. */
	obj->obj_ops->put_ref(obj);
}

/**
 * @brief Attempt to grant a blocked lock
 *
 * @param[in] lock_entry Lock entry to grant
 */

void try_to_grant_lock(state_lock_entry_t *lock_entry)
{
	granted_callback_t call_back;
	state_blocking_t blocked;
	state_status_t status;
	struct gsh_export *export = lock_entry->sle_export;
	const char *reason;

	/* Try to grant if not cancelled and has block data and we are able
	 * to get an export reference.
	 */
	if (lock_entry->sle_blocked == STATE_NON_BLOCKING) {
		/* Nothing to do if already granted */
		LogEntry("Lock already granted", lock_entry);
		return;
	} else if (lock_entry->sle_blocked == STATE_AVAILABLE) {
		/* Should not happen, but just in case... Nothing to do. */
		LogEntry("Lock grant already in progress", lock_entry);
		return;
	} else if (lock_entry->sle_blocked == STATE_CANCELED) {
		reason = "Removing canceled blocked lock entry";
	} else if (lock_entry->sle_block_data == NULL) {
		reason = "Removing blocked lock entry with no block data";
	} else if (!export_ready(export)) {
		reason = "Removing blocked lock entry due to stale export";
	} else if (admin_shutdown) {
		reason = "Removing blocked lock entry due to shutdown";
	} else {
		call_back = lock_entry->sle_block_data->sbd_granted_callback;
		/* Mark the lock_entry as provisionally granted and make the
		 * granted call back. The granted call back is responsible
		 * for acquiring a reference to the lock entry if needed.
		 */
		blocked = lock_entry->sle_blocked;
		lock_entry->sle_blocked = STATE_AVAILABLE;
		if (lock_entry->sle_block_data->sbd_grant_type ==
		    STATE_GRANT_NONE)
			lock_entry->sle_block_data->sbd_grant_type =
				STATE_GRANT_INTERNAL;

		status = call_back(lock_entry->sle_obj, lock_entry);
		LOCK_AUTO_TRACEPOINT(lock_entry, granted_callback, TRACE_INFO,
				     "Sent granted callback and got {}",
				     status);

		if (status == STATE_LOCK_BLOCKED) {
			/* The lock is still blocked, restore it's type and
			 * leave it in the list.
			 */
			lock_entry->sle_blocked = blocked;
			lock_entry->sle_block_data->sbd_grant_type =
				STATE_GRANT_NONE;
			LogEntry("Granting callback left lock still blocked",
				 lock_entry);
			return;
		}

		/* At this point, we no longer need the entry on the
		 * blocked lock list in nlm, in nfsv4 we still need it to
		 * poll in case it is never requested by client after grant
		 * so that we could cancel it.
		 */
		if (lock_entry->sle_protocol == LOCK_NLM) {
			PTHREAD_MUTEX_lock(&blocked_locks_mutex);

			glist_del(&lock_entry->sle_block_data->sbd_list);

			PTHREAD_MUTEX_unlock(&blocked_locks_mutex);
		}

		if (status == STATE_SUCCESS)
			return;

		reason = "Removing unsuccessfully granted blocked lock";
	}

	/* There was no call back data, the call back failed,
	 * or the block was cancelled.
	 * Remove lock from list.
	 */
	LogEntry(reason, lock_entry);
	remove_from_locklist(lock_entry);
}

/**
 * @brief Routine to be called from the FSAL upcall handler
 *
 * @param[in] block_data Data describing blocked lock
 */

void process_blocked_lock_upcall(state_lock_entry_t *lock_entry)
{
	/* A lock entry reference was taken when this work was scheduled. */

	STATELOCK_lock(lock_entry->sle_obj);

	if (glist_null(&lock_entry->sle_list)) {
		LogEntry(
			"Received up-call for lock entry that was already removed from the lock list. Ignoring",
			lock_entry);
		goto out_upcall;
	}

	try_to_grant_lock(lock_entry);

out_upcall:
	STATELOCK_unlock(lock_entry->sle_obj);
}

/**
 * @brief Attempt to grant all blocked locks on a file
 *
 * @param[in] ostate File state
 */

static void grant_blocked_locks(struct state_hdl *ostate)
{
	state_lock_entry_t *found_entry;
	struct glist_head *glist, *glistn;
	struct fsal_export *export = op_ctx->ctx_export->fsal_export;

	if (!ostate)
		return;

	/* If FSAL supports async blocking locks,
	 * allow it to grant blocked locks.
	 */
	if (export->exp_ops.fs_supports(export, fso_lock_support_async_block))
		return;

	glist_for_each_safe(glist, glistn, &ostate->file.lock_list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		if (found_entry->sle_blocked != STATE_BLOCKING)
			continue;

		/* Found a blocked entry for this file,
		 * see if we can place the lock.
		 */
		if (get_overlapping_entry(ostate, found_entry->sle_owner,
					  &found_entry->sle_lock) != NULL)
			continue;

		/* Found an entry that might work, try to grant it. */
		try_to_grant_lock(found_entry);
	}
}

/**
 * @brief Cancel a blocked lock
 *
 * @param[in] obj      File on which to cancel the lock
 * @param[in] lock_entry Lock to cancel
 *
 * @return State status.
 */
static void cancel_blocked_lock(struct fsal_obj_handle *obj,
				state_lock_entry_t *lock_entry)
{
	state_cookie_entry_t *cookie = NULL;
	state_status_t state_status;

	/* If lock list is empty, there really isn't any work for us to do. */
	if (glist_empty(&obj->state_hdl->file.lock_list)) {
		LogDebug(COMPONENT_STATE,
			 "Cancel success on file with no locks");
		return;
	}

	/* Mark lock as canceled */
	LogEntry("Cancelling blocked", lock_entry);
	lock_entry->sle_blocked = STATE_CANCELED;

	/* Unlocking the entire region will remove any FSAL locks we held,
	 * whether from fully granted locks, or from blocking locks that were
	 * in the process of being granted.
	 */

	/* Try to clean up blocked lock if a cookie is present */
	if (lock_entry->sle_block_data != NULL &&
	    lock_entry->sle_block_data->sbd_blocked_cookie != NULL) {
		/* Cookie is attached, try to get it */
		cookie = lock_entry->sle_block_data->sbd_blocked_cookie;

		state_status = state_find_grant(cookie->sce_cookie,
						cookie->sce_cookie_size,
						&cookie);

		if (state_status == STATE_SUCCESS) {
			/* We've got the cookie,
			 * free the cookie and the blocked lock
			 */
			free_cookie(cookie, true);
		}
		/* otherwise, another thread has the cookie, let it do it's
		 * business, which won't be much, since we've already marked
		 * the lock CANCELED.
		 */
	} else {
		/* Otherwise, if block data is present, it will be freed when
		 * the lock entry is freed. If the cookie is held, the refcount
		 * it holds will prevent the lock entry from being released
		 * until the cookie is freed.
		 */

		/* Since a cookie was not found,
		 * the lock must still be in a state of needing cancelling.
		 */
		state_status =
			do_lock_op(obj, lock_entry->sle_state, FSAL_OP_CANCEL,
				   lock_entry->sle_owner, &lock_entry->sle_lock,
				   NULL, /* no conflict expected */
				   NULL, false); /* overlap not relevant */

		LOCK_AUTO_TRACEPOINT(lock_entry, cancel_request_end, TRACE_INFO,
				     "cancel request end {}", state_status);

		if (state_status != STATE_SUCCESS) {
			/* Unable to cancel,
			 * assume that granted upcall is on it's way.
			 */
			LogFullDebug(COMPONENT_STATE,
				     "Unable to cancel lock %d", state_status);
			LogEntry("Unable to cancel (grant upcall expected)",
				 lock_entry);
		}

		/* If there is a pending upcall at this point, the
		 * following request will release it. The upcall
		 * processing code can just ignore the granted lock, if
		 * a lock isn't found in the state_blocked_lock list.
		 * This is only true for nlm, as in nfsv4 lockers only
		 * become holders after a direct lock request which would have
		 * meant we wouldn't have reached here but to the unlock path
		 *
		 * @todo: Maybe an issue if this was a lock upgrade?
		 */
		if (lock_entry->sle_protocol == LOCK_NLM) {
			state_status = do_lock_op(
				obj, lock_entry->sle_state, FSAL_OP_UNLOCK,
				lock_entry->sle_owner, &lock_entry->sle_lock,
				NULL, /* no conflict expected */
				NULL, false); /* overlap not relevant */
			if (state_status != STATE_SUCCESS) {
				/* lock was probably not granted */
				LogFullDebug(
					COMPONENT_STATE,
					"Unable to unlock a blocked lock %d",
					state_status);
			}
		}
	}

	/* Remove the lock from the lock list */
	LogEntry("Removing", lock_entry);
	remove_from_locklist(lock_entry);
}

/**
 *
 * @brief Cancel blocked locks that overlap a lock
 *
 * Handle the situation where we have granted a lock and the client now
 * assumes it holds the lock, but we haven't received the GRANTED RSP, and
 * now the client is unlocking the lock.
 *
 * This will also handle the case of a client that uses UNLOCK to cancel
 * a blocked lock.
 *
 * Because this will release any blocked lock that was in the process of
 * being granted that overlaps the lock at all, we protect ourselves from
 * having a stuck lock at the risk of the client thinking it has a lock
 * it now doesn't.
 *
 * If the client unlock doesn't happen to fully overlap a blocked lock,
 * the blocked lock will be cancelled in full. Hopefully the client will
 * retry the remainder lock that should have still been blocking.
 *
 * @param[in,out] ostate File state on which to operate
 * @param[in]     owner  The state owner for the lock
 * @param[in]     state  Associated state
 * @param[in]     lock   Lock description
 */
void cancel_blocked_locks_range(struct state_hdl *ostate, state_owner_t *owner,
				bool state_applies, int32_t state,
				fsal_lock_param_t *lock)
{
	struct glist_head *glist, *glistn;
	state_lock_entry_t *found_entry = NULL;
	uint64_t found_entry_end, range_end = lock_end(lock);

	glist_for_each_safe(glist, glistn, &ostate->file.lock_list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		/* Skip locks not owned by owner */
		if (owner != NULL &&
		    different_owners(found_entry->sle_owner, owner))
			continue;

		/* Skip locks owned by this NLM state.
		 * This protects NLM locks from the current iteration of an NLM
		 * client from being released by SM_NOTIFY.
		 */
		if (state_applies &&
		    found_entry->sle_state->state_seqid == state)
			continue;

		/* Skip granted locks */
		if (found_entry->sle_blocked == STATE_NON_BLOCKING)
			continue;

		LogEntry("Checking", found_entry);

		found_entry_end = lock_end(&found_entry->sle_lock);

		if ((found_entry_end >= lock->lock_start) &&
		    (found_entry->sle_lock.lock_start <= range_end)) {
			/* lock overlaps, cancel it. */
			cancel_blocked_lock(ostate->file.obj, found_entry);
		}
	}
}

/**
 * @brief Release a lock grant
 *
 * @param[in] cookie_entry Grant entry
 *
 * @return State status.
 */
state_status_t state_release_grant(state_cookie_entry_t *cookie_entry)
{
	state_lock_entry_t *lock_entry;
	struct fsal_obj_handle *obj;
	state_status_t status = STATE_SUCCESS;

	lock_entry = cookie_entry->sce_lock_entry;
	obj = cookie_entry->sce_obj;

	obj->obj_ops->get_ref(obj);

	STATELOCK_lock(obj);

	/* We need to make sure lock is only "granted" once...
	 * It's (remotely) possible that due to latency, we might end up
	 * processing two GRANTED_RSP calls at the same time.
	 */
	if (lock_entry->sle_blocked == STATE_AVAILABLE) {
		/* Mark lock as canceled */
		lock_entry->sle_blocked = STATE_CANCELED;

		/* We had acquired an FSAL lock, need to release it. */
		status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_UNLOCK,
				    lock_entry->sle_owner,
				    &lock_entry->sle_lock,
				    NULL, /* no conflict expected */
				    NULL, false);

		if (status != STATE_SUCCESS)
			LogMajor(
				COMPONENT_STATE,
				"Unable to unlock FSAL for released GRANTED lock, error=%s",
				state_err_str(status));
		else {
			/* Remove the lock from the lock list.
			 * Will not free yet because of cookie reference to
			 * lock entry.
			 */
			LogEntry("Release Grant Removing", lock_entry);
			remove_from_locklist(lock_entry);
		}
	}

	/* Free the cookie and unblock the lock. This will release our final
	 * reference on the lock entry and should free it. (Unless another
	 * thread has a reference for some reason.
	 */
	free_cookie(cookie_entry, true);

	/* Check to see if we can grant any blocked locks. */
	grant_blocked_locks(obj->state_hdl);

	STATELOCK_unlock(obj);
	obj->obj_ops->put_ref(obj);

	return status;
}

/******************************************************************************
 *
 * Functions to interact with FSAL
 *
 ******************************************************************************/

/**
 * @brief Human-readable string from the lock operation
 *
 * @param[in] op The lock operation
 *
 * @return The human-readable string.
 */
static inline const char *fsal_lock_op_str(fsal_lock_op_t op)
{
	switch (op) {
	case FSAL_OP_LOCKT:
		return "FSAL_OP_LOCKT ";
	case FSAL_OP_LOCK:
		return "FSAL_OP_LOCK  ";
	case FSAL_OP_LOCKB:
		return "FSAL_OP_LOCKB ";
	case FSAL_OP_UNLOCK:
		return "FSAL_OP_UNLOCK";
	case FSAL_OP_CANCEL:
		return "FSAL_OP_CANCEL";
	}
	return "unknown";
}

/**
 * @brief Perform a lock operation
 *
 * We do state management and call down to the FSAL as appropriate, so
 * that the caller has a single entry point.
 *
 * @note The st_lock MUST be held
 *
 * @param[in]  obj      File on which to operate
 * @param[in]  state    state_t associated with lock if any
 * @param[in]  lock_op  Operation to perform
 * @param[in]  owner    Lock operation
 * @param[in]  lock     Lock description
 * @param[out] holder   Owner of conflicting lock
 * @param[out] conflict Description of conflicting lock
 * @param[in]  overlap  Hint that lock overlaps
 *
 * @return State status.
 */
state_status_t do_lock_op(struct fsal_obj_handle *obj, state_t *state,
			  fsal_lock_op_t lock_op, state_owner_t *owner,
			  fsal_lock_param_t *lock, state_owner_t **holder,
			  fsal_lock_param_t *conflict, bool overlap)
{
	fsal_status_t fsal_status;
	state_status_t status = STATE_SUCCESS;
	fsal_lock_param_t conflicting_lock;
	struct fsal_export *fsal_export = op_ctx->fsal_export;
	fsal_lock_op_t fsal_lock_op = lock_op;
	struct gsh_client *saved_client = op_ctx->client;
	struct gsh_client *owner_client = NULL;
#ifdef _USE_NLM
	struct state_nlm_client_t *nlm_client;
#endif
	struct nfs_client_id_t *nfs4_clientid = NULL;

	/* If the state is stale (under deletion), there is no locker, so return
	 * STATE_STATE_ERROR. we do have cases where we use null state but then
	 * the state is not stale.
	 */
	if (state != NULL && state->state_owner == NULL) {
		LogWarn(COMPONENT_STATE,
			"lock request with stale state owner "
			"(state:)%p (owner:)%p (obj:)%p",
			state, owner, obj);
		return STATE_STATE_ERROR;
	}

	lock->lock_sle_type = FSAL_POSIX_LOCK;

	/* Quick exit if:
	 * Locks are not supported by FSAL
	 * Async blocking locks are not supported and this is a cancel
	 * Lock owners are not supported and hint tells us that lock fully
	 *   overlaps a lock we already have (no need to make another FSAL
	 *   call in that case)
	 * Unlock of stale nfs4_clientid and some FSAL need the existence
	 * of lock record to check the future lock reclaim.
	 *
	 * We do NOT need to quick exit if async blocking locks are not
	 * supported and there is an overlap because we won't get here if
	 * the overlap includes a write lock (which would cause a block).
	 */
	LogFullDebug(
		COMPONENT_STATE,
		"Reasons to quick exit fso_lock_support=%s fso_lock_support_async_block=%s overlap=%s",
		fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support)
			? "yes"
			: "no",
		fsal_export->exp_ops.fs_supports(fsal_export,
						 fso_lock_support_async_block)
			? "yes"
			: "no",
		overlap ? "yes" : "no");
	if (!fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support) ||
	    (!fsal_export->exp_ops.fs_supports(fsal_export,
					       fso_lock_support_async_block) &&
	     lock_op == FSAL_OP_CANCEL))
		return STATE_SUCCESS;

	LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, fsal_lock_op_str(lock_op), obj,
		owner, lock);

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

	if (lock_op == FSAL_OP_LOCKB &&
	    !fsal_export->exp_ops.fs_supports(fsal_export,
					      fso_lock_support_async_block)) {
		fsal_lock_op = FSAL_OP_LOCK;
	}

	/* Fetch the gsh_client associated with the lock owner if available */
	switch (owner->so_type) {
	case STATE_LOCK_OWNER_UNKNOWN:
		/* Should never get here. */
		break;

#ifdef _USE_NLM
	case STATE_LOCK_OWNER_NLM:
		nlm_client = owner->so_owner.so_nlm_owner.so_client;
		owner_client = nlm_client->slc_nsm_client->ssc_client;
		break;
#endif

#ifdef _USE_9P
	case STATE_LOCK_OWNER_9P:
		/* OOPS - 9P won't work with FSALs that need
		 * op_ctx->client because it doesn't set it...
		 * They will crash soon enough...
		 */
		LogDebug(COMPONENT_STATE, "9P doesn't set op_ctx->client...");
		break;
#endif

	case STATE_OPEN_OWNER_NFSV4:
	case STATE_LOCK_OWNER_NFSV4:
	case STATE_CLIENTID_OWNER_NFSV4:
		nfs4_clientid = owner->so_owner.so_nfs4_owner.so_clientrec;
		owner_client = nfs4_clientid->gsh_client;
		break;
	}

	if (fsal_lock_op == FSAL_OP_UNLOCK && nfs4_clientid != NULL &&
	    nfs4_clientid->cid_confirmed == STALE_CLIENT_ID &&
	    fsal_export->exp_ops.fs_supports(fsal_export,
					     fso_lock_full_control)) {
		LogDebug(
			COMPONENT_STATE,
			"skip unlock of stale nfs4_clientid. FSAL need the lock record to check lock reclaim");
		return STATE_SUCCESS;
	}

	/* If the owner gsh_client doesn't match the op_ctx and is not NULL
	 * then save the op_ctx->client and switch to owner_client.
	 */
	if (owner_client != op_ctx->client && owner_client != NULL)
		set_op_context_client(owner_client);

	/* Perform this lock operation using the support_ex lock op. */
	fsal_status = obj->obj_ops->lock_op2(obj, state, owner, fsal_lock_op,
					     lock, &conflicting_lock);

	set_op_context_client(saved_client);

	status = state_error_convert(fsal_status);

	LogFullDebug(COMPONENT_STATE, "FSAL_lock_op returned %s",
		     state_err_str(status));

	if (status == STATE_LOCK_BLOCKED && fsal_lock_op != FSAL_OP_LOCKB) {
		/* This is an unexpected return code,
		 * make sure caller reports an error
		 */
		LogMajor(COMPONENT_STATE,
			 "FSAL returned unexpected STATE_LOCK_BLOCKED result");
		status = STATE_FSAL_ERROR;
	} else if (status == STATE_LOCK_CONFLICT && lock_op == FSAL_OP_LOCKB) {
		/* This must be a non-async blocking lock that was
		 * blocked, where we actually made a non-blocking
		 * call. In that case, actually return
		 * STATE_LOCK_BLOCKED.
		 */
		status = STATE_LOCK_BLOCKED;
	}

	if (status == STATE_LOCK_CONFLICT) {
		if (holder != NULL) {
			*holder = &unknown_owner;
			inc_state_owner_ref(&unknown_owner);
		}

		if (conflict != NULL)
			*conflict = conflicting_lock;
	}

	return status;
}

/**
 * @brief Fill out conflict information
 *
 * @param[in]  found_entry Conflicting lock
 * @param[out] holder      Owner that holds conflicting lock
 * @param[out] conflict    Description of conflicting lock
 */
void copy_conflict(state_lock_entry_t *found_entry, state_owner_t **holder,
		   fsal_lock_param_t *conflict)
{
	if (found_entry == NULL)
		return;

	if (holder != NULL) {
		*holder = found_entry->sle_owner;
		inc_state_owner_ref(found_entry->sle_owner);
	}
	if (conflict != NULL)
		*conflict = found_entry->sle_lock;
}

/******************************************************************************
 *
 * Primary lock interface functions
 *
 ******************************************************************************/

/**
 * @brief Test for lock availability
 *
 * This function acquires the state lock on an entry and thus is only
 * suitable for operations like lockt.  If one wishes to use it as
 * part of a larger lock or state operation one would need to split it
 * out.
 *
 * @param[in]  obj      File to test
 * @param[in]  state    Optional state_t to relate this test to a fsal_fd
 * @param[in]  owner    Lock owner making the test
 * @param[in]  lock     Lock description
 * @param[out] holder   Owner that holds conflicting lock
 * @param[out] conflict Description of conflicting lock
 *
 * @return State status.
 */
state_status_t state_test(struct fsal_obj_handle *obj, state_t *state,
			  state_owner_t *owner, fsal_lock_param_t *lock,
			  state_owner_t **holder, fsal_lock_param_t *conflict)
{
	state_lock_entry_t *found_entry;
	state_status_t status = 0;

	LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "TEST", obj, owner, lock);
	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, test_request_start, TRACE_INFO,
				      "test request started");

	STATELOCK_lock(obj);
	if (state && !state->state_owner) {
		/* This is a stale state, don't pass it to FSAL. */
		LogLock(COMPONENT_STATE, NIV_WARN, "Owner released", obj, owner,
			lock);
		state = NULL;
	}
	found_entry = get_overlapping_entry(obj->state_hdl, owner, lock);

	if (found_entry != NULL) {
		/* found a conflicting lock, return it */
		LogEntry("Found conflict", found_entry);
		copy_conflict(found_entry, holder, conflict);
		status = STATE_LOCK_CONFLICT;
	} else {
		/* Prepare to make call to FSAL for this lock */
		status = do_lock_op(obj, state, FSAL_OP_LOCKT, owner, lock,
				    holder, conflict, false);

		switch (status) {
		case STATE_SUCCESS:
			LogFullDebug(COMPONENT_STATE, "Lock success");
			break;
		case STATE_LOCK_CONFLICT:
			if (holder != NULL && conflict != NULL) {
				LogLock(COMPONENT_STATE, NIV_FULL_DEBUG,
					"Conflict from FSAL", obj, *holder,
					conflict);
			}
			break;
		case STATE_ESTALE:
			LogDebug(COMPONENT_STATE,
				 "Got error %s from FSAL lock operation",
				 state_err_str(status));
			break;
		default:
			LogMajor(COMPONENT_STATE,
				 "Got error from FSAL lock operation, error=%s",
				 state_err_str(status));
			break;
		}
	}
	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, test_request_end, TRACE_INFO,
				      "test request ended {}", status);

	if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS))
		LogList("Lock List", obj, &obj->state_hdl->file.lock_list);

	STATELOCK_unlock(obj);

	return status;
}

/**
 * @brief Cancel lock on given file
 *
 * @param[in]  obj	File to operate on
 * @param[in]  owner	Lock operation
 * @param[in]  lock	Lock description
 */
static void state_cancel_internal(struct fsal_obj_handle *obj,
				  state_owner_t *owner, fsal_lock_param_t *lock)
{
	struct glist_head *glist;
	state_lock_entry_t *found_entry;

	/* If lock list is empty, there really isn't any work for us to do. */
	if (glist_empty(&obj->state_hdl->file.lock_list)) {
		LogDebug(COMPONENT_STATE,
			 "Cancel success on file with no locks");

		return;
	}
	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, cancel_request_start,
				      TRACE_INFO, "cancel request started");

	glist_for_each(glist, &obj->state_hdl->file.lock_list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);

		if (different_owners(found_entry->sle_owner, owner))
			continue;

		/* Can not cancel a lock once it is granted */
		if (found_entry->sle_blocked == STATE_NON_BLOCKING)
			continue;

		if (different_lock(&found_entry->sle_lock, lock))
			continue;

		/* Cancel the blocked lock */
		cancel_blocked_lock(obj, found_entry);

		/* Check to see if we can grant any blocked locks. */
		grant_blocked_locks(obj->state_hdl);

		break;
	}
}

/**
 * @brief handle the case where requested lock is either contained or overlaps
 * an existing lock
 *
 * @param[in]  obj		File to operate on
 * @param[in]  owner		Lock operation
 * @param[in]  state		state_t associated with lock if any
 * @param[in]  blocking		lock block type
 * @param[in]  lock		Lock description
 * @param[in]  found_entry	entry to check for
 * @param[in]  found_entry_end	end of entry
 * @param[in]  range_end	end of range to test for
 * @param[out] overlap		Hint that lock overlaps
 * @param[out] lock_granted	Was lock granted from cache
 * @param[out] lock_cancel	Was request treated as a lock cancel
 */
static void handle_contained_range_if_present(
	struct fsal_obj_handle *obj, state_owner_t *owner, state_t *state,
	state_blocking_t blocking, fsal_lock_param_t *lock,
	state_lock_entry_t *found_entry, uint64_t found_entry_end,
	uint64_t range_end, bool *overlap, bool *lock_granted,
	bool *lock_cancel)
{
	*lock_granted = false;
	*lock_cancel = false;
	if (found_entry_end < range_end ||
	    found_entry->sle_lock.lock_start > lock->lock_start ||
	    found_entry->sle_lock.lock_type != lock->lock_type) {
		/* Lock is not fully overlap or contained */
		return;
	}
	if (different_owners(found_entry->sle_owner, owner)) {
		/* Found a compatible lock with a different lock owner
		 * that fully overlaps, set hint if conflicting lock exists.
		 */
		LogEntry("Found overlapping", found_entry);
		*overlap = true;
		return;
	}

	if (found_entry->sle_blocked == STATE_AVAILABLE &&
	    found_entry->sle_protocol == LOCK_NLM) {
		/* Need to handle completion of granting
		 * of this lock because a GRANT was in
		 * progress. This could be a client
		 * retrying a blocked lock due to
		 * mis-trust of server. If the client
		 * also accepts the GRANT_MSG with a
		 * GRANT_RESP, that will be just fine.
		 */
		LogEntry("Immediate grant for", found_entry);
		LOCK_AUTO_TRACEPOINT(found_entry, immediate_grant1, TRACE_INFO,
				     "Immediate grant");
		grant_blocked_lock_immediate(obj->state_hdl, found_entry);
	} else if (found_entry->sle_blocked == STATE_AVAILABLE &&
		   found_entry->sle_protocol == LOCK_NFSv4) {
		/* Nfsv4 locks can only be granted by an exact match */
		if (different_lock(&found_entry->sle_lock, lock)) {
			return;
		} else {
			LOCK_AUTO_TRACEPOINT(found_entry, immediate_grant2,
					     TRACE_INFO, "Immediate grant");
			LogEntry("Immediate grant for", found_entry);
			grant_nfsv4_blocking_lock(obj, found_entry);
		}
	} else if ((found_entry->sle_blocked == STATE_BLOCKING) &&
		   (found_entry->sle_protocol == LOCK_NFSv4) &&
		   (blocking == STATE_NON_BLOCKING) &&
		   (!different_lock(&found_entry->sle_lock, lock))) {
		/* In Nfsv4 when client sends same lock but changes from
		 * blocking to non blocking we need to remove the lock from
		 * non blocking list, and return the lock result */

		LOCK_AUTO_TRACEPOINT(found_entry, cancel_blocked, TRACE_INFO,
				     "cancel blocked lock");
		LogEntry("cancel blocked lock", found_entry);
		/* Cancel the blocked lock */
		cancel_blocked_lock(obj, found_entry);

		/* Check to see if we can grant any blocked locks. */
		grant_blocked_locks(obj->state_hdl);
		*lock_cancel = true;
		return;
	} else if (found_entry->sle_blocked != STATE_NON_BLOCKING) {
		return;
	}
	/* Found an entry that entirely overlaps the new entry
	 * (and due to the preceding test does not prevent
	 * granting this lock - therefore there can't be any
	 * other locks that would prevent granting this lock
	 */
	*lock_granted = true;

	LogEntry("Found existing", found_entry);

	if (found_entry->sle_state != state) {
		state_t *old_state = NULL;

		LogFullDebug(COMPONENT_STATE,
			     "Existing lock entry has old state");

		old_state = found_entry->sle_state;
		found_entry->sle_state = state;
		inc_state_t_ref(state);

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

		glist_add_tail(&state->state_data.lock.state_locklist,
			       &found_entry->sle_state_locks);

		LogEntry("sle after state change", found_entry);
	}
}

/**
 * @brief Attempt to acquire a lock
 *
 * Must hold the st_lock
 *
 * @param[in]     obj        File to lock
 * @param[in]     owner      Lock owner
 * @param[in]     state      Associated state for the lock
 * @param[in]     blocking   Blocking type
 * @param[in,out] bdata      Blocking lock data
 * @param[in]     lock       Lock description
 * @param[out]    holder     Holder of conflicting lock
 * @param[out]    conflict   Conflicting lock description
 *
 * @return State status.
 */
state_status_t state_lock(struct fsal_obj_handle *obj, state_owner_t *owner,
			  state_t *state, state_blocking_t blocking,
			  lock_protocol_t protocol, state_block_data_t **bdata,
			  fsal_lock_param_t *lock, state_owner_t **holder,
			  fsal_lock_param_t *conflict)
{
	bool allow = true, overlap = false;
	struct glist_head *glist;
	struct glist_head *glist_n;
	state_lock_entry_t *found_entry;
	state_lock_entry_t *new_entry;
	uint64_t found_entry_end;
	uint64_t range_end = lock_end(lock);
	struct fsal_export *fsal_export = op_ctx->fsal_export;
	fsal_lock_op_t lock_op;
	state_status_t status = 0;
	bool async;
	state_block_data_t *block_data;
	bool lock_granted;
	bool lock_cancel;

	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, lock_request_start, TRACE_INFO,
				      "lock request started");
	if (blocking != STATE_NON_BLOCKING) {
		/* First search for a blocked request. Client can ignore the
		 * blocked request and keep sending us new lock request again
		 * and again. So if we have a mapping blocked request return
		 * that
		 */
		glist_for_each(glist, &obj->state_hdl->file.lock_list) {
			found_entry = glist_entry(glist, state_lock_entry_t,
						  sle_list);

			if (different_owners(found_entry->sle_owner, owner))
				continue;

			/* Need to reject lock request if this lock owner
			 * already has a lock on this file via a different
			 * export.
			 */
			if (found_entry->sle_export != op_ctx->ctx_export) {
				struct tmp_export_paths tmp = { NULL, NULL };

				tmp_get_exp_paths(&tmp,
						  found_entry->sle_export);

				LogEvent(
					COMPONENT_STATE,
					"Lock Owner Export Conflict, Lock held for export %d (%s), request for export %d (%s)",
					found_entry->sle_export->export_id,
					op_ctx_tmp_export_path(op_ctx, &tmp),
					op_ctx->ctx_export->export_id,
					op_ctx_export_path(op_ctx));
				LogEntry(
					"Found lock entry belonging to another export",
					found_entry);

				tmp_put_exp_paths(&tmp);

				status = STATE_INVALID_ARGUMENT;
				return status;
			}

			if (found_entry->sle_blocked != blocking)
				continue;

			if (different_lock(&found_entry->sle_lock, lock))
				continue;

			/* We have matched all attribute of the existing lock.
			 * Just return with blocked status. Client may be
			 * polling.
			 */
			LogEntry("Found blocked", found_entry);
			LOCK_AUTO_TRACEPOINT(found_entry, found_blocked,
					     TRACE_INFO, "Found blocked");
			status = STATE_LOCK_BLOCKED;
			return status;
		}
	}

recheck_for_conflicting_entries:
	glist_for_each_safe(glist, glist_n, &obj->state_hdl->file.lock_list) {
		found_entry = glist_entry(glist, state_lock_entry_t, sle_list);
		/* Need to reject lock request if this lock owner already has
		 * a lock on this file via a different export.
		 */
		if (found_entry->sle_export != op_ctx->ctx_export &&
		    !different_owners(found_entry->sle_owner, owner)) {
			struct tmp_export_paths tmp = { NULL, NULL };

			tmp_get_exp_paths(&tmp, found_entry->sle_export);

			LogEvent(
				COMPONENT_STATE,
				"Lock Owner Export Conflict, Lock held for export %d (%s), request for export %d (%s)",
				found_entry->sle_export->export_id,
				op_ctx_tmp_export_path(op_ctx, &tmp),
				op_ctx->ctx_export->export_id,
				op_ctx_export_path(op_ctx));

			LogEntry("Found lock entry belonging to another export",
				 found_entry);

			tmp_put_exp_paths(&tmp);

			status = STATE_INVALID_ARGUMENT;
			return status;
		}

		/* Don't skip blocked locks for fairness */
		found_entry_end = lock_end(&found_entry->sle_lock);

		if (!(lock->lock_reclaim) &&
		    (found_entry_end >= lock->lock_start) &&
		    (found_entry->sle_lock.lock_start <= range_end) && allow) {
			/* lock overlaps see if we can allow:
			 * allow if neither lock is exclusive or
			 * the owner is the same
			 */
			if ((found_entry->sle_lock.lock_type == FSAL_LOCK_W ||
			     lock->lock_type == FSAL_LOCK_W) &&
			    different_owners(found_entry->sle_owner, owner)) {
				/* Found a conflicting lock, break out of loop.
				 * Also indicate overlap hint.
				 */
				LOCK_AUTO_TRACEPOINT(found_entry, lock_conflict,
						     TRACE_INFO,
						     "Found conflict");
				LogEntry("Conflicts with", found_entry);
				LogList("Locks", obj,
					&obj->state_hdl->file.lock_list);
				state_owner_t *cf_own = found_entry->sle_owner;
				state_nfs4_owner_t *owner =
					&cf_own->so_owner.so_nfs4_owner;
				nfs_client_id_t *c_id = owner->so_clientrec;

				if ((atomic_fetch_uint32_t(
					    &num_of_curr_expired_clients)) &&
				    (cf_own->so_type >=
				     STATE_OPEN_OWNER_NFSV4) &&
				    c_id->marked_for_delayed_cleanup) {
					/* Release the state lock, to clean */
					STATELOCK_unlock(obj);
					reap_expired_client_list(c_id);
					/* Acquire back the state lock */
					STATELOCK_lock(obj);

					/* Continue to recheck for conflicts */
					goto recheck_for_conflicting_entries;
				}

				if (blocking != STATE_BLOCKING) {
					copy_conflict(found_entry, holder,
						      conflict);
				}
				allow = false;
				overlap = true;
				/* We must continue as the request can be an
				 * nfsv4 cancel request
				 */
				continue;
			}
		}

		handle_contained_range_if_present(obj, owner, state, blocking,
						  lock, found_entry,
						  found_entry_end, range_end,
						  &overlap, &lock_granted,
						  &lock_cancel);

		if (lock_granted) {
			status = STATE_SUCCESS;
			return status;
		}
		if (lock_cancel) {
			status = STATE_LOCK_BLOCKED;
			return status;
		}
	}

	/* Decide how to proceed */
	if (blocking == STATE_BLOCKING) {
		/* do_lock_op will handle FSAL_OP_LOCKB for those FSALs that
		 * do not support async blocking locks. It will make a
		 * non-blocking call in that case, and it will return
		 * STATE_LOCK_BLOCKED if the lock was not granted.
		 */
		lock_op = FSAL_OP_LOCKB;
	} else if (allow) {
		/* No conflict found in Ganesha, and this is not a blocking
		 * request.
		 */
		lock_op = FSAL_OP_LOCK;
	} else {
		/* This is not a blocking lock and has a conflict.
		 * Return it.
		 */
		status = STATE_LOCK_CONFLICT;
		return status;
	}

	/* We have already returned if:
	 * + we have found an identical blocking lock
	 * + we have found an entirely overlapping lock with the same lock owner
	 * + this was not a supported blocking lock and we found a conflict
	 *
	 * So at this point, we are either going to do one of the following (all
	 * descriptions below assume no problems occur):
	 *
	 * (1) FSAL supports async blocking locks, we know there is a conflict,
	 (     and this is a supported blocking lock request
	 *
	 *     Make FSAL_OP_LOCKB call anyway, we will rely on FSAL to grant
	 *     blocking locks. We will return the conflict we know about rather
	 *     than what the FSAL returns. Insert blocking lock into queue.
	 *
	 * (2) FSAL supports async blocking locks, we don't know about any
	 *     conflict, and this is a supported blocking lock request
	 *
	 *     Make FSAL_OP_LOCKB call, if it indicates block, insert blocking
	 *     lock into queue, and return the conflict the FSAL indicates. If
	 *     FSAL grants lock, then return granted lock and insert into lock
	 *     list, otherwise insert blocking lock into queue.
	 *
	 * (3) FSAL doesn't support async blocking locks, this is a supported
	 *     blocking lock and we know there is a conflict
	 *
	 *     Insert blocking lock into queue, we will grant lock when
	 *     possible.
	 *
	 * (4) FSAL doesn't support async blocking locks and we don't know
	 *     about any conflict
	 *
	 *     Make FSAL_OP_LOCK call, if it indicates conflict, return that.
	 *     Even if this is a supported blocking lock call, there is no way
	 *     to block. If lock is granted, return that and insert lock into
	 *     list.
	 *
	 * (5) FSAL supports async blocking locks, we don't know about any
	 *     conflict, and this is not a supported blocking lock request
	 *
	 *     Make FSAL_OP_LOCK call, if it indicates conflict, return that.
	 *     If lock is granted, return that and insert lock into list.
	 */

	/* Create the new lock entry.
	 * Provisionally mark this lock as granted.
	 */
	new_entry = create_state_lock_entry(obj, op_ctx->ctx_export,
					    STATE_NON_BLOCKING, protocol, owner,
					    state, lock);

	/* Check for async blocking lock support. */
	async = fsal_export->exp_ops.fs_supports(fsal_export,
						 fso_lock_support_async_block);

	/* If no conflict in lock list, or FSAL supports async blocking locks,
	 * make FSAL call. Don't ask for conflict if we know about a conflict.
	 */
	if (allow || async) {
		/* Prepare to make call to FSAL for this lock */
		status = do_lock_op(obj, state, lock_op, owner, lock,
				    allow ? holder : NULL,
				    allow ? conflict : NULL, overlap);
	} else {
		/* FSAL does not support async blocking locks and we have
		 * a blocking lock within Ganesha, no need to make an
		 * FSAL call that will just return denied.
		 */
		status = STATE_LOCK_BLOCKED;
	}
	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, lock_request, TRACE_INFO,
				      "lock request ended with status {}",
				      status);

	if (status == STATE_SUCCESS) {
		/* Merge any touching or overlapping locks into this one */
		LogEntry("FSAL lock acquired, merging locks for", new_entry);

		merge_lock_entry(obj->state_hdl, new_entry);

		/* Insert entry into lock list */
		LogEntry("New lock", new_entry);

		glist_add_tail(&obj->state_hdl->file.lock_list,
			       &new_entry->sle_list);

		/* A lock downgrade could unblock blocked locks */
		grant_blocked_locks(obj->state_hdl);
	} else if (status == STATE_LOCK_CONFLICT) {
		LogEntry("Conflict in FSAL for", new_entry);

		/* Discard lock entry */
		remove_from_locklist(new_entry);
	} else if (status == STATE_LOCK_BLOCKED) {
		/* We are going to use the bdata, set it to NULL so that
		 * the caller doesn't free it!
		 */
		block_data = *bdata;
		*bdata = NULL;

		/* Mark entry as blocking and attach block_data */
		new_entry->sle_block_data = block_data;
		new_entry->sle_blocked = blocking;
		block_data->sbd_lock_entry = new_entry;
		if (async) {
			/* Allow FSAL to signal when lock is granted or
			 * available for retry.
			 */
			block_data->sbd_block_type = STATE_BLOCK_ASYNC;
		} else if (allow) {
			/* Actively poll for the lock only for nlm locks. */
			block_data->sbd_block_type = (protocol == LOCK_NLM)
							     ? STATE_BLOCK_ASYNC
							     : STATE_BLOCK_POLL;
		} else {
			/* Ganesha will attempt to grant the lock when
			 * a conflicting lock is released.
			 */
			block_data->sbd_block_type = STATE_BLOCK_INTERNAL;
		}

		/* Insert entry into lock list */
		LogEntry("FSAL block for", new_entry);

		glist_add_tail(&obj->state_hdl->file.lock_list,
			       &new_entry->sle_list);

		PTHREAD_MUTEX_lock(&blocked_locks_mutex);

		glist_add_tail(&state_blocked_locks, &block_data->sbd_list);

		PTHREAD_MUTEX_unlock(&blocked_locks_mutex);
	} else {
		if (status == STATE_ESTALE)
			LogDebug(COMPONENT_STATE,
				 "Unable to lock FSAL, error=%s",
				 state_err_str(status));
		else
			LogMajor(COMPONENT_STATE,
				 "Unable to lock FSAL, error=%s",
				 state_err_str(status));

		/* Discard lock entry */
		remove_from_locklist(new_entry);
	}

	return status;
}

/* Like state_unlock- but assumes obj state lock is taken */
static state_status_t state_unlock_internal(
	struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner,
	bool state_applies, int32_t nsm_state, fsal_lock_param_t *lock)
{
	bool empty = false;
	bool removed = false;
	state_status_t status = 0;

	if (obj->type != REGULAR_FILE) {
		LogLock(COMPONENT_STATE, NIV_DEBUG, "Bad Unlock", obj, owner,
			lock);
		return STATE_BAD_TYPE;
	}

	/* If lock list is empty, there really isn't any work for us to do. */
	if (glist_empty(&obj->state_hdl->file.lock_list)) {
		LogDebug(COMPONENT_STATE,
			 "Unlock success on file with no locks");
		goto unlock_fsal;
	}

	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, unlock_request_start,
				      TRACE_INFO, "unlock request started");

	LogFullDebug(
		COMPONENT_STATE,
		"----------------------------------------------------------------------");
	LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "Subtracting", obj, owner,
		lock);
	LogFullDebug(
		COMPONENT_STATE,
		"----------------------------------------------------------------------");

	/* First cancel any blocking locks that might
	 * overlap the unlocked range.
	 */
	cancel_blocked_locks_range(obj->state_hdl, owner, state_applies,
				   nsm_state, lock);

	/* Release the lock from lock list for entry */
	status = subtract_lock_from_list(owner, state_applies, nsm_state, lock,
					 &removed,
					 &obj->state_hdl->file.lock_list);

	if (status != STATE_SUCCESS) {
		/* The unlock has not taken affect (other than canceling any
		 * blocking locks.
		 */
		LogMajor(COMPONENT_STATE,
			 "Unable to remove lock from list for unlock, error=%s",
			 state_err_str(status));

		goto out_unlock;
	}

unlock_fsal:
	/* Unlocking the entire region will remove any FSAL locks we held,
	 * whether from fully granted locks, or from blocking locks that were
	 * in the process of being granted.
	 */
	status = do_lock_op(obj, state, FSAL_OP_UNLOCK, owner, lock,
			    NULL, /* no conflict expected */
			    NULL, false);

	LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, unlock_request_end, TRACE_INFO,
				      "lock request started {}", status);
	if (status != STATE_SUCCESS)
		LogMajor(COMPONENT_STATE, "Unable to unlock FSAL, error=%s",
			 state_err_str(status));

	LogFullDebug(
		COMPONENT_STATE,
		"----------------------------------------------------------------------");
	LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "Done", obj, owner, lock);
	LogFullDebug(
		COMPONENT_STATE,
		"----------------------------------------------------------------------");

	if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS) &&
	    lock->lock_start == 0 && lock->lock_length == 0)
		empty = LogList("Lock List", obj,
				&obj->state_hdl->file.lock_list);

	grant_blocked_locks(obj->state_hdl);

	if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS) &&
	    lock->lock_start == 0 && lock->lock_length == 0 && empty)
		dump_all_locks("All locks (after unlock)");

out_unlock:
	return status;
}

/**
 * @brief Release a lock
 *
 * @param[in] obj      File to unlock
 * @param[in] state         Associated state_t (if any)
 * @param[in] owner         Owner of lock
 * @param[in] state_applies Indicator if nsm_state is relevant
 * @param[in] nsm_state     NSM state number
 * @param[in] lock          Lock description
 */
state_status_t state_unlock(struct fsal_obj_handle *obj, state_t *state,
			    state_owner_t *owner, bool state_applies,
			    int32_t nsm_state, fsal_lock_param_t *lock)
{
	state_status_t status;

	STATELOCK_lock(obj);

	status = state_unlock_internal(obj, state, owner, state_applies,
				       nsm_state, lock);

	STATELOCK_unlock(obj);

	return status;
}

/**
 * @brief Release all locks in a state
 * Assumes obj state lock is taken
 *
 * @param[in] obj      File to unlock
 * @param[in] state      The state
 */
void state_unlock_all(struct fsal_obj_handle *obj, state_t *state)
{
	/* The state lock associated with the state */
	struct state_lock *locks;
	/* Specific lock entry */
	state_lock_entry_t *lock_entry;
	state_status_t status = 0;

	if (state->state_type != STATE_TYPE_LOCK) {
		/* Only relevant for lock state */
		return;
	}
	locks = &state->state_data.lock;

	while (true) {
		PTHREAD_MUTEX_lock(&state->state_mutex);

		lock_entry = glist_first_entry(&locks->state_locklist,
					       struct state_lock_entry_t,
					       sle_state_locks);

		if (lock_entry == NULL) {
			PTHREAD_MUTEX_unlock(&state->state_mutex);
			break;
		}

		lock_entry_inc_ref(lock_entry);

		/* Move this state to the end of the list
		 * (this will help if errors occur)
		 */
		glist_move_tail(&locks->state_locklist,
				&lock_entry->sle_state_locks);

		PTHREAD_MUTEX_unlock(&state->state_mutex);

		status = state_unlock_internal(state->state_obj, state,
					       state->state_owner, false, 0,
					       &lock_entry->sle_lock);

		lock_entry_dec_ref(lock_entry);

		if (status != STATE_SUCCESS) {
			LogCrit(COMPONENT_STATE,
				"Failed to unlock lock in a state. This will be retried (status = %s)",
				state_err_str(status));
			continue;
		}
	}
}

/**
 * @brief Cancel a blocking lock
 *
 * @param[in] obj    File on which to cancel the lock
 * @param[in] owner  Lock owner
 * @param[in] lock   Lock description
 *
 * @return State status.
 */
state_status_t state_cancel(struct fsal_obj_handle *obj, state_owner_t *owner,
			    fsal_lock_param_t *lock)
{
	if (obj->type != REGULAR_FILE) {
		LogLock(COMPONENT_STATE, NIV_DEBUG, "Bad Cancel", obj, owner,
			lock);
		return STATE_BAD_TYPE;
	}

	STATELOCK_lock(obj);

	state_cancel_internal(obj, owner, lock);

	STATELOCK_unlock(obj);

	return STATE_SUCCESS;
}

/**
 * @brief Release a lock if blocked
 *
 * @param[in] lock_entry lock entry to cancel
 */
state_status_t state_cancel_blocked(state_lock_entry_t *lock_entry)
{
	state_status_t status;

	STATELOCK_lock(lock_entry->sle_obj);
	switch (lock_entry->sle_blocked) {
	case STATE_BLOCKING:
		state_cancel_internal(lock_entry->sle_obj,
				      lock_entry->sle_owner,
				      &lock_entry->sle_lock);
		status = STATE_SUCCESS;
		break;

	case STATE_AVAILABLE:
		switch (lock_entry->sle_protocol) {
		case LOCK_NFSv4:
			state_cancel_internal(lock_entry->sle_obj,
					      lock_entry->sle_owner,
					      &lock_entry->sle_lock);
			status = STATE_SUCCESS;
			break;

		case LOCK_NLM:
			status = STATE_LOCKED;
			break;

		default:
			LogFatal(COMPONENT_STATE, "Got an protocol type %s",
				 str_protocol(lock_entry->sle_protocol));
		}
		break;

	case STATE_NON_BLOCKING:
		status = STATE_LOCKED;
		break;

	case STATE_CANCELED:
		status = STATE_SUCCESS;
		break;

	default:
		LogFatal(COMPONENT_STATE, "Got an unexpected block type %s",
			 str_blocked(lock_entry->sle_blocked));
	}

	STATELOCK_unlock(lock_entry->sle_obj);

	return status;
}

#ifdef _USE_NLM
/**
 * @brief Handle an SM_NOTIFY from NLM
 *
 * Also used to handle NLM_FREE_ALL
 *
 * @param[in] nsmclient     NSM client data
 * @param[in] state_applies Indicates if nsm_state is valid
 * @param[in] nsm_state     NSM state value
 *
 * @return State status.
 */
state_status_t state_nlm_notify(state_nsm_client_t *nsmclient,
				bool state_applies, int32_t nsm_state)
{
	state_owner_t *owner;
	state_lock_entry_t *found_entry;
	fsal_lock_param_t lock;
	struct fsal_obj_handle *obj;
	int errcnt = 0;
	struct glist_head newlocks;
	state_t *found_share;
	state_status_t status = 0;
	struct req_op_context op_context;
	struct gsh_export *export;
	state_t *state;

	/* Initialize a context */
	init_op_context_simple(&op_context, NULL, NULL);

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

		display_nsm_client(&dspbuf, nsmclient);

		LogFullDebug(COMPONENT_STATE, "Notify for %s", str);
	}

	glist_init(&newlocks);

	/* First remove byte range locks.
	 * Only accept so many errors before giving up.
	 */
	while (errcnt < STATE_ERR_MAX) {
		PTHREAD_MUTEX_lock(&nsmclient->ssc_mutex);

		/* We just need to find any file this client has locks on.
		 * We pick the first lock the client holds, and use it's file.
		 */
		found_entry = glist_first_entry(&nsmclient->ssc_lock_list,
						state_lock_entry_t,
						sle_client_locks);

		/* If we don't find any entries, then we are done. */
		if (found_entry == NULL) {
			PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);
			break;
		}

		/* Remove from the client lock list */
		glist_del(&found_entry->sle_client_locks);

		/* If called as a result of SM_NOTIFY, we don't drop the
		 * locks belonging to the "current" NSM state counter passed
		 * in the SM_NOTIFTY. Otherwise, we drop all locks.
		 * We expect never to get an SM_NOTIFY from a client that uses
		 * non-monitored locks (it will send a FREE_ALL instead (which
		 * will not set the state_applies flag).
		 */
		if (state_applies &&
		    found_entry->sle_state->state_seqid == nsm_state) {
			/* This is a new lock acquired since the client
			 * rebooted, retain it.
			 *
			 * Note that although this entry is temporarily
			 * not on the ssc_lock_list, we don't need to hold
			 * an extra reference to the entry since the list
			 * still effectively protected by the ssc_mutex.
			 * If something happens to remove this entry, it will
			 * actually be removed from the newlocks list, which
			 * in the end is correct. So we don't need to do
			 * anything special.
			 */
			/** @todo FSF: vulnerability - if a SECOND SM_NOTIFY
			 * comes in while this one is still being processed
			 * the locks put on the newlocks list would not be
			 * cleaned up. This list REALLY should be attached
			 * to the nsm_client in some way, or we should block
			 * handling of the 2nd SM_NOTIFY until this one is
			 * complete.
			 */
			LogEntry("Don't release new lock", found_entry);
			glist_add_tail(&newlocks,
				       &found_entry->sle_client_locks);
			PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);
			continue;
		}

		LogEntry("Release client locks based on", found_entry);

		/* Extract the bits from the lock entry that we will need
		 * to proceed with the operation (file, owner, and
		 * export).
		 */
		obj = found_entry->sle_obj;
		owner = found_entry->sle_owner;
		export = found_entry->sle_export;
		state = found_entry->sle_state;

		/* Get a reference to the export while we still hold the
		 * ssc_mutex. This assures that the export definitely can
		 * not have had it's last refcount released. We will check
		 * later to see if the export is being removed.
		 */
		get_gsh_export_ref(export);
		set_op_context_export(export);

		/* Get a reference to the owner */
		inc_state_owner_ref(owner);

		/* Get a reference to the state_t */
		inc_state_t_ref(state);

		/* Move this entry to the end of the list
		 * (this will help if errors occur)
		 */
		glist_add_tail(&nsmclient->ssc_lock_list,
			       &found_entry->sle_client_locks);

		/* Now we are done with this specific entry, release the mutex.
		 */
		PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);

		/* Make lock that covers the whole file.
		 * type doesn't matter for unlock
		 */
		lock.lock_type = FSAL_LOCK_R;
		lock.lock_start = 0;
		lock.lock_length = 0;

		if (export_ready(export)) {
			/* Remove all locks held by this NLM Client on
			 * the file.
			 */
			status = state_unlock(obj, state, owner, state_applies,
					      nsm_state, &lock);
		} else {
			/* The export is being removed, we didn't bother
			 * calling state_unlock() because export cleanup
			 * will remove all the state. This is assured by
			 * the call to put_gsh_export from
			 * clear_op_context_export. Pretend success.
			 */
			status = STATE_SUCCESS;
		}

		/* Release the refcounts we took above. */
		dec_state_owner_ref(owner);
		clear_op_context_export();

		if (!state_unlock_err_ok(status)) {
			/* Increment the error count and try the next lock,
			 * with any luck the memory pressure which is causing
			 * the problem will resolve itself.
			 */
			LogFullDebug(COMPONENT_STATE,
				     "state_unlock returned %s",
				     state_err_str(status));
		}
	}

	/* Now remove NLM_SHARE reservations.
	 * Only accept so many errors before giving up.
	 * We always drop all NLM_SHARE reservations since they are
	 * non-monitored.
	 */
	while (errcnt < STATE_ERR_MAX) {
		PTHREAD_MUTEX_lock(&nsmclient->ssc_mutex);

		/* We just need to find any file this client has locks on.
		 * We pick the first lock the client holds, and use it's file.
		 */
		found_share =
			glist_first_entry(&nsmclient->ssc_share_list, state_t,
					  state_data.nlm_share.share_perclient);

		/* If we don't find any entries, then we are done. */
		if (found_share == NULL) {
			PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);
			break;
		}

		/* Extract the bits from the lock entry that we will need
		 * to proceed with the operation (object, owner, and
		 * export).
		 */
		obj = get_state_obj_ref(found_share);

		if (obj == NULL) {
			LogDebug(COMPONENT_STATE, "Entry for state is stale");
			PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);
			break;
		}

		owner = found_share->state_owner;
		export = found_share->state_export;

		/* Get a reference to the export while we still hold the
		 * ssc_mutex. This assures that the export definitely can
		 * not have had it's last refcount released. We will check
		 * later to see if the export is being removed.
		 */
		get_gsh_export_ref(export);
		set_op_context_export(export);

		/* Get a reference to the owner */
		inc_state_owner_ref(owner);

		/* Get a reference to the state_t */
		inc_state_t_ref(found_share);

		/* Move this entry to the end of the list
		 * (this will help if errors occur)
		 */
		glist_move_tail(
			&nsmclient->ssc_share_list,
			&found_share->state_data.nlm_share.share_perclient);

		PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);

		if (export_ready(export)) {
			/* Remove all shares held by this NSM Client and
			 * Owner on the file (on all exports)
			 */
			status = state_nlm_share(obj, OPEN4_SHARE_ACCESS_ALL,
						 OPEN4_SHARE_DENY_ALL, owner,
						 found_share, false, true);
		} else {
			/* The export is being removed, we didn't bother
			 * calling state_unlock() because export cleanup
			 * will remove all the state. This is assured by
			 * the call to put_gsh_export from
			 * clear_op_context_export. Pretend success.
			 */
			status = STATE_SUCCESS;
		}

		/* Release the refcounts we took above. */
		dec_state_owner_ref(owner);
		obj->obj_ops->put_ref(obj);
		dec_state_t_ref(found_share);
		clear_op_context_export();

		if (!state_unlock_err_ok(status)) {
			/* Increment the error count and try the next share,
			 * with any luck the memory pressure which is causing
			 * the problem will resolve itself.
			 */
			LogFullDebug(COMPONENT_STATE,
				     "state_nlm_unshare returned %s",
				     state_err_str(status));
		}
	}

	if (errcnt == STATE_ERR_MAX) {
		char str[LOG_BUFF_LEN] = "\0";
		struct display_buffer dspbuf = { sizeof(str), str, str };

		display_nsm_client(&dspbuf, nsmclient);

		LogFatal(COMPONENT_STATE,
			 "Could not complete NLM notify for %s", str);
	}

	/* Put locks from current client incarnation onto end of list.
	 *
	 * Note that it's possible one or more of the locks we originally
	 * put on the newlocks list have been removed. This is actually
	 * just fine. Since everything is protected by the ssc_mutex,
	 * even the fact that the newlocks list head is a stack local
	 * variable is local will actually work just fine.
	 */
	PTHREAD_MUTEX_lock(&nsmclient->ssc_mutex);
	glist_add_list_tail(&nsmclient->ssc_lock_list, &newlocks);
	PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex);
	LogFullDebug(COMPONENT_STATE, "DONE");

	release_op_context();
	return status;
}
#endif /* _USE_NLM */

/**
 * @brief Release all locks held by an NFS v4 lock owner
 *
 * @param[in] owner Lock owner
 *
 */
void state_nfs4_owner_unlock_all(state_owner_t *owner)
{
	fsal_lock_param_t lock;
	struct fsal_obj_handle *obj;
	int errcnt = 0;
	state_status_t status = 0;
	struct saved_export_context saved;
	struct gsh_export *export;
	state_t *state;
	bool ok;

	/* Only accept so many errors before giving up. */
	while (errcnt < STATE_ERR_MAX) {
		PTHREAD_MUTEX_lock(&owner->so_mutex);

		/* We just need to find any file this owner has locks on.
		 * We pick the first lock the owner holds, and use it's file.
		 */
		state = glist_first_entry(
			&owner->so_owner.so_nfs4_owner.so_state_list, state_t,
			state_owner_list);

		/* If we don't find any entries, then we are done. */
		if (state == NULL) {
			PTHREAD_MUTEX_unlock(&owner->so_mutex);
			break;
		}

		inc_state_t_ref(state);

		/* Move this state to the end of the list
		 * (this will help if errors occur)
		 */
		glist_move_tail(&owner->so_owner.so_nfs4_owner.so_state_list,
				&state->state_owner_list);

		/* Get references to the obj and export */
		ok = get_state_obj_export_owner_refs(state, &obj, &export,
						     NULL);

		PTHREAD_MUTEX_unlock(&owner->so_mutex);

		if (!ok) {
			/* Entry and/or export is dying, skip this state,
			 * it will be cleaned up soon enough.
			 */
			continue;
		}

		/* Set up the op_context with the proper export */
		save_op_context_export_and_set_export(&saved, export);

		/* Make lock that covers the whole file.
		 * type doesn't matter for unlock
		 */
		lock.lock_type = FSAL_LOCK_R;
		lock.lock_start = 0;
		lock.lock_length = 0;

		/* Remove all locks held by this owner on the file */
		status = state_unlock(obj, state, owner, false, 0, &lock);

		if (!state_unlock_err_ok(status)) {
			/* Increment the error count and try the next lock,
			 * with any luck the memory pressure which is causing
			 * the problem will resolve itself.
			 */
			LogCrit(COMPONENT_STATE, "state_unlock failed %s",
				state_err_str(status));
			errcnt++;
		} else if (status == STATE_SUCCESS) {
			/* Delete the state_t */
			state_del(state);
		}

		dec_state_t_ref(state);

		/* Release the obj ref and export ref. */
		obj->obj_ops->put_ref(obj);
		restore_op_context_export(&saved);
	}

	if (errcnt == STATE_ERR_MAX) {
		char str[LOG_BUFF_LEN] = "\0";
		struct display_buffer dspbuf = { sizeof(str), str, str };

		display_owner(&dspbuf, owner);

		LogFatal(
			COMPONENT_STATE,
			"Could not complete cleanup of lock state for lock owner %s",
			str);
	}
}

/**
 * @brief Release all locks held on an export
 *
 */

void state_export_unlock_all(void)
{
	state_lock_entry_t *found_entry;
	fsal_lock_param_t lock;
	struct fsal_obj_handle *obj;
	int errcnt = 0;
	state_status_t status = 0;
	state_owner_t *owner;
	state_t *state;

	/* Only accept so many errors before giving up. */
	while (errcnt < STATE_ERR_MAX) {
		PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock);

		/* We just need to find any file this owner has locks on.
		 * We pick the first lock the owner holds, and use it's file.
		 */
		found_entry =
			glist_first_entry(&op_ctx->ctx_export->exp_lock_list,
					  state_lock_entry_t, sle_export_locks);

		/* If we don't find any entries, then we are done. */
		if (found_entry == NULL) {
			PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock);
			break;
		}

		/* Extract the bits from the lock entry that we will need
		 * to proceed with the operation (obj, owner, and
		 * export).
		 */
		obj = found_entry->sle_obj;
		owner = found_entry->sle_owner;
		state = found_entry->sle_state;

		/* take a reference on the state_t */
		inc_state_t_ref(state);

		/* Get a reference to the file object while we still hold
		 * the ssc_mutex (since we hold this mutex, any other function
		 * that might be cleaning up this lock CAN NOT have released
		 * the last LRU reference, thus it is safe to grab another. */
		obj->obj_ops->get_ref(obj);

		/* Get a reference to the owner */
		inc_state_owner_ref(owner);

		/* Move this entry to the end of the list
		 * (this will help if errors occur)
		 */
		glist_move_tail(&op_ctx->ctx_export->exp_lock_list,
				&found_entry->sle_export_locks);

		/* Now we are done with this specific entry, release the lock.
		 */
		PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock);

		/* Make lock that covers the whole file.
		 * type doesn't matter for unlock
		 */
		lock.lock_type = FSAL_LOCK_R;
		lock.lock_start = 0;
		lock.lock_length = 0;

		/* Remove all locks held by this NLM Client on
		 * the file.
		 */
		status = state_unlock(obj, state, owner, false, 0, &lock);

		/* call state_del regardless of status as we're killing
		 * the export
		 */
		if (owner->so_type == STATE_LOCK_OWNER_NFSV4)
			state_del(state);

		/* Release the refcounts we took above. */
		dec_state_t_ref(state);
		dec_state_owner_ref(owner);
		obj->obj_ops->put_ref(obj);

		if (!state_unlock_err_ok(status)) {
			/* Increment the error count and try the next lock,
			 * with any luck the memory pressure which is causing
			 * the problem will resolve itself.
			 */
			LogDebug(COMPONENT_STATE, "state_unlock failed %s",
				 state_err_str(status));
		}
	}

	if (errcnt == STATE_ERR_MAX) {
		LogFatal(COMPONENT_STATE,
			 "Could not complete cleanup of locks for %s",
			 op_ctx_export_path(op_ctx));
	}
}

/**
 * @brief handle nlm lock polling logic
 *
 * @param[in] pblock lock block data
 * @param[in] found_entry entry to poll
 */

static void handle_nlm_lock(state_block_data_t *pblock,
			    state_lock_entry_t *found_entry)
{
	/* Check if right type */
	if (pblock->sbd_block_type != STATE_BLOCK_POLL)
		return;

	/* Schedule async processing, leave the lock on the blocked
	 * lock list since we might not succeed in granting this lock.
	 */
	pblock->sbd_grant_type = STATE_GRANT_POLL;

	/* Since we're scheduling this block to be handled in
	 * another thread, we need to hold a reference on the
	 * lock entry to prevent the entry from release before
	 * we are done processing.
	 */
	lock_entry_inc_ref(found_entry);

	if (state_block_schedule(found_entry) != STATE_SUCCESS) {
		LogMajor(COMPONENT_STATE,
			 "Unable to schedule lock notification.");
		/* scheduling failed, release the reference to lock entry */
		lock_entry_dec_ref(found_entry);
	}

	LogEntry("Blocked Lock found", found_entry);
}

/* It looks like when we report OPEN4_RESULT_MAY_NOTIFY_LOCK, the client polls
 * exactly every lease time. This creates a race where the poll request arrives
 * after we cancel the lock. For this reason, we add a small time buffer.
 */
#define POLL_TIME_CANCEL_BUFFER_SEC (5)

/**
 * @brief handle nfsv4 lock polling logic
 *
 * @param[in] pblock lock block data
 * @param[in] found_entry entry to poll
 * @param[in] check_time time we performed the poll
 */

static void handle_nfsv4_lock(state_block_data_t *pblock,
			      state_lock_entry_t *found_entry,
			      time_t check_time)
{
	assert(found_entry->sle_protocol == LOCK_NFSv4);
	if (found_entry->sle_blocked == STATE_AVAILABLE) {
		const uint64_t expire_time =
			pblock->sbd_prot.sbd_v4.snbd_notified_eligible_time +
			nfs_param.nfsv4_param.lease_lifetime +
			POLL_TIME_CANCEL_BUFFER_SEC;

		if (check_time < expire_time) {
			LOCK_AUTO_TRACEPOINT(
				found_entry, lock_poll_unexpired, TRACE_INFO,
				"lock not expired check_time {} expire {}",
				check_time, expire_time);
			return;
		}
		LOCK_AUTO_TRACEPOINT(found_entry, lock_poll_expired, TRACE_INFO,
				     "lock not expired check_time {} expire {}",
				     check_time, expire_time);

		/* Since we are scheduling this lock to be unlocked in
		* another thread, we need to hold a reference on the
		* lock entry to prevent the entry from being released
		* before we are done processing.
		*/
		lock_entry_inc_ref(found_entry);

		if (state_block_cancel_schedule(found_entry) != STATE_SUCCESS) {
			LogMajor(COMPONENT_STATE, "Unable to schedule cancel.");
			/* scheduling failed, release the reference to lock
			* entry */
			lock_entry_dec_ref(found_entry);
		}
	} else {
		/* We set minimum time between polls to be twice the lease time,
		 * but this is just an internal parameter
		 */
		const uint32_t min_time_between_polls =
			2 * nfs_param.nfsv4_param.lease_lifetime;
		const uint64_t next_poll_time =
			pblock->sbd_prot.sbd_v4.snbd_last_poll_time +
			min_time_between_polls;
		if (check_time < next_poll_time) {
			LOCK_AUTO_TRACEPOINT(
				found_entry, lock_poll_not_check, TRACE_INFO,
				"poll eligibility check_time {} next_poll_time {}",
				check_time, next_poll_time);
			return;
		}
		LOCK_AUTO_TRACEPOINT(
			found_entry, lock_poll_check, TRACE_INFO,
			"poll eligibility check_time {} next_poll_time {}",
			check_time, next_poll_time);
		pblock->sbd_prot.sbd_v4.snbd_last_poll_time = time(NULL);
		lock_entry_inc_ref(found_entry);
		if (test_blocking_lock_eligibility_schedule(found_entry) !=
		    STATE_SUCCESS) {
			LogMajor(COMPONENT_STATE,
				 "Unable to schedule lock elgibility test.");
			lock_entry_dec_ref(found_entry);
		}
	}
}

/**
 * @brief Poll any blocked locks of type STATE_BLOCK_POLL
 *
 * @param[in] ctx Fridge Thread Context
 *
 */

void blocked_lock_polling(struct fridgethr_context *ctx)
{
	state_lock_entry_t *found_entry;
	struct glist_head *glist;
	state_block_data_t *pblock;

	SetNameFunction("lk_poll");
	PTHREAD_MUTEX_lock(&blocked_locks_mutex);
	time_t check_time = time(NULL);

	if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS))
		LogBlockedList("Blocked Lock List", NULL, &state_blocked_locks);

	glist_for_each(glist, &state_blocked_locks) {
		pblock = glist_entry(glist, state_block_data_t, sbd_list);

		found_entry = pblock->sbd_lock_entry;

		/* Check if got an entry */
		if (found_entry == NULL)
			continue;

		/* Check if this is a non polled nfsv4 lock*/
		if (found_entry->sle_protocol == LOCK_NFSv4)
			handle_nfsv4_lock(pblock, found_entry, check_time);
		else
			handle_nlm_lock(pblock, found_entry);

	} /* glist_for_each_safe */

	PTHREAD_MUTEX_unlock(&blocked_locks_mutex);
}

/**
 * @brief Find a lock and add to grant list
 *
 * @param[in] obj      File to search
 * @param[in] owner      Lock owner
 * @param[in] lock       Lock description
 * @param[in] grant_type Grant type
 */
static void find_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner,
				     fsal_lock_param_t *lock,
				     state_grant_type_t grant_type)
{
	state_lock_entry_t *found_entry;
	struct glist_head *glist;
	state_block_data_t *pblock;

	PTHREAD_MUTEX_lock(&blocked_locks_mutex);

	glist_for_each(glist, &state_blocked_locks) {
		pblock = glist_entry(glist, state_block_data_t, sbd_list);

		found_entry = pblock->sbd_lock_entry;

		/* Check if got an entry */
		if (found_entry == NULL)
			continue;

		/* Check if for same file */
		if (found_entry->sle_obj != obj)
			continue;

		/* Check if for same owner */
		if (found_entry->sle_owner != owner)
			continue;

		/* Check if same lock */
		if (different_lock(&found_entry->sle_lock, lock))
			continue;

		/* Schedule async processing, leave the lock on the blocked
		 * lock list for now (if we get multiple upcalls because our
		 * async processing is slow for some reason, we don't want to
		 * not find this lock entry and hit the LogFatal below...
		 */
		pblock->sbd_grant_type = grant_type;

		/* Since we are scheduling this block to be handled in
		 * another thread, we need to hold a reference on the
		 * lock entry to prevent the entry from releasing before
		 * we are done processing.
		 */
		lock_entry_inc_ref(found_entry);

		if (state_block_schedule(found_entry) != STATE_SUCCESS) {
			LogMajor(COMPONENT_STATE,
				 "Unable to schedule lock notification.");
			/* scheduling failed, release the reference to lock
			 * entry */
			lock_entry_dec_ref(found_entry);
		}

		LogEntry("Blocked Lock found", found_entry);

		PTHREAD_MUTEX_unlock(&blocked_locks_mutex);

		return;
	} /* glist_for_each_safe */

	if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS))
		LogBlockedList("Blocked Lock List", NULL, &state_blocked_locks);

	PTHREAD_MUTEX_unlock(&blocked_locks_mutex);

	if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS)) {
		STATELOCK_lock(obj);

		LogList("File Lock List", obj, &obj->state_hdl->file.lock_list);

		STATELOCK_unlock(obj);
	}

	/* It is likely that we got an upcall before the cancel request.
	 * The cancel code will also unlock, so there is nothing to do
	 * even if this is a granted lock.
	 */
	LogLockDesc(COMPONENT_STATE, NIV_EVENT, "Blocked Lock Not Found for",
		    obj, owner, lock);
}

/**
 * @brief Handle upcall for granted lock
 *
 * @param[in] obj File on which lock is granted
 * @param[in] owner Lock owner
 * @param[in] lock  Lock description
 */
void grant_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner,
			       fsal_lock_param_t *lock)
{
	LogLockDesc(COMPONENT_STATE, NIV_DEBUG, "Grant Upcall for", obj, owner,
		    lock);

	find_blocked_lock_upcall(obj, owner, lock, STATE_GRANT_FSAL);
}

/**
 * @brief Handle upcall for available lock
 *
 * @param[in] obj File on which lock has become available
 * @param[in] owner Lock owner
 * @param[in] lock  Lock description
 */
void available_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner,
				   fsal_lock_param_t *lock)
{
	LogLockDesc(COMPONENT_STATE, NIV_DEBUG, "Available Upcall for", obj,
		    owner, lock);

	find_blocked_lock_upcall(obj, owner, lock, STATE_GRANT_FSAL_AVAILABLE);
}

/**
 * @brief Free all locks on a file
 *
 * @param[in] obj File to free
 */
void state_lock_wipe(struct state_hdl *hstate)
{
	if (glist_empty(&hstate->file.lock_list))
		return;

	free_list(&hstate->file.lock_list);
}

void cancel_all_nlm_blocked(void)
{
	state_lock_entry_t *found_entry;
	state_block_data_t *pblock;
	struct req_op_context op_context;

	/* Initialize context */
	init_op_context(&op_context, NULL, NULL, NULL, NULL, 0, 0, NFS_RELATED);

	LogDebug(COMPONENT_STATE, "Cancel all blocked locks");

	PTHREAD_MUTEX_lock(&blocked_locks_mutex);

	pblock = glist_first_entry(&state_blocked_locks, state_block_data_t,
				   sbd_list);

	if (pblock == NULL) {
		LogFullDebug(COMPONENT_STATE, "No blocked locks");
		goto out;
	}

	while (pblock != NULL) {
		found_entry = pblock->sbd_lock_entry;

		/* Remove lock from blocked list */
		glist_del(&pblock->sbd_list);

		lock_entry_inc_ref(found_entry);

		PTHREAD_MUTEX_unlock(&blocked_locks_mutex);

		get_gsh_export_ref(found_entry->sle_export);
		set_op_context_export(found_entry->sle_export);

		/** @todo also look at the LRU ref for pentry */

		LogEntry("Blocked Lock found", found_entry);

		STATELOCK_lock(found_entry->sle_obj);

		cancel_blocked_lock(found_entry->sle_obj, found_entry);

		STATELOCK_unlock(found_entry->sle_obj);

		if (pblock->sbd_blocked_cookie != NULL) {
			gsh_free(pblock->sbd_blocked_cookie);
			pblock->sbd_blocked_cookie = NULL;
		}

		if (found_entry->sle_block_data != NULL) {
			gsh_free(found_entry->sle_block_data);
			found_entry->sle_block_data = NULL;
		}

		LogEntry("Canceled Lock", found_entry);

		lock_entry_dec_ref(found_entry);

		clear_op_context_export();

		PTHREAD_MUTEX_lock(&blocked_locks_mutex);

		/* Get next item off list */
		pblock = glist_first_entry(&state_blocked_locks,
					   state_block_data_t, sbd_list);
	}

out:

	PTHREAD_MUTEX_unlock(&blocked_locks_mutex);
	release_op_context();
}

/* Cleanup on shutdown */
void state_cleanup(void)
{
	PTHREAD_MUTEX_destroy(&unknown_owner.so_mutex);
#ifdef DEBUG_SAL
	PTHREAD_MUTEX_destroy(&all_locks_mutex);
#endif
	PTHREAD_MUTEX_destroy(&blocked_locks_mutex);
	PTHREAD_MUTEX_destroy(&cached_open_owners_lock);
#ifdef _USE_NLM
	PTHREAD_MUTEX_destroy(&granted_mutex);
	PTHREAD_MUTEX_destroy(&nlm_async_resp_mutex);
	PTHREAD_COND_destroy(&nlm_async_resp_cond);
	PTHREAD_MUTEX_destroy(&nsm_mutex);
#endif
#ifdef DEBUG_SAL
	PTHREAD_MUTEX_destroy(&all_state_owners_mutex);
	PTHREAD_MUTEX_destroy(&all_state_v4_mutex);
#endif
}

struct cleanup_list_element state_cleanup_element = {
	.clean = state_cleanup,
};

/**
 * @brief Initialize locking
 *
 * @return State status.
 */
state_status_t state_lock_init(void)
{
	state_status_t status = STATE_SUCCESS;

	ht_lock_cookies = hashtable_init(&cookie_param);
	if (ht_lock_cookies == NULL) {
		LogCrit(COMPONENT_STATE, "Cannot init NLM Client cache");
		status = STATE_INIT_ENTRY_FAILED;
		return status;
	}

	PTHREAD_MUTEX_init(&unknown_owner.so_mutex, NULL);
#ifdef DEBUG_SAL
	PTHREAD_MUTEX_init(&all_locks_mutex, NULL);
#endif
	PTHREAD_MUTEX_init(&blocked_locks_mutex, NULL);
	PTHREAD_MUTEX_init(&cached_open_owners_lock, NULL);
#ifdef _USE_NLM
	PTHREAD_MUTEX_init(&granted_mutex, NULL);
	PTHREAD_MUTEX_init(&nlm_async_resp_mutex, NULL);
	PTHREAD_COND_init(&nlm_async_resp_cond, NULL);
	PTHREAD_MUTEX_init(&nsm_mutex, NULL);
#endif
#ifdef DEBUG_SAL
	PTHREAD_MUTEX_init(&all_state_owners_mutex, NULL);
	PTHREAD_MUTEX_init(&all_state_v4_mutex, NULL);
#endif

	RegisterCleanup(&state_cleanup_element);

	status = state_async_init();

	state_owner_pool =
		pool_basic_init("NFSv4 state owners", sizeof(state_owner_t));

	return status;
}

/** @} */
