| /* |
| * 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_misc.c |
| * @brief Misc exported routines for the state abstraction layer |
| */ |
| |
| #include "config.h" |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <time.h> |
| #include <pthread.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <assert.h> |
| |
| #include "log.h" |
| #include "hashtable.h" |
| #include "fsal.h" |
| #include "nfs_core.h" |
| #include "sal_functions.h" |
| |
| struct glist_head cached_open_owners = GLIST_HEAD_INIT(cached_open_owners); |
| |
| pthread_mutex_t cached_open_owners_lock = PTHREAD_MUTEX_INITIALIZER; |
| |
| pool_t *state_owner_pool; /*< Pool for NFSv4 files's open owner */ |
| |
| #ifdef DEBUG_SAL |
| struct glist_head state_owners_all = GLIST_HEAD_INIT(state_owners_all); |
| pthread_mutex_t all_state_owners_mutex = PTHREAD_MUTEX_INITIALIZER; |
| #endif |
| |
| /* Error conversion routines */ |
| /** |
| * @brief Get a string from an error code |
| * |
| * @param[in] err Error code |
| * |
| * @return Error string. |
| */ |
| const char *state_err_str(state_status_t err) |
| { |
| switch (err) { |
| case STATE_SUCCESS: |
| return "STATE_SUCCESS"; |
| case STATE_MALLOC_ERROR: |
| return "STATE_MALLOC_ERROR"; |
| case STATE_POOL_MUTEX_INIT_ERROR: |
| return "STATE_POOL_MUTEX_INIT_ERROR"; |
| case STATE_GET_NEW_LRU_ENTRY: |
| return "STATE_GET_NEW_LRU_ENTRY"; |
| case STATE_INIT_ENTRY_FAILED: |
| return "STATE_INIT_ENTRY_FAILED"; |
| case STATE_FSAL_ERROR: |
| return "STATE_FSAL_ERROR"; |
| case STATE_LRU_ERROR: |
| return "STATE_LRU_ERROR"; |
| case STATE_HASH_SET_ERROR: |
| return "STATE_HASH_SET_ERROR"; |
| case STATE_NOT_A_DIRECTORY: |
| return "STATE_NOT_A_DIRECTORY"; |
| case STATE_INCONSISTENT_ENTRY: |
| return "STATE_INCONSISTENT_ENTRY"; |
| case STATE_BAD_TYPE: |
| return "STATE_BAD_TYPE"; |
| case STATE_ENTRY_EXISTS: |
| return "STATE_ENTRY_EXISTS"; |
| case STATE_DIR_NOT_EMPTY: |
| return "STATE_DIR_NOT_EMPTY"; |
| case STATE_NOT_FOUND: |
| return "STATE_NOT_FOUND"; |
| case STATE_INVALID_ARGUMENT: |
| return "STATE_INVALID_ARGUMENT"; |
| case STATE_INSERT_ERROR: |
| return "STATE_INSERT_ERROR"; |
| case STATE_HASH_TABLE_ERROR: |
| return "STATE_HASH_TABLE_ERROR"; |
| case STATE_FSAL_EACCESS: |
| return "STATE_FSAL_EACCESS"; |
| case STATE_IS_A_DIRECTORY: |
| return "STATE_IS_A_DIRECTORY"; |
| case STATE_FSAL_EPERM: |
| return "STATE_FSAL_EPERM"; |
| case STATE_NO_SPACE_LEFT: |
| return "STATE_NO_SPACE_LEFT"; |
| case STATE_READ_ONLY_FS: |
| return "STATE_READ_ONLY_FS"; |
| case STATE_IO_ERROR: |
| return "STATE_IO_ERROR"; |
| case STATE_ESTALE: |
| return "STATE_ESTALE"; |
| case STATE_FSAL_ERR_SEC: |
| return "STATE_FSAL_ERR_SEC"; |
| case STATE_QUOTA_EXCEEDED: |
| return "STATE_QUOTA_EXCEEDED"; |
| case STATE_ASYNC_POST_ERROR: |
| return "STATE_ASYNC_POST_ERROR"; |
| case STATE_NOT_SUPPORTED: |
| return "STATE_NOT_SUPPORTED"; |
| case STATE_STATE_ERROR: |
| return "STATE_STATE_ERROR"; |
| case STATE_FSAL_DELAY: |
| return "STATE_FSAL_DELAY"; |
| case STATE_NAME_TOO_LONG: |
| return "STATE_NAME_TOO_LONG"; |
| case STATE_LOCK_CONFLICT: |
| return "STATE_LOCK_CONFLICT"; |
| case STATE_LOCKED: |
| return "STATE_LOCKED"; |
| case STATE_LOCK_BLOCKED: |
| return "STATE_LOCK_BLOCKED"; |
| case STATE_LOCK_DEADLOCK: |
| return "STATE_LOCK_DEADLOCK"; |
| case STATE_BAD_COOKIE: |
| return "STATE_BAD_COOKIE"; |
| case STATE_FILE_BIG: |
| return "STATE_FILE_BIG"; |
| case STATE_GRACE_PERIOD: |
| return "STATE_GRACE_PERIOD"; |
| case STATE_CACHE_INODE_ERR: |
| return "STATE_CACHE_INODE_ERR"; |
| case STATE_SIGNAL_ERROR: |
| return "STATE_SIGNAL_ERROR"; |
| case STATE_FILE_OPEN: |
| return "STATE_FILE_OPEN"; |
| case STATE_SHARE_DENIED: |
| return "STATE_SHARE_DENIED"; |
| case STATE_MLINK: |
| return "STATE_MLINK"; |
| case STATE_SERVERFAULT: |
| return "STATE_SERVERFAULT"; |
| case STATE_TOOSMALL: |
| return "STATE_TOOSMALL"; |
| case STATE_XDEV: |
| return "STATE_XDEV"; |
| case STATE_IN_GRACE: |
| return "STATE_IN_GRACE"; |
| case STATE_BADHANDLE: |
| return "STATE_BADHANDLE"; |
| case STATE_BAD_RANGE: |
| return "STATE_BAD_RANGE"; |
| } |
| return "unknown"; |
| } |
| |
| /** |
| * @brief converts an FSAL error to the corresponding state error. |
| * |
| * @param[in] fsal_status Fsal error to be converted |
| * |
| * @return State status. |
| */ |
| state_status_t state_error_convert(fsal_status_t fsal_status) |
| { |
| switch (fsal_status.major) { |
| case ERR_FSAL_NO_ERROR: |
| return STATE_SUCCESS; |
| |
| case ERR_FSAL_NOENT: |
| return STATE_NOT_FOUND; |
| |
| case ERR_FSAL_DELAY: |
| case ERR_FSAL_ACCESS: |
| /* EDELAY and EACCESS are documented by fcntl as |
| * indicating lock conflict |
| */ |
| return STATE_LOCK_CONFLICT; |
| |
| case ERR_FSAL_PERM: |
| return STATE_FSAL_EPERM; |
| |
| case ERR_FSAL_NOSPC: |
| return STATE_NO_SPACE_LEFT; |
| |
| case ERR_FSAL_ROFS: |
| return STATE_READ_ONLY_FS; |
| |
| case ERR_FSAL_IO: |
| case ERR_FSAL_NXIO: |
| return STATE_IO_ERROR; |
| |
| case ERR_FSAL_STALE: |
| case ERR_FSAL_FHEXPIRED: |
| return STATE_ESTALE; |
| |
| case ERR_FSAL_INVAL: |
| case ERR_FSAL_OVERFLOW: |
| return STATE_INVALID_ARGUMENT; |
| |
| case ERR_FSAL_SEC: |
| return STATE_FSAL_ERR_SEC; |
| |
| case ERR_FSAL_NOTSUPP: |
| case ERR_FSAL_ATTRNOTSUPP: |
| case ERR_FSAL_UNION_NOTSUPP: |
| return STATE_NOT_SUPPORTED; |
| |
| case ERR_FSAL_NOMEM: |
| return STATE_MALLOC_ERROR; |
| |
| case ERR_FSAL_DEADLOCK: |
| return STATE_LOCK_DEADLOCK; |
| |
| case ERR_FSAL_BADCOOKIE: |
| return STATE_BAD_COOKIE; |
| |
| case ERR_FSAL_NOT_OPENED: |
| LogCrit(COMPONENT_STATE, |
| "Conversion of ERR_FSAL_NOT_OPENED to STATE_FSAL_ERROR"); |
| return STATE_FSAL_ERROR; |
| |
| case ERR_FSAL_SYMLINK: |
| case ERR_FSAL_BADTYPE: |
| return STATE_BAD_TYPE; |
| |
| case ERR_FSAL_ISDIR: |
| return STATE_IS_A_DIRECTORY; |
| |
| case ERR_FSAL_FBIG: |
| return STATE_FILE_BIG; |
| |
| case ERR_FSAL_FILE_OPEN: |
| return STATE_FILE_OPEN; |
| |
| case ERR_FSAL_SHARE_DENIED: |
| return STATE_SHARE_DENIED; |
| |
| case ERR_FSAL_BLOCKED: |
| return STATE_LOCK_BLOCKED; |
| |
| case ERR_FSAL_IN_GRACE: |
| return STATE_IN_GRACE; |
| |
| case ERR_FSAL_BADHANDLE: |
| return STATE_BADHANDLE; |
| |
| case ERR_FSAL_BAD_RANGE: |
| return STATE_BAD_RANGE; |
| |
| case ERR_FSAL_LOCKED: |
| return STATE_LOCKED; |
| |
| case ERR_FSAL_TOOSMALL: |
| return STATE_TOOSMALL; |
| |
| case ERR_FSAL_DQUOT: |
| case ERR_FSAL_NAMETOOLONG: |
| case ERR_FSAL_EXIST: |
| case ERR_FSAL_NOTEMPTY: |
| case ERR_FSAL_NOTDIR: |
| case ERR_FSAL_INTERRUPT: |
| case ERR_FSAL_FAULT: |
| case ERR_FSAL_NOT_INIT: |
| case ERR_FSAL_ALREADY_INIT: |
| case ERR_FSAL_BAD_INIT: |
| case ERR_FSAL_NO_QUOTA: |
| case ERR_FSAL_XDEV: |
| case ERR_FSAL_MLINK: |
| case ERR_FSAL_TIMEOUT: |
| case ERR_FSAL_SERVERFAULT: |
| case ERR_FSAL_NO_DATA: |
| case ERR_FSAL_NO_ACE: |
| case ERR_FSAL_CROSS_JUNCTION: |
| case ERR_FSAL_BADNAME: |
| /* These errors should be handled inside state |
| * (or should never be seen by state) |
| */ |
| LogDebug(COMPONENT_STATE, |
| "Conversion of FSAL error %d,%d to STATE_FSAL_ERROR", |
| fsal_status.major, fsal_status.minor); |
| return STATE_FSAL_ERROR; |
| } |
| |
| /* We should never reach this line, this may produce a warning with |
| * certain compiler */ |
| LogCrit(COMPONENT_STATE, |
| "Default conversion to STATE_FSAL_ERROR for error %d, line %u should never be reached", |
| fsal_status.major, __LINE__); |
| |
| return STATE_FSAL_ERROR; |
| } |
| |
| /** |
| * @brief Converts a state status to a nfsv4 status |
| * |
| * @param[in] error Input state error |
| * |
| * @return the converted NFSv4 status. |
| * |
| */ |
| nfsstat4 nfs4_Errno_state(state_status_t error) |
| { |
| nfsstat4 nfserror = NFS4ERR_INVAL; |
| |
| switch (error) { |
| case STATE_SUCCESS: |
| nfserror = NFS4_OK; |
| break; |
| |
| case STATE_MALLOC_ERROR: |
| nfserror = NFS4ERR_RESOURCE; |
| break; |
| |
| case STATE_POOL_MUTEX_INIT_ERROR: |
| case STATE_GET_NEW_LRU_ENTRY: |
| case STATE_INIT_ENTRY_FAILED: |
| nfserror = NFS4ERR_SERVERFAULT; |
| break; |
| |
| case STATE_BAD_TYPE: |
| nfserror = NFS4ERR_INVAL; |
| break; |
| |
| case STATE_NOT_A_DIRECTORY: |
| nfserror = NFS4ERR_NOTDIR; |
| break; |
| |
| case STATE_ENTRY_EXISTS: |
| nfserror = NFS4ERR_EXIST; |
| break; |
| |
| case STATE_DIR_NOT_EMPTY: |
| nfserror = NFS4ERR_NOTEMPTY; |
| break; |
| |
| case STATE_NOT_FOUND: |
| nfserror = NFS4ERR_NOENT; |
| break; |
| |
| case STATE_FSAL_ERROR: |
| case STATE_INSERT_ERROR: |
| case STATE_LRU_ERROR: |
| case STATE_HASH_SET_ERROR: |
| nfserror = NFS4ERR_IO; |
| break; |
| |
| case STATE_FSAL_EACCESS: |
| nfserror = NFS4ERR_ACCESS; |
| break; |
| |
| case STATE_FSAL_EPERM: |
| case STATE_FSAL_ERR_SEC: |
| nfserror = NFS4ERR_PERM; |
| break; |
| |
| case STATE_NO_SPACE_LEFT: |
| nfserror = NFS4ERR_NOSPC; |
| break; |
| |
| case STATE_IS_A_DIRECTORY: |
| nfserror = NFS4ERR_ISDIR; |
| break; |
| |
| case STATE_READ_ONLY_FS: |
| nfserror = NFS4ERR_ROFS; |
| break; |
| |
| case STATE_IO_ERROR: |
| nfserror = NFS4ERR_IO; |
| break; |
| |
| case STATE_FILE_OPEN: |
| nfserror = NFS4ERR_FILE_OPEN; |
| break; |
| |
| case STATE_NAME_TOO_LONG: |
| nfserror = NFS4ERR_NAMETOOLONG; |
| break; |
| |
| case STATE_ESTALE: |
| nfserror = NFS4ERR_STALE; |
| break; |
| |
| case STATE_SHARE_DENIED: |
| nfserror = NFS4ERR_SHARE_DENIED; |
| break; |
| |
| case STATE_LOCKED: |
| nfserror = NFS4ERR_LOCKED; |
| break; |
| |
| case STATE_QUOTA_EXCEEDED: |
| nfserror = NFS4ERR_DQUOT; |
| break; |
| |
| case STATE_NOT_SUPPORTED: |
| nfserror = NFS4ERR_NOTSUPP; |
| break; |
| |
| case STATE_FSAL_DELAY: |
| nfserror = NFS4ERR_DELAY; |
| break; |
| |
| case STATE_FILE_BIG: |
| nfserror = NFS4ERR_FBIG; |
| break; |
| |
| case STATE_LOCK_DEADLOCK: |
| nfserror = NFS4ERR_DEADLOCK; |
| break; |
| |
| case STATE_LOCK_BLOCKED: |
| case STATE_LOCK_CONFLICT: |
| nfserror = NFS4ERR_DENIED; |
| break; |
| |
| case STATE_STATE_ERROR: |
| nfserror = NFS4ERR_BAD_STATEID; |
| break; |
| |
| case STATE_BAD_COOKIE: |
| nfserror = NFS4ERR_BAD_COOKIE; |
| break; |
| |
| case STATE_GRACE_PERIOD: |
| nfserror = NFS4ERR_GRACE; |
| break; |
| |
| case STATE_SERVERFAULT: |
| nfserror = NFS4ERR_SERVERFAULT; |
| break; |
| |
| case STATE_MLINK: |
| nfserror = NFS4ERR_MLINK; |
| break; |
| |
| case STATE_TOOSMALL: |
| nfserror = NFS4ERR_TOOSMALL; |
| break; |
| |
| case STATE_IN_GRACE: |
| nfserror = NFS4ERR_GRACE; |
| break; |
| |
| case STATE_XDEV: |
| nfserror = NFS4ERR_XDEV; |
| break; |
| |
| case STATE_BADHANDLE: |
| nfserror = NFS4ERR_BADHANDLE; |
| break; |
| |
| case STATE_BAD_RANGE: |
| nfserror = NFS4ERR_BAD_RANGE; |
| break; |
| |
| case STATE_INVALID_ARGUMENT: |
| case STATE_CACHE_INODE_ERR: |
| case STATE_INCONSISTENT_ENTRY: |
| case STATE_HASH_TABLE_ERROR: |
| case STATE_ASYNC_POST_ERROR: |
| case STATE_SIGNAL_ERROR: |
| /* Should not occur */ |
| nfserror = NFS4ERR_INVAL; |
| break; |
| } |
| |
| return nfserror; |
| } |
| |
| /** |
| * @brief Converts a state status to an NFSv3 status |
| * |
| * @param[in] error State error |
| * |
| * @return Converted NFSv3 status. |
| */ |
| nfsstat3 nfs3_Errno_state(state_status_t error) |
| { |
| nfsstat3 nfserror = NFS3ERR_INVAL; |
| |
| switch (error) { |
| case STATE_SUCCESS: |
| nfserror = NFS3_OK; |
| break; |
| |
| case STATE_MALLOC_ERROR: |
| case STATE_POOL_MUTEX_INIT_ERROR: |
| case STATE_GET_NEW_LRU_ENTRY: |
| case STATE_INIT_ENTRY_FAILED: |
| case STATE_INSERT_ERROR: |
| case STATE_LRU_ERROR: |
| case STATE_HASH_SET_ERROR: |
| case STATE_FILE_OPEN: |
| LogCrit(COMPONENT_NFSPROTO, |
| "Error %u converted to NFS3ERR_IO but was set non-retryable", |
| error); |
| nfserror = NFS3ERR_IO; |
| break; |
| |
| case STATE_INVALID_ARGUMENT: |
| nfserror = NFS3ERR_INVAL; |
| break; |
| |
| case STATE_FSAL_ERROR: |
| /** @todo: Check if this works by making stress tests */ |
| LogCrit(COMPONENT_NFSPROTO, |
| "Error STATE_FSAL_ERROR converted to NFS3ERR_IO but was set non-retryable"); |
| nfserror = NFS3ERR_IO; |
| break; |
| |
| case STATE_NOT_A_DIRECTORY: |
| nfserror = NFS3ERR_NOTDIR; |
| break; |
| |
| case STATE_ENTRY_EXISTS: |
| nfserror = NFS3ERR_EXIST; |
| break; |
| |
| case STATE_DIR_NOT_EMPTY: |
| nfserror = NFS3ERR_NOTEMPTY; |
| break; |
| |
| case STATE_NOT_FOUND: |
| nfserror = NFS3ERR_NOENT; |
| break; |
| |
| case STATE_FSAL_EACCESS: |
| nfserror = NFS3ERR_ACCES; |
| break; |
| |
| case STATE_LOCKED: |
| case STATE_FSAL_EPERM: |
| case STATE_FSAL_ERR_SEC: |
| nfserror = NFS3ERR_PERM; |
| break; |
| |
| case STATE_NO_SPACE_LEFT: |
| nfserror = NFS3ERR_NOSPC; |
| break; |
| |
| case STATE_IS_A_DIRECTORY: |
| nfserror = NFS3ERR_ISDIR; |
| break; |
| |
| case STATE_READ_ONLY_FS: |
| nfserror = NFS3ERR_ROFS; |
| break; |
| |
| case STATE_ESTALE: |
| nfserror = NFS3ERR_STALE; |
| break; |
| |
| case STATE_QUOTA_EXCEEDED: |
| nfserror = NFS3ERR_DQUOT; |
| break; |
| |
| case STATE_BAD_TYPE: |
| nfserror = NFS3ERR_BADTYPE; |
| break; |
| |
| case STATE_NOT_SUPPORTED: |
| nfserror = NFS3ERR_NOTSUPP; |
| break; |
| |
| case STATE_FSAL_DELAY: |
| case STATE_SHARE_DENIED: |
| nfserror = NFS3ERR_JUKEBOX; |
| break; |
| |
| case STATE_IO_ERROR: |
| LogCrit(COMPONENT_NFSPROTO, |
| "Error STATE_IO_ERROR converted to NFS3ERR_IO but was set non-retryable"); |
| nfserror = NFS3ERR_IO; |
| break; |
| |
| case STATE_NAME_TOO_LONG: |
| nfserror = NFS3ERR_NAMETOOLONG; |
| break; |
| |
| case STATE_FILE_BIG: |
| nfserror = NFS3ERR_FBIG; |
| break; |
| |
| case STATE_BAD_COOKIE: |
| nfserror = NFS3ERR_BAD_COOKIE; |
| break; |
| |
| case STATE_MLINK: |
| nfserror = NFS3ERR_MLINK; |
| break; |
| |
| case STATE_SERVERFAULT: |
| nfserror = NFS3ERR_SERVERFAULT; |
| break; |
| |
| case STATE_TOOSMALL: |
| nfserror = NFS3ERR_TOOSMALL; |
| break; |
| |
| case STATE_XDEV: |
| nfserror = NFS3ERR_XDEV; |
| break; |
| |
| case STATE_IN_GRACE: |
| nfserror = NFS3ERR_JUKEBOX; |
| break; |
| |
| case STATE_BADHANDLE: |
| nfserror = NFS3ERR_BADHANDLE; |
| break; |
| |
| case STATE_CACHE_INODE_ERR: |
| case STATE_INCONSISTENT_ENTRY: |
| case STATE_HASH_TABLE_ERROR: |
| case STATE_ASYNC_POST_ERROR: |
| case STATE_STATE_ERROR: |
| case STATE_LOCK_CONFLICT: |
| case STATE_LOCK_BLOCKED: |
| case STATE_LOCK_DEADLOCK: |
| case STATE_GRACE_PERIOD: |
| case STATE_SIGNAL_ERROR: |
| case STATE_BAD_RANGE: |
| /* Should not occur */ |
| LogCrit(COMPONENT_NFSPROTO, |
| "Unexpected status for conversion = %s", |
| state_err_str(error)); |
| nfserror = NFS3ERR_INVAL; |
| break; |
| } |
| |
| return nfserror; |
| } |
| |
| bool state_unlock_err_ok(state_status_t status) |
| { |
| return status == STATE_SUCCESS || |
| status == STATE_ESTALE; |
| } |
| |
| /** String for undefined state owner types */ |
| const char *invalid_state_owner_type = "INVALID STATE OWNER TYPE"; |
| |
| /** |
| * @brief Return a string describing a state owner type |
| * |
| * @param[in] type The state owner type |
| * |
| * @return The string representation of the type. |
| */ |
| const char *state_owner_type_to_str(state_owner_type_t type) |
| { |
| switch (type) { |
| case STATE_LOCK_OWNER_UNKNOWN: |
| return "STATE_LOCK_OWNER_UNKNOWN"; |
| #ifdef _USE_NLM |
| case STATE_LOCK_OWNER_NLM: |
| return "STATE_LOCK_OWNER_NLM"; |
| #endif /* _USE_NLM */ |
| #ifdef _USE_9P |
| case STATE_LOCK_OWNER_9P: |
| return "STALE_LOCK_OWNER_9P"; |
| #endif |
| case STATE_OPEN_OWNER_NFSV4: |
| return "STATE_OPEN_OWNER_NFSV4"; |
| case STATE_LOCK_OWNER_NFSV4: |
| return "STATE_LOCK_OWNER_NFSV4"; |
| case STATE_CLIENTID_OWNER_NFSV4: |
| return "STATE_CLIENTID_OWNER_NFSV4"; |
| } |
| return invalid_state_owner_type; |
| } |
| |
| /** |
| * @brief See if two owners differ |
| * |
| * @param[in] owner1 An owner |
| * @param[in] owner2 Another owner |
| * |
| * @retval true if owners differ. |
| * @retval false if owners are the same. |
| */ |
| bool different_owners(state_owner_t *owner1, state_owner_t *owner2) |
| { |
| if (owner1 == NULL || owner2 == NULL) |
| return true; |
| |
| /* Shortcut if we actually are pointing to the same owner structure */ |
| if (owner1 == owner2) |
| return false; |
| |
| if (owner1->so_type != owner2->so_type) |
| return true; |
| |
| switch (owner1->so_type) { |
| #ifdef _USE_NLM |
| case STATE_LOCK_OWNER_NLM: |
| return compare_nlm_owner(owner1, owner2); |
| #endif /* _USE_NLM */ |
| #ifdef _USE_9P |
| case STATE_LOCK_OWNER_9P: |
| return compare_9p_owner(owner1, owner2); |
| #endif |
| case STATE_OPEN_OWNER_NFSV4: |
| case STATE_LOCK_OWNER_NFSV4: |
| case STATE_CLIENTID_OWNER_NFSV4: |
| return compare_nfs4_owner(owner1, owner2); |
| |
| case STATE_LOCK_OWNER_UNKNOWN: |
| break; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @brief Display a state owner |
| * |
| * @param[in/out] dspbuf display_buffer describing output string |
| * @param[in] owner Owner to display |
| * |
| * @return the bytes remaining in the buffer. |
| */ |
| int display_owner(struct display_buffer *dspbuf, state_owner_t *owner) |
| { |
| if (owner == NULL) |
| return display_printf(dspbuf, "<NULL>"); |
| |
| switch (owner->so_type) { |
| #ifdef _USE_NLM |
| case STATE_LOCK_OWNER_NLM: |
| return display_nlm_owner(dspbuf, owner); |
| #endif /* _USE_NLM */ |
| |
| #ifdef _USE_9P |
| case STATE_LOCK_OWNER_9P: |
| return display_9p_owner(dspbuf, owner); |
| #endif |
| |
| case STATE_OPEN_OWNER_NFSV4: |
| case STATE_LOCK_OWNER_NFSV4: |
| case STATE_CLIENTID_OWNER_NFSV4: |
| return display_nfs4_owner(dspbuf, owner); |
| |
| case STATE_LOCK_OWNER_UNKNOWN: |
| return |
| display_printf(dspbuf, |
| "%s powner=%p: refcount=%d", |
| state_owner_type_to_str(owner->so_type), |
| owner, |
| atomic_fetch_int32_t(&owner->so_refcount)); |
| } |
| |
| return display_printf(dspbuf, |
| "%s powner=%p", |
| invalid_state_owner_type, owner); |
| } |
| |
| /** |
| * @brief Acquire a reference on a state owner |
| * |
| * @param[in] owner Owner to acquire |
| */ |
| void inc_state_owner_ref(state_owner_t *owner) |
| { |
| char str[LOG_BUFF_LEN]; |
| struct display_buffer dspbuf = {sizeof(str), str, str}; |
| bool str_valid = false; |
| int32_t refcount; |
| |
| if (isFullDebug(COMPONENT_STATE)) { |
| display_owner(&dspbuf, owner); |
| str_valid = true; |
| } |
| |
| refcount = atomic_inc_int32_t(&owner->so_refcount); |
| |
| if (str_valid) |
| LogFullDebug(COMPONENT_STATE, |
| "Increment refcount now=%" PRId32 " {%s}", |
| refcount, str); |
| } |
| |
| /** |
| * @brief Free a state owner object |
| * |
| * @param[in] owner Owner to free |
| */ |
| void free_state_owner(state_owner_t *owner) |
| { |
| char str[LOG_BUFF_LEN]; |
| struct display_buffer dspbuf = {sizeof(str), str, str}; |
| |
| switch (owner->so_type) { |
| #ifdef _USE_NLM |
| case STATE_LOCK_OWNER_NLM: |
| free_nlm_owner(owner); |
| break; |
| #endif /* _USE_NLM */ |
| |
| #ifdef _USE_9P |
| case STATE_LOCK_OWNER_9P: |
| break; |
| #endif |
| |
| case STATE_OPEN_OWNER_NFSV4: |
| case STATE_LOCK_OWNER_NFSV4: |
| case STATE_CLIENTID_OWNER_NFSV4: |
| free_nfs4_owner(owner); |
| break; |
| |
| case STATE_LOCK_OWNER_UNKNOWN: |
| display_owner(&dspbuf, owner); |
| |
| LogCrit(COMPONENT_STATE, "Unexpected removal of {%s}", str); |
| return; |
| } |
| |
| if (owner->so_owner_val != NULL) |
| gsh_free(owner->so_owner_val); |
| |
| PTHREAD_MUTEX_destroy(&owner->so_mutex); |
| |
| #ifdef DEBUG_SAL |
| PTHREAD_MUTEX_lock(&all_state_owners_mutex); |
| |
| glist_del(&owner->so_all_owners); |
| |
| PTHREAD_MUTEX_unlock(&all_state_owners_mutex); |
| #endif |
| |
| pool_free(state_owner_pool, owner); |
| } |
| |
| /** |
| * @brief Get the hash table associated with a state owner object |
| * |
| * @param[in] owner Owner to get associated hash table for |
| */ |
| hash_table_t *get_state_owner_hash_table(state_owner_t *owner) |
| { |
| switch (owner->so_type) { |
| #ifdef _USE_NLM |
| case STATE_LOCK_OWNER_NLM: |
| return ht_nlm_owner; |
| #endif /* _USE_NLM */ |
| |
| #ifdef _USE_9P |
| case STATE_LOCK_OWNER_9P: |
| return ht_9p_owner; |
| #endif |
| |
| case STATE_OPEN_OWNER_NFSV4: |
| case STATE_LOCK_OWNER_NFSV4: |
| case STATE_CLIENTID_OWNER_NFSV4: |
| return ht_nfs4_owner; |
| |
| case STATE_LOCK_OWNER_UNKNOWN: |
| break; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * @brief Relinquish a reference on a state owner |
| * |
| * @param[in] owner Owner to release |
| */ |
| void dec_state_owner_ref(state_owner_t *owner) |
| { |
| char str[LOG_BUFF_LEN]; |
| struct display_buffer dspbuf = {sizeof(str), str, str}; |
| bool str_valid = false; |
| struct hash_latch latch; |
| hash_error_t rc; |
| struct gsh_buffdesc buffkey; |
| struct gsh_buffdesc old_value; |
| struct gsh_buffdesc old_key; |
| int32_t refcount; |
| hash_table_t *ht_owner; |
| |
| if (isDebug(COMPONENT_STATE)) { |
| display_owner(&dspbuf, owner); |
| str_valid = true; |
| } |
| |
| refcount = atomic_dec_int32_t(&owner->so_refcount); |
| |
| if (refcount != 0) { |
| if (str_valid) |
| LogFullDebug(COMPONENT_STATE, |
| "Decrement refcount now=%" PRId32 " {%s}", |
| refcount, str); |
| |
| assert(refcount > 0); |
| |
| return; |
| } |
| |
| ht_owner = get_state_owner_hash_table(owner); |
| |
| if (ht_owner == NULL) { |
| if (!str_valid) |
| display_printf(&dspbuf, "Invalid owner %p", owner); |
| |
| LogCrit(COMPONENT_STATE, "Unexpected owner {%s}, type {%d}", |
| str, owner->so_type); |
| |
| /** @todo: we need to understand how this situation occurs and |
| * either prevent it from happening or provide an explanation |
| * why this is ok. |
| */ |
| |
| return; |
| } |
| |
| buffkey.addr = owner; |
| buffkey.len = sizeof(*owner); |
| |
| /* Get the hash table entry and hold latch */ |
| rc = hashtable_getlatch(ht_owner, &buffkey, &old_value, true, &latch); |
| |
| if (rc != HASHTABLE_SUCCESS) { |
| if (rc == HASHTABLE_ERROR_NO_SUCH_KEY) |
| hashtable_releaselatched(ht_owner, &latch); |
| |
| if (!str_valid) |
| display_printf(&dspbuf, "Invalid owner %p", owner); |
| |
| LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", |
| hash_table_err_to_str(rc), str); |
| |
| return; |
| } |
| |
| /* use the key to delete the entry */ |
| hashtable_deletelatched(ht_owner, &buffkey, &latch, &old_key, |
| &old_value); |
| |
| /* Release the latch */ |
| hashtable_releaselatched(ht_owner, &latch); |
| |
| if (str_valid) |
| LogFullDebug(COMPONENT_STATE, "Free {%s}", str); |
| |
| free_state_owner(owner); |
| } |
| |
| /** @brief Remove an NFS 4 open owner from the cached owners list. |
| * |
| * The caller MUST hold the cached_open_owners_lock, also must NOT hold |
| * so_mutex as the so_mutex may get destroyed after this call. |
| * |
| * If this owner is being revived, the refcount should have already been |
| * incremented for the new primary reference. This function will release the |
| * refcount that held it in the cache. |
| * |
| * @param[in] nfs4_owner The owner to release. |
| * |
| */ |
| void uncache_nfs4_owner(struct state_nfs4_owner_t *nfs4_owner) |
| { |
| state_owner_t *owner = container_of(nfs4_owner, |
| state_owner_t, |
| so_owner.so_nfs4_owner); |
| |
| /* This owner is to be removed from the open owner cache: |
| * 1. Remove it from the list. |
| * 2. Make sure this is now a proper list head again. |
| * 3. Indicate it is no longer cached. |
| * 4. Release the reference held on behalf of the cache. |
| */ |
| if (isFullDebug(COMPONENT_STATE)) { |
| char str[LOG_BUFF_LEN]; |
| struct display_buffer dspbuf = {sizeof(str), str, str}; |
| |
| display_owner(&dspbuf, owner); |
| |
| LogFullDebug(COMPONENT_STATE, "Uncache {%s}", str); |
| } |
| |
| /* Remove owner from cached_open_owners */ |
| glist_del(&nfs4_owner->so_cache_entry); |
| |
| atomic_store_time_t(&nfs4_owner->so_cache_expire, 0); |
| |
| dec_state_owner_ref(owner); |
| } |
| |
| static inline |
| void refresh_nfs4_open_owner(struct state_nfs4_owner_t *nfs4_owner) |
| { |
| time_t cache_expire; |
| |
| /* Since this owner is active, reset cache_expire. */ |
| cache_expire = atomic_fetch_time_t(&nfs4_owner->so_cache_expire); |
| |
| if (cache_expire != 0) { |
| PTHREAD_MUTEX_lock(&cached_open_owners_lock); |
| |
| /* Check again while holding the mutex. */ |
| |
| if (atomic_fetch_time_t(&nfs4_owner->so_cache_expire) != 0) { |
| /* This is a cached open owner, uncache it for use. */ |
| uncache_nfs4_owner(nfs4_owner); |
| } |
| |
| PTHREAD_MUTEX_unlock(&cached_open_owners_lock); |
| } |
| } |
| |
| /** |
| * @brief Get a state owner |
| * |
| * @param[in] care indicates how we care about the owner |
| * @param[in] key the owner key we are searching for |
| * @param[in] init_owner routine to initialize a new owner |
| * @param[in,out] isnew pointer to flag indicating a new owner was created |
| * |
| * @return the owner found or NULL if no owner was found or created |
| */ |
| state_owner_t *get_state_owner(care_t care, state_owner_t *key, |
| state_owner_init_t init_owner, bool_t *isnew) |
| { |
| state_owner_t *owner; |
| char str[LOG_BUFF_LEN]; |
| struct display_buffer dspbuf = {sizeof(str), str, str}; |
| bool str_valid = false; |
| struct hash_latch latch; |
| hash_error_t rc; |
| struct gsh_buffdesc buffkey; |
| struct gsh_buffdesc buffval; |
| hash_table_t *ht_owner; |
| |
| if (isnew != NULL) |
| *isnew = false; |
| |
| if (isFullDebug(COMPONENT_STATE)) { |
| display_owner(&dspbuf, key); |
| |
| LogFullDebug(COMPONENT_STATE, "Find {%s}", str); |
| |
| str_valid = true; |
| } |
| |
| ht_owner = get_state_owner_hash_table(key); |
| |
| if (ht_owner == NULL) { |
| if (!str_valid) |
| display_owner(&dspbuf, key); |
| |
| LogCrit(COMPONENT_STATE, "ht=%p Unexpected key {%s}", ht_owner, |
| str); |
| return NULL; |
| } |
| |
| buffkey.addr = key; |
| buffkey.len = sizeof(*key); |
| |
| again: |
| |
| rc = hashtable_getlatch(ht_owner, &buffkey, &buffval, true, &latch); |
| |
| /* If we found it, return it */ |
| if (rc == HASHTABLE_SUCCESS) { |
| int32_t refcount; |
| |
| owner = buffval.addr; |
| |
| /* Return the found NSM Client */ |
| if (isFullDebug(COMPONENT_STATE)) { |
| display_owner(&dspbuf, owner); |
| str_valid = true; |
| } |
| |
| /* Increment refcount under hash latch. This protects against |
| * a race with dec_state_owner_ref removing the final |
| * reference. If we have that race however, we defer to |
| * the other thread and pretend we did NOT find the owner. |
| */ |
| refcount = atomic_inc_int32_t(&owner->so_refcount); |
| |
| if (refcount == 1) { |
| /* This owner is in the process of being freed. |
| * If care is not CARE_NOT, we will go back and |
| * retry, otherwise we will return NULL. |
| * |
| * If we care, the retry may cycle a time or two |
| * until the old owner manages to get out of the |
| * table, and then if multiple get_state_owner |
| * calls are racing, one of them will get the |
| * latch, not find the owner, and create it, the |
| * rest will then in turn find that new owner. |
| */ |
| if (isDebug(COMPONENT_STATE)) { |
| if (!str_valid) { |
| /* Since we still hold the latch, |
| * owner MUST still be valid, |
| * dec_state_owner_ref has not even |
| * removed from the hash table yet, |
| * let alone destroyed the object. |
| */ |
| display_owner(&dspbuf, owner); |
| } |
| |
| LogDebug(COMPONENT_STATE, |
| "Found owner in process of deconstruction, will %s {%s}", |
| care == CARE_NOT |
| ? "return NULL (CARE_NOT)" |
| : "retry", |
| str); |
| } |
| |
| /* Drop the reference we just got. */ |
| refcount = atomic_dec_int32_t(&owner->so_refcount); |
| |
| /* Just for kicks, validate the refcount */ |
| if (refcount != 0) { |
| display_owner(&dspbuf, owner); |
| LogCrit(COMPONENT_STATE, |
| "Deconstructed state owner has gained a reference {%s}", |
| str); |
| } |
| |
| /* Now drop the hash table latch and try again or exit. |
| */ |
| hashtable_releaselatched(ht_owner, &latch); |
| |
| /* If we don't care, return NULL at this point, |
| * otherwise retry. |
| */ |
| /** @todo should we nanosleep before retry? */ |
| if (care == CARE_NOT) |
| return NULL; |
| else |
| goto again; |
| } |
| |
| hashtable_releaselatched(ht_owner, &latch); |
| |
| /* Refresh an nfs4 open owner if needed. */ |
| if (owner->so_type == STATE_OPEN_OWNER_NFSV4) { |
| refresh_nfs4_open_owner(&owner->so_owner.so_nfs4_owner); |
| } |
| |
| if (isFullDebug(COMPONENT_STATE)) { |
| if (!str_valid) |
| display_owner(&dspbuf, owner); |
| |
| LogFullDebug(COMPONENT_STATE, |
| "Found {%s} refcount now=%" PRId32, |
| str, refcount); |
| } |
| |
| return owner; |
| } |
| |
| /* An error occurred, return NULL */ |
| if (rc != HASHTABLE_ERROR_NO_SUCH_KEY) { |
| if (!str_valid) |
| display_owner(&dspbuf, key); |
| |
| LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", |
| hash_table_err_to_str(rc), str); |
| |
| return NULL; |
| } |
| |
| /* Not found, but we don't care, return NULL */ |
| if (care == CARE_NOT) { |
| if (str_valid) |
| LogFullDebug(COMPONENT_STATE, "Ignoring {%s}", str); |
| |
| hashtable_releaselatched(ht_owner, &latch); |
| |
| return NULL; |
| } |
| |
| owner = pool_alloc(state_owner_pool); |
| |
| /* Copy everything over */ |
| memcpy(owner, key, sizeof(*key)); |
| |
| PTHREAD_MUTEX_init(&owner->so_mutex, NULL); |
| |
| #ifdef DEBUG_SAL |
| PTHREAD_MUTEX_lock(&all_state_owners_mutex); |
| |
| glist_add_tail(&state_owners_all, &owner->so_all_owners); |
| |
| PTHREAD_MUTEX_unlock(&all_state_owners_mutex); |
| #endif |
| |
| /* Do any owner type specific initialization */ |
| if (init_owner != NULL) |
| init_owner(owner); |
| |
| |
| if (key->so_owner_len != 0) { |
| owner->so_owner_val = gsh_malloc(key->so_owner_len); |
| |
| memcpy(owner->so_owner_val, |
| key->so_owner_val, |
| key->so_owner_len); |
| } |
| |
| glist_init(&owner->so_lock_list); |
| |
| owner->so_refcount = 1; |
| |
| if (isFullDebug(COMPONENT_STATE)) { |
| display_reset_buffer(&dspbuf); |
| display_owner(&dspbuf, owner); |
| LogFullDebug(COMPONENT_STATE, "New {%s}", str); |
| str_valid = true; |
| } else { |
| /* If we had the key, we don't want it anymore */ |
| str_valid = false; |
| } |
| |
| buffkey.addr = owner; |
| buffkey.len = sizeof(*owner); |
| buffval.addr = owner; |
| buffval.len = sizeof(*owner); |
| |
| rc = hashtable_setlatched(ht_owner, &buffval, &buffval, &latch, false, |
| NULL, NULL); |
| |
| /* An error occurred, return NULL */ |
| if (rc != HASHTABLE_SUCCESS) { |
| if (!str_valid) |
| display_owner(&dspbuf, owner); |
| |
| LogCrit(COMPONENT_STATE, "Error %s, inserting {%s}", |
| hash_table_err_to_str(rc), str); |
| |
| free_state_owner(owner); |
| |
| return NULL; |
| } |
| |
| if (isnew != NULL) |
| *isnew = true; |
| |
| return owner; |
| } |
| |
| /** |
| * @brief Release all state on a file |
| * |
| * This function may not be called in any context which could hold |
| * entry->state_lock. It will now be reliably called in cleanup |
| * processing. |
| * |
| * @param[in,out] obj File to be wiped |
| */ |
| void state_wipe_file(struct fsal_obj_handle *obj) |
| { |
| bool release; |
| /* |
| * currently, only REGULAR files can have state; byte range locks and |
| * stateid (for v4). In the future, 4.1, directories could have |
| * delegations, which is state. At that point, we may need to modify |
| * this routine to clear state on directories. |
| */ |
| if (obj->type != REGULAR_FILE) |
| return; |
| |
| PTHREAD_RWLOCK_wrlock(&obj->state_hdl->state_lock); |
| |
| release = state_lock_wipe(obj->state_hdl); |
| #ifdef _USE_NLM |
| state_share_wipe(obj->state_hdl); |
| #endif /* _USE_NLM */ |
| state_nfs4_state_wipe(obj->state_hdl); |
| |
| PTHREAD_RWLOCK_unlock(&obj->state_hdl->state_lock); |
| |
| #ifdef DEBUG_SAL |
| dump_all_states(); |
| #endif |
| |
| if (release) { |
| /* Drop the ref for the lock_list */ |
| obj->obj_ops.put_ref(obj); |
| } |
| } |
| |
| #ifdef DEBUG_SAL |
| void dump_all_owners(void) |
| { |
| if (!isFullDebug(COMPONENT_STATE)) |
| return; |
| |
| PTHREAD_MUTEX_lock(&all_state_owners_mutex); |
| |
| if (!glist_empty(&state_owners_all)) { |
| char str[LOG_BUFF_LEN]; |
| struct display_buffer dspbuf = {sizeof(str), str, str}; |
| struct glist_head *glist; |
| |
| LogFullDebug(COMPONENT_STATE, |
| " ---------------------- State Owner List ----------------------"); |
| |
| glist_for_each(glist, &state_owners_all) { |
| display_reset_buffer(&dspbuf); |
| display_owner(&dspbuf, glist_entry(glist, |
| state_owner_t, |
| so_all_owners)); |
| LogFullDebug(COMPONENT_STATE, "{%s}", str); |
| } |
| |
| LogFullDebug(COMPONENT_STATE, " ----------------------"); |
| } else |
| LogFullDebug(COMPONENT_STATE, "All state owners released"); |
| |
| PTHREAD_MUTEX_unlock(&all_state_owners_mutex); |
| } |
| #endif |
| |
| /** |
| * @brief Release all the state belonging to an export. |
| * |
| * @param[in] exp The export to release state for. |
| * |
| */ |
| |
| void state_release_export(struct gsh_export *export) |
| { |
| struct root_op_context root_op_context; |
| |
| /* Initialize req_ctx */ |
| init_root_op_context(&root_op_context, export, export->fsal_export, |
| 0, 0, UNKNOWN_REQUEST); |
| |
| state_export_unlock_all(); |
| state_export_release_nfs4_state(); |
| #ifdef _USE_NLM |
| state_export_unshare_all(); |
| #endif /* _USE_NLM */ |
| release_root_op_context(); |
| } |
| |
| /** @} */ |