Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/hwmon/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 26 kB image not shown  

Quelle  nfs4state.c   Sprache: C

 
/*
*  Copyright (c) 2001 The Regents of the University of Michigan.
*  All rights reserved.
*
*  Kendrick Smith <kmsmith@umich.edu>
*  Andy Adamson <kandros@umich.edu>
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*  2. Redistributions in binary form must reproduce the above copyright
*     notice, this list of conditions and the following disclaimer in the
*     documentation and/or other materials provided with the distribution.
*  3. Neither the name of the University nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
*  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
*  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
*  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
*  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
*  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/


#include <linux/file.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/namei.h>
#include <linux/swap.h>
#include <linux/pagemap.h>
#include <linux/ratelimit.h>
#include <linux/sunrpc/svcauth_gss.h>
#include <linux/sunrpc/addr.h>
#include <linux/jhash.h>
#include <linux/string_helpers.h>
#include <linux/fsnotify.h>
#include <linux/rhashtable.h>
#include <linux/nfs_ssc.h>

#include "xdr4.h"
#include "xdr4cb.h"
#include "vfs.h"
#include "current_stateid.h"

#include "netns.h"
#include "pnfs.h"
#include "filecache.h"
#include "trace.h"

#define NFSDDBG_FACILITY                NFSDDBG_PROC

#define all_ones {{ ~0, ~0}, ~0}
static const stateid_t one_stateid = {
 .si_generation = ~0,
 .si_opaque = all_ones,
};
static const stateid_t zero_stateid = {
 /* all fields zero */
};
static const stateid_t currentstateid = {
 .si_generation = 1,
};
static const stateid_t close_stateid = {
 .si_generation = 0xffffffffU,
};

static u64 current_sessionid = 1;

#define ZERO_STATEID(stateid) (!memcmp((stateid), &zero_stateid, sizeof(stateid_t)))
#define ONE_STATEID(stateid)  (!memcmp((stateid), &one_stateid, sizeof(stateid_t)))
#define CURRENT_STATEID(stateid) (!memcmp((stateid), ¤tstateid, sizeof(stateid_t)))
#define CLOSE_STATEID(stateid)  (!memcmp((stateid), &close_stateid, sizeof(stateid_t)))

/* forward declarations */
static bool check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner);
static void nfs4_free_ol_stateid(struct nfs4_stid *stid);
void nfsd4_end_grace(struct nfsd_net *nn);
static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps);
static void nfsd4_file_hash_remove(struct nfs4_file *fi);
static void deleg_reaper(struct nfsd_net *nn);

/* Locking: */

/*
 * Currently used for the del_recall_lru and file hash table.  In an
 * effort to decrease the scope of the client_mutex, this spinlock may
 * eventually cover more:
 */

static DEFINE_SPINLOCK(state_lock);

enum nfsd4_st_mutex_lock_subclass {
 OPEN_STATEID_MUTEX = 0,
 LOCK_STATEID_MUTEX = 1,
};

/*
 * A waitqueue for all in-progress 4.0 CLOSE operations that are waiting for
 * the refcount on the open stateid to drop.
 */

static DECLARE_WAIT_QUEUE_HEAD(close_wq);

/*
 * A waitqueue where a writer to clients/#/ctl destroying a client can
 * wait for cl_rpc_users to drop to 0 and then for the client to be
 * unhashed.
 */

static DECLARE_WAIT_QUEUE_HEAD(expiry_wq);

static struct kmem_cache *client_slab;
static struct kmem_cache *openowner_slab;
static struct kmem_cache *lockowner_slab;
static struct kmem_cache *file_slab;
static struct kmem_cache *stateid_slab;
static struct kmem_cache *deleg_slab;
static struct kmem_cache *odstate_slab;

static void free_session(struct nfsd4_session *);

static const struct nfsd4_callback_ops nfsd4_cb_recall_ops;
static const struct nfsd4_callback_ops nfsd4_cb_notify_lock_ops;
static const struct nfsd4_callback_ops nfsd4_cb_getattr_ops;

static struct workqueue_struct *laundry_wq;

int nfsd4_create_laundry_wq(void)
{
 int rc = 0;

 laundry_wq = alloc_workqueue("%s", WQ_UNBOUND, 0, "nfsd4");
 if (laundry_wq == NULL)
  rc = -ENOMEM;
 return rc;
}

void nfsd4_destroy_laundry_wq(void)
{
 destroy_workqueue(laundry_wq);
}

static bool is_session_dead(struct nfsd4_session *ses)
{
 return ses->se_dead;
}

static __be32 mark_session_dead_locked(struct nfsd4_session *ses, int ref_held_by_me)
{
 if (atomic_read(&ses->se_ref) > ref_held_by_me)
  return nfserr_jukebox;
 ses->se_dead = true;
 return nfs_ok;
}

static bool is_client_expired(struct nfs4_client *clp)
{
 return clp->cl_time == 0;
}

static void nfsd4_dec_courtesy_client_count(struct nfsd_net *nn,
     struct nfs4_client *clp)
{
 if (clp->cl_state != NFSD4_ACTIVE)
  atomic_add_unless(&nn->nfsd_courtesy_clients, -1, 0);
}

static __be32 get_client_locked(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 lockdep_assert_held(&nn->client_lock);

 if (is_client_expired(clp))
  return nfserr_expired;
 atomic_inc(&clp->cl_rpc_users);
 nfsd4_dec_courtesy_client_count(nn, clp);
 clp->cl_state = NFSD4_ACTIVE;
 return nfs_ok;
}

/* must be called under the client_lock */
static inline void
renew_client_locked(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 if (is_client_expired(clp)) {
  WARN_ON(1);
  printk("%s: client (clientid %08x/%08x) already expired\n",
   __func__,
   clp->cl_clientid.cl_boot,
   clp->cl_clientid.cl_id);
  return;
 }

 list_move_tail(&clp->cl_lru, &nn->client_lru);
 clp->cl_time = ktime_get_boottime_seconds();
 nfsd4_dec_courtesy_client_count(nn, clp);
 clp->cl_state = NFSD4_ACTIVE;
}

static void put_client_renew_locked(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 lockdep_assert_held(&nn->client_lock);

 if (!atomic_dec_and_test(&clp->cl_rpc_users))
  return;
 if (!is_client_expired(clp))
  renew_client_locked(clp);
 else
  wake_up_all(&expiry_wq);
}

static void put_client_renew(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 if (!atomic_dec_and_lock(&clp->cl_rpc_users, &nn->client_lock))
  return;
 if (!is_client_expired(clp))
  renew_client_locked(clp);
 else
  wake_up_all(&expiry_wq);
 spin_unlock(&nn->client_lock);
}

static __be32 nfsd4_get_session_locked(struct nfsd4_session *ses)
{
 __be32 status;

 if (is_session_dead(ses))
  return nfserr_badsession;
 status = get_client_locked(ses->se_client);
 if (status)
  return status;
 atomic_inc(&ses->se_ref);
 return nfs_ok;
}

static void nfsd4_put_session_locked(struct nfsd4_session *ses)
{
 struct nfs4_client *clp = ses->se_client;
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 lockdep_assert_held(&nn->client_lock);

 if (atomic_dec_and_test(&ses->se_ref) && is_session_dead(ses))
  free_session(ses);
 put_client_renew_locked(clp);
}

static void nfsd4_put_session(struct nfsd4_session *ses)
{
 struct nfs4_client *clp = ses->se_client;
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 spin_lock(&nn->client_lock);
 nfsd4_put_session_locked(ses);
 spin_unlock(&nn->client_lock);
}

static struct nfsd4_blocked_lock *
find_blocked_lock(struct nfs4_lockowner *lo, struct knfsd_fh *fh,
   struct nfsd_net *nn)
{
 struct nfsd4_blocked_lock *cur, *found = NULL;

 spin_lock(&nn->blocked_locks_lock);
 list_for_each_entry(cur, &lo->lo_blocked, nbl_list) {
  if (fh_match(fh, &cur->nbl_fh)) {
   list_del_init(&cur->nbl_list);
   WARN_ON(list_empty(&cur->nbl_lru));
   list_del_init(&cur->nbl_lru);
   found = cur;
   break;
  }
 }
 spin_unlock(&nn->blocked_locks_lock);
 if (found)
  locks_delete_block(&found->nbl_lock);
 return found;
}

static struct nfsd4_blocked_lock *
find_or_allocate_block(struct nfs4_lockowner *lo, struct knfsd_fh *fh,
   struct nfsd_net *nn)
{
 struct nfsd4_blocked_lock *nbl;

 nbl = find_blocked_lock(lo, fh, nn);
 if (!nbl) {
  nbl = kmalloc(sizeof(*nbl), GFP_KERNEL);
  if (nbl) {
   INIT_LIST_HEAD(&nbl->nbl_list);
   INIT_LIST_HEAD(&nbl->nbl_lru);
   fh_copy_shallow(&nbl->nbl_fh, fh);
   locks_init_lock(&nbl->nbl_lock);
   kref_init(&nbl->nbl_kref);
   nfsd4_init_cb(&nbl->nbl_cb, lo->lo_owner.so_client,
     &nfsd4_cb_notify_lock_ops,
     NFSPROC4_CLNT_CB_NOTIFY_LOCK);
  }
 }
 return nbl;
}

static void
free_nbl(struct kref *kref)
{
 struct nfsd4_blocked_lock *nbl;

 nbl = container_of(kref, struct nfsd4_blocked_lock, nbl_kref);
 locks_release_private(&nbl->nbl_lock);
 kfree(nbl);
}

static void
free_blocked_lock(struct nfsd4_blocked_lock *nbl)
{
 locks_delete_block(&nbl->nbl_lock);
 kref_put(&nbl->nbl_kref, free_nbl);
}

static void
remove_blocked_locks(struct nfs4_lockowner *lo)
{
 struct nfs4_client *clp = lo->lo_owner.so_client;
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
 struct nfsd4_blocked_lock *nbl;
 LIST_HEAD(reaplist);

 /* Dequeue all blocked locks */
 spin_lock(&nn->blocked_locks_lock);
 while (!list_empty(&lo->lo_blocked)) {
  nbl = list_first_entry(&lo->lo_blocked,
     struct nfsd4_blocked_lock,
     nbl_list);
  list_del_init(&nbl->nbl_list);
  WARN_ON(list_empty(&nbl->nbl_lru));
  list_move(&nbl->nbl_lru, &reaplist);
 }
 spin_unlock(&nn->blocked_locks_lock);

 /* Now free them */
 while (!list_empty(&reaplist)) {
  nbl = list_first_entry(&reaplist, struct nfsd4_blocked_lock,
     nbl_lru);
  list_del_init(&nbl->nbl_lru);
  free_blocked_lock(nbl);
 }
}

static void
nfsd4_cb_notify_lock_prepare(struct nfsd4_callback *cb)
{
 struct nfsd4_blocked_lock *nbl = container_of(cb,
      struct nfsd4_blocked_lock, nbl_cb);
 locks_delete_block(&nbl->nbl_lock);
}

static int
nfsd4_cb_notify_lock_done(struct nfsd4_callback *cb, struct rpc_task *task)
{
 trace_nfsd_cb_notify_lock_done(&zero_stateid, task);

 /*
 * Since this is just an optimization, we don't try very hard if it
 * turns out not to succeed. We'll requeue it on NFS4ERR_DELAY, and
 * just quit trying on anything else.
 */

 switch (task->tk_status) {
 case -NFS4ERR_DELAY:
  rpc_delay(task, 1 * HZ);
  return 0;
 default:
  return 1;
 }
}

static void
nfsd4_cb_notify_lock_release(struct nfsd4_callback *cb)
{
 struct nfsd4_blocked_lock *nbl = container_of(cb,
      struct nfsd4_blocked_lock, nbl_cb);

 free_blocked_lock(nbl);
}

static const struct nfsd4_callback_ops nfsd4_cb_notify_lock_ops = {
 .prepare = nfsd4_cb_notify_lock_prepare,
 .done  = nfsd4_cb_notify_lock_done,
 .release = nfsd4_cb_notify_lock_release,
 .opcode  = OP_CB_NOTIFY_LOCK,
};

/*
 * We store the NONE, READ, WRITE, and BOTH bits separately in the
 * st_{access,deny}_bmap field of the stateid, in order to track not
 * only what share bits are currently in force, but also what
 * combinations of share bits previous opens have used.  This allows us
 * to enforce the recommendation in
 * https://datatracker.ietf.org/doc/html/rfc7530#section-16.19.4 that
 * the server return an error if the client attempt to downgrade to a
 * combination of share bits not explicable by closing some of its
 * previous opens.
 *
 * This enforcement is arguably incomplete, since we don't keep
 * track of access/deny bit combinations; so, e.g., we allow:
 *
 * OPEN allow read, deny write
 * OPEN allow both, deny none
 * DOWNGRADE allow read, deny none
 *
 * which we should reject.
 *
 * But you could also argue that our current code is already overkill,
 * since it only exists to return NFS4ERR_INVAL on incorrect client
 * behavior.
 */

static unsigned int
bmap_to_share_mode(unsigned long bmap)
{
 int i;
 unsigned int access = 0;

 for (i = 1; i < 4; i++) {
  if (test_bit(i, &bmap))
   access |= i;
 }
 return access;
}

/* set share access for a given stateid */
static inline void
set_access(u32 access, struct nfs4_ol_stateid *stp)
{
 unsigned char mask = 1 << access;

 WARN_ON_ONCE(access > NFS4_SHARE_ACCESS_BOTH);
 stp->st_access_bmap |= mask;
}

/* clear share access for a given stateid */
static inline void
clear_access(u32 access, struct nfs4_ol_stateid *stp)
{
 unsigned char mask = 1 << access;

 WARN_ON_ONCE(access > NFS4_SHARE_ACCESS_BOTH);
 stp->st_access_bmap &= ~mask;
}

/* test whether a given stateid has access */
static inline bool
test_access(u32 access, struct nfs4_ol_stateid *stp)
{
 unsigned char mask = 1 << access;

 return (bool)(stp->st_access_bmap & mask);
}

/* set share deny for a given stateid */
static inline void
set_deny(u32 deny, struct nfs4_ol_stateid *stp)
{
 unsigned char mask = 1 << deny;

 WARN_ON_ONCE(deny > NFS4_SHARE_DENY_BOTH);
 stp->st_deny_bmap |= mask;
}

/* clear share deny for a given stateid */
static inline void
clear_deny(u32 deny, struct nfs4_ol_stateid *stp)
{
 unsigned char mask = 1 << deny;

 WARN_ON_ONCE(deny > NFS4_SHARE_DENY_BOTH);
 stp->st_deny_bmap &= ~mask;
}

/* test whether a given stateid is denying specific access */
static inline bool
test_deny(u32 deny, struct nfs4_ol_stateid *stp)
{
 unsigned char mask = 1 << deny;

 return (bool)(stp->st_deny_bmap & mask);
}

static int nfs4_access_to_omode(u32 access)
{
 switch (access & NFS4_SHARE_ACCESS_BOTH) {
 case NFS4_SHARE_ACCESS_READ:
  return O_RDONLY;
 case NFS4_SHARE_ACCESS_WRITE:
  return O_WRONLY;
 case NFS4_SHARE_ACCESS_BOTH:
  return O_RDWR;
 }
 WARN_ON_ONCE(1);
 return O_RDONLY;
}

static inline int
access_permit_read(struct nfs4_ol_stateid *stp)
{
 return test_access(NFS4_SHARE_ACCESS_READ, stp) ||
  test_access(NFS4_SHARE_ACCESS_BOTH, stp) ||
  test_access(NFS4_SHARE_ACCESS_WRITE, stp);
}

static inline int
access_permit_write(struct nfs4_ol_stateid *stp)
{
 return test_access(NFS4_SHARE_ACCESS_WRITE, stp) ||
  test_access(NFS4_SHARE_ACCESS_BOTH, stp);
}

static inline struct nfs4_stateowner *
nfs4_get_stateowner(struct nfs4_stateowner *sop)
{
 atomic_inc(&sop->so_count);
 return sop;
}

static int
same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner)
{
 return (sop->so_owner.len == owner->len) &&
  0 == memcmp(sop->so_owner.data, owner->data, owner->len);
}

static struct nfs4_openowner *
find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open,
   struct nfs4_client *clp)
{
 struct nfs4_stateowner *so;

 lockdep_assert_held(&clp->cl_lock);

 list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[hashval],
       so_strhash) {
  if (!so->so_is_open_owner)
   continue;
  if (same_owner_str(so, &open->op_owner))
   return openowner(nfs4_get_stateowner(so));
 }
 return NULL;
}

static inline u32
opaque_hashval(const void *ptr, int nbytes)
{
 unsigned char *cptr = (unsigned char *) ptr;

 u32 x = 0;
 while (nbytes--) {
  x *= 37;
  x += *cptr++;
 }
 return x;
}

void
put_nfs4_file(struct nfs4_file *fi)
{
 if (refcount_dec_and_test(&fi->fi_ref)) {
  nfsd4_file_hash_remove(fi);
  WARN_ON_ONCE(!list_empty(&fi->fi_clnt_odstate));
  WARN_ON_ONCE(!list_empty(&fi->fi_delegations));
  kfree_rcu(fi, fi_rcu);
 }
}

static struct nfsd_file *
find_writeable_file_locked(struct nfs4_file *f)
{
 struct nfsd_file *ret;

 lockdep_assert_held(&f->fi_lock);

 ret = nfsd_file_get(f->fi_fds[O_WRONLY]);
 if (!ret)
  ret = nfsd_file_get(f->fi_fds[O_RDWR]);
 return ret;
}

static struct nfsd_file *
find_writeable_file(struct nfs4_file *f)
{
 struct nfsd_file *ret;

 spin_lock(&f->fi_lock);
 ret = find_writeable_file_locked(f);
 spin_unlock(&f->fi_lock);

 return ret;
}

static struct nfsd_file *
find_readable_file_locked(struct nfs4_file *f)
{
 struct nfsd_file *ret;

 lockdep_assert_held(&f->fi_lock);

 ret = nfsd_file_get(f->fi_fds[O_RDONLY]);
 if (!ret)
  ret = nfsd_file_get(f->fi_fds[O_RDWR]);
 return ret;
}

static struct nfsd_file *
find_readable_file(struct nfs4_file *f)
{
 struct nfsd_file *ret;

 spin_lock(&f->fi_lock);
 ret = find_readable_file_locked(f);
 spin_unlock(&f->fi_lock);

 return ret;
}

struct nfsd_file *
find_any_file(struct nfs4_file *f)
{
 struct nfsd_file *ret;

 if (!f)
  return NULL;
 spin_lock(&f->fi_lock);
 ret = nfsd_file_get(f->fi_fds[O_RDWR]);
 if (!ret) {
  ret = nfsd_file_get(f->fi_fds[O_WRONLY]);
  if (!ret)
   ret = nfsd_file_get(f->fi_fds[O_RDONLY]);
 }
 spin_unlock(&f->fi_lock);
 return ret;
}

static struct nfsd_file *find_any_file_locked(struct nfs4_file *f)
{
 lockdep_assert_held(&f->fi_lock);

 if (f->fi_fds[O_RDWR])
  return f->fi_fds[O_RDWR];
 if (f->fi_fds[O_WRONLY])
  return f->fi_fds[O_WRONLY];
 if (f->fi_fds[O_RDONLY])
  return f->fi_fds[O_RDONLY];
 return NULL;
}

static atomic_long_t num_delegations;
unsigned long max_delegations;

/*
 * Open owner state (share locks)
 */


/* hash tables for lock and open owners */
#define OWNER_HASH_BITS              8
#define OWNER_HASH_SIZE             (1 << OWNER_HASH_BITS)
#define OWNER_HASH_MASK             (OWNER_HASH_SIZE - 1)

static unsigned int ownerstr_hashval(struct xdr_netobj *ownername)
{
 unsigned int ret;

 ret = opaque_hashval(ownername->data, ownername->len);
 return ret & OWNER_HASH_MASK;
}

static struct rhltable nfs4_file_rhltable ____cacheline_aligned_in_smp;

static const struct rhashtable_params nfs4_file_rhash_params = {
 .key_len  = sizeof_field(struct nfs4_file, fi_inode),
 .key_offset  = offsetof(struct nfs4_file, fi_inode),
 .head_offset  = offsetof(struct nfs4_file, fi_rlist),

 /*
 * Start with a single page hash table to reduce resizing churn
 * on light workloads.
 */

 .min_size  = 256,
 .automatic_shrinking = true,
};

/*
 * Check if courtesy clients have conflicting access and resolve it if possible
 *
 * access:  is op_share_access if share_access is true.
 *     Check if access mode, op_share_access, would conflict with
 *     the current deny mode of the file 'fp'.
 * access:  is op_share_deny if share_access is false.
 *     Check if the deny mode, op_share_deny, would conflict with
 *     current access of the file 'fp'.
 * stp:     skip checking this entry.
 * new_stp: normal open, not open upgrade.
 *
 * Function returns:
 * false - access/deny mode conflict with normal client.
 * true  - no conflict or conflict with courtesy client(s) is resolved.
 */

static bool
nfs4_resolve_deny_conflicts_locked(struct nfs4_file *fp, bool new_stp,
  struct nfs4_ol_stateid *stp, u32 access, bool share_access)
{
 struct nfs4_ol_stateid *st;
 bool resolvable = true;
 unsigned char bmap;
 struct nfsd_net *nn;
 struct nfs4_client *clp;

 lockdep_assert_held(&fp->fi_lock);
 list_for_each_entry(st, &fp->fi_stateids, st_perfile) {
  /* ignore lock stateid */
  if (st->st_openstp)
   continue;
  if (st == stp && new_stp)
   continue;
  /* check file access against deny mode or vice versa */
  bmap = share_access ? st->st_deny_bmap : st->st_access_bmap;
  if (!(access & bmap_to_share_mode(bmap)))
   continue;
  clp = st->st_stid.sc_client;
  if (try_to_expire_client(clp))
   continue;
  resolvable = false;
  break;
 }
 if (resolvable) {
  clp = stp->st_stid.sc_client;
  nn = net_generic(clp->net, nfsd_net_id);
  mod_delayed_work(laundry_wq, &nn->laundromat_work, 0);
 }
 return resolvable;
}

static void
__nfs4_file_get_access(struct nfs4_file *fp, u32 access)
{
 lockdep_assert_held(&fp->fi_lock);

 if (access & NFS4_SHARE_ACCESS_WRITE)
  atomic_inc(&fp->fi_access[O_WRONLY]);
 if (access & NFS4_SHARE_ACCESS_READ)
  atomic_inc(&fp->fi_access[O_RDONLY]);
}

static __be32
nfs4_file_get_access(struct nfs4_file *fp, u32 access)
{
 lockdep_assert_held(&fp->fi_lock);

 /* Does this access mode make sense? */
 if (access & ~NFS4_SHARE_ACCESS_BOTH)
  return nfserr_inval;

 /* Does it conflict with a deny mode already set? */
 if ((access & fp->fi_share_deny) != 0)
  return nfserr_share_denied;

 __nfs4_file_get_access(fp, access);
 return nfs_ok;
}

static __be32 nfs4_file_check_deny(struct nfs4_file *fp, u32 deny)
{
 /* Common case is that there is no deny mode. */
 if (deny) {
  /* Does this deny mode make sense? */
  if (deny & ~NFS4_SHARE_DENY_BOTH)
   return nfserr_inval;

  if ((deny & NFS4_SHARE_DENY_READ) &&
      atomic_read(&fp->fi_access[O_RDONLY]))
   return nfserr_share_denied;

  if ((deny & NFS4_SHARE_DENY_WRITE) &&
      atomic_read(&fp->fi_access[O_WRONLY]))
   return nfserr_share_denied;
 }
 return nfs_ok;
}

static void __nfs4_file_put_access(struct nfs4_file *fp, int oflag)
{
 might_lock(&fp->fi_lock);

 if (atomic_dec_and_lock(&fp->fi_access[oflag], &fp->fi_lock)) {
  struct nfsd_file *f1 = NULL;
  struct nfsd_file *f2 = NULL;

  swap(f1, fp->fi_fds[oflag]);
  if (atomic_read(&fp->fi_access[1 - oflag]) == 0)
   swap(f2, fp->fi_fds[O_RDWR]);
  spin_unlock(&fp->fi_lock);
  if (f1)
   nfsd_file_put(f1);
  if (f2)
   nfsd_file_put(f2);
 }
}

static void nfs4_file_put_access(struct nfs4_file *fp, u32 access)
{
 WARN_ON_ONCE(access & ~NFS4_SHARE_ACCESS_BOTH);

 if (access & NFS4_SHARE_ACCESS_WRITE)
  __nfs4_file_put_access(fp, O_WRONLY);
 if (access & NFS4_SHARE_ACCESS_READ)
  __nfs4_file_put_access(fp, O_RDONLY);
}

/*
 * Allocate a new open/delegation state counter. This is needed for
 * pNFS for proper return on close semantics.
 *
 * Note that we only allocate it for pNFS-enabled exports, otherwise
 * all pointers to struct nfs4_clnt_odstate are always NULL.
 */

static struct nfs4_clnt_odstate *
alloc_clnt_odstate(struct nfs4_client *clp)
{
 struct nfs4_clnt_odstate *co;

 co = kmem_cache_zalloc(odstate_slab, GFP_KERNEL);
 if (co) {
  co->co_client = clp;
  refcount_set(&co->co_odcount, 1);
 }
 return co;
}

static void
hash_clnt_odstate_locked(struct nfs4_clnt_odstate *co)
{
 struct nfs4_file *fp = co->co_file;

 lockdep_assert_held(&fp->fi_lock);
 list_add(&co->co_perfile, &fp->fi_clnt_odstate);
}

static inline void
get_clnt_odstate(struct nfs4_clnt_odstate *co)
{
 if (co)
  refcount_inc(&co->co_odcount);
}

static void
put_clnt_odstate(struct nfs4_clnt_odstate *co)
{
 struct nfs4_file *fp;

 if (!co)
  return;

 fp = co->co_file;
 if (refcount_dec_and_lock(&co->co_odcount, &fp->fi_lock)) {
  list_del(&co->co_perfile);
  spin_unlock(&fp->fi_lock);

  nfsd4_return_all_file_layouts(co->co_client, fp);
  kmem_cache_free(odstate_slab, co);
 }
}

static struct nfs4_clnt_odstate *
find_or_hash_clnt_odstate(struct nfs4_file *fp, struct nfs4_clnt_odstate *new)
{
 struct nfs4_clnt_odstate *co;
 struct nfs4_client *cl;

 if (!new)
  return NULL;

 cl = new->co_client;

 spin_lock(&fp->fi_lock);
 list_for_each_entry(co, &fp->fi_clnt_odstate, co_perfile) {
  if (co->co_client == cl) {
   get_clnt_odstate(co);
   goto out;
  }
 }
 co = new;
 co->co_file = fp;
 hash_clnt_odstate_locked(new);
out:
 spin_unlock(&fp->fi_lock);
 return co;
}

struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *slab,
      void (*sc_free)(struct nfs4_stid *))
{
 struct nfs4_stid *stid;
 int new_id;

 stid = kmem_cache_zalloc(slab, GFP_KERNEL);
 if (!stid)
  return NULL;

 idr_preload(GFP_KERNEL);
 spin_lock(&cl->cl_lock);
 /* Reserving 0 for start of file in nfsdfs "states" file: */
 new_id = idr_alloc_cyclic(&cl->cl_stateids, stid, 1, 0, GFP_NOWAIT);
 spin_unlock(&cl->cl_lock);
 idr_preload_end();
 if (new_id < 0)
  goto out_free;

 stid->sc_free = sc_free;
 stid->sc_client = cl;
 stid->sc_stateid.si_opaque.so_id = new_id;
 stid->sc_stateid.si_opaque.so_clid = cl->cl_clientid;
 /* Will be incremented before return to client: */
 refcount_set(&stid->sc_count, 1);
 spin_lock_init(&stid->sc_lock);
 INIT_LIST_HEAD(&stid->sc_cp_list);

 return stid;
out_free:
 kmem_cache_free(slab, stid);
 return NULL;
}

/*
 * Create a unique stateid_t to represent each COPY.
 */

static int nfs4_init_cp_state(struct nfsd_net *nn, copy_stateid_t *stid,
         unsigned char cs_type)
{
 int new_id;

 stid->cs_stid.si_opaque.so_clid.cl_boot = (u32)nn->boot_time;
 stid->cs_stid.si_opaque.so_clid.cl_id = nn->s2s_cp_cl_id;

 idr_preload(GFP_KERNEL);
 spin_lock(&nn->s2s_cp_lock);
 new_id = idr_alloc_cyclic(&nn->s2s_cp_stateids, stid, 0, 0, GFP_NOWAIT);
 stid->cs_stid.si_opaque.so_id = new_id;
 stid->cs_stid.si_generation = 1;
 spin_unlock(&nn->s2s_cp_lock);
 idr_preload_end();
 if (new_id < 0)
  return 0;
 stid->cs_type = cs_type;
 return 1;
}

int nfs4_init_copy_state(struct nfsd_net *nn, struct nfsd4_copy *copy)
{
 return nfs4_init_cp_state(nn, ©->cp_stateid, NFS4_COPY_STID);
}

struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
           struct nfs4_stid *p_stid)
{
 struct nfs4_cpntf_state *cps;

 cps = kzalloc(sizeof(struct nfs4_cpntf_state), GFP_KERNEL);
 if (!cps)
  return NULL;
 cps->cpntf_time = ktime_get_boottime_seconds();
 refcount_set(&cps->cp_stateid.cs_count, 1);
 if (!nfs4_init_cp_state(nn, &cps->cp_stateid, NFS4_COPYNOTIFY_STID))
  goto out_free;
 spin_lock(&nn->s2s_cp_lock);
 list_add(&cps->cp_list, &p_stid->sc_cp_list);
 spin_unlock(&nn->s2s_cp_lock);
 return cps;
out_free:
 kfree(cps);
 return NULL;
}

void nfs4_free_copy_state(struct nfsd4_copy *copy)
{
 struct nfsd_net *nn;

 if (copy->cp_stateid.cs_type != NFS4_COPY_STID)
  return;
 nn = net_generic(copy->cp_clp->net, nfsd_net_id);
 spin_lock(&nn->s2s_cp_lock);
 idr_remove(&nn->s2s_cp_stateids,
     copy->cp_stateid.cs_stid.si_opaque.so_id);
 spin_unlock(&nn->s2s_cp_lock);
}

static void nfs4_free_cpntf_statelist(struct net *net, struct nfs4_stid *stid)
{
 struct nfs4_cpntf_state *cps;
 struct nfsd_net *nn;

 nn = net_generic(net, nfsd_net_id);
 spin_lock(&nn->s2s_cp_lock);
 while (!list_empty(&stid->sc_cp_list)) {
  cps = list_first_entry(&stid->sc_cp_list,
           struct nfs4_cpntf_state, cp_list);
  _free_cpntf_state_locked(nn, cps);
 }
 spin_unlock(&nn->s2s_cp_lock);
}

static struct nfs4_ol_stateid * nfs4_alloc_open_stateid(struct nfs4_client *clp)
{
 struct nfs4_stid *stid;

 stid = nfs4_alloc_stid(clp, stateid_slab, nfs4_free_ol_stateid);
 if (!stid)
  return NULL;

 return openlockstateid(stid);
}

/*
 * As the sc_free callback of deleg, this may be called by nfs4_put_stid
 * in nfsd_break_one_deleg.
 * Considering nfsd_break_one_deleg is called with the flc->flc_lock held,
 * this function mustn't ever sleep.
 */

static void nfs4_free_deleg(struct nfs4_stid *stid)
{
 struct nfs4_delegation *dp = delegstateid(stid);

 WARN_ON_ONCE(!list_empty(&stid->sc_cp_list));
 WARN_ON_ONCE(!list_empty(&dp->dl_perfile));
 WARN_ON_ONCE(!list_empty(&dp->dl_perclnt));
 WARN_ON_ONCE(!list_empty(&dp->dl_recall_lru));
 kmem_cache_free(deleg_slab, stid);
 atomic_long_dec(&num_delegations);
}

/*
 * When we recall a delegation, we should be careful not to hand it
 * out again straight away.
 * To ensure this we keep a pair of bloom filters ('new' and 'old')
 * in which the filehandles of recalled delegations are "stored".
 * If a filehandle appear in either filter, a delegation is blocked.
 * When a delegation is recalled, the filehandle is stored in the "new"
 * filter.
 * Every 30 seconds we swap the filters and clear the "new" one,
 * unless both are empty of course.  This results in delegations for a
 * given filehandle being blocked for between 30 and 60 seconds.
 *
 * Each filter is 256 bits.  We hash the filehandle to 32bit and use the
 * low 3 bytes as hash-table indices.
 *
 * 'blocked_delegations_lock', which is always taken in block_delegations(),
 * is used to manage concurrent access.  Testing does not need the lock
 * except when swapping the two filters.
 */

static DEFINE_SPINLOCK(blocked_delegations_lock);
static struct bloom_pair {
 int entries, old_entries;
 time64_t swap_time;
 int new/* index into 'set' */
 DECLARE_BITMAP(set[2], 256);
} blocked_delegations;

static int delegation_blocked(struct knfsd_fh *fh)
{
 u32 hash;
 struct bloom_pair *bd = &blocked_delegations;

 if (bd->entries == 0)
  return 0;
 if (ktime_get_seconds() - bd->swap_time > 30) {
  spin_lock(&blocked_delegations_lock);
  if (ktime_get_seconds() - bd->swap_time > 30) {
   bd->entries -= bd->old_entries;
   bd->old_entries = bd->entries;
   bd->new = 1-bd->new;
   memset(bd->set[bd->new], 0,
          sizeof(bd->set[0]));
   bd->swap_time = ktime_get_seconds();
  }
  spin_unlock(&blocked_delegations_lock);
 }
 hash = jhash(&fh->fh_raw, fh->fh_size, 0);
 if (test_bit(hash&255, bd->set[0]) &&
     test_bit((hash>>8)&255, bd->set[0]) &&
     test_bit((hash>>16)&255, bd->set[0]))
  return 1;

 if (test_bit(hash&255, bd->set[1]) &&
     test_bit((hash>>8)&255, bd->set[1]) &&
     test_bit((hash>>16)&255, bd->set[1]))
  return 1;

 return 0;
}

static void block_delegations(struct knfsd_fh *fh)
{
 u32 hash;
 struct bloom_pair *bd = &blocked_delegations;

 hash = jhash(&fh->fh_raw, fh->fh_size, 0);

 spin_lock(&blocked_delegations_lock);
 __set_bit(hash&255, bd->set[bd->new]);
 __set_bit((hash>>8)&255, bd->set[bd->new]);
 __set_bit((hash>>16)&255, bd->set[bd->new]);
 if (bd->entries == 0)
  bd->swap_time = ktime_get_seconds();
 bd->entries += 1;
 spin_unlock(&blocked_delegations_lock);
}

static struct nfs4_delegation *
alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
   struct nfs4_clnt_odstate *odstate, u32 dl_type)
{
 struct nfs4_delegation *dp;
 struct nfs4_stid *stid;
 long n;

 dprintk("NFSD alloc_init_deleg\n");
 n = atomic_long_inc_return(&num_delegations);
 if (n < 0 || n > max_delegations)
  goto out_dec;
 if (delegation_blocked(&fp->fi_fhandle))
  goto out_dec;
 stid = nfs4_alloc_stid(clp, deleg_slab, nfs4_free_deleg);
 if (stid == NULL)
  goto out_dec;
 dp = delegstateid(stid);

 /*
 * delegation seqid's are never incremented.  The 4.1 special
 * meaning of seqid 0 isn't meaningful, really, but let's avoid
 * 0 anyway just for consistency and use 1:
 */

 dp->dl_stid.sc_stateid.si_generation = 1;
 INIT_LIST_HEAD(&dp->dl_perfile);
 INIT_LIST_HEAD(&dp->dl_perclnt);
 INIT_LIST_HEAD(&dp->dl_recall_lru);
 dp->dl_clnt_odstate = odstate;
 get_clnt_odstate(odstate);
 dp->dl_type = dl_type;
 dp->dl_retries = 1;
 dp->dl_recalled = false;
 nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
        &nfsd4_cb_recall_ops, NFSPROC4_CLNT_CB_RECALL);
 nfsd4_init_cb(&dp->dl_cb_fattr.ncf_getattr, dp->dl_stid.sc_client,
   &nfsd4_cb_getattr_ops, NFSPROC4_CLNT_CB_GETATTR);
 dp->dl_cb_fattr.ncf_file_modified = false;
 get_nfs4_file(fp);
 dp->dl_stid.sc_file = fp;
 return dp;
out_dec:
 atomic_long_dec(&num_delegations);
 return NULL;
}

void
nfs4_put_stid(struct nfs4_stid *s)
{
 struct nfs4_file *fp = s->sc_file;
 struct nfs4_client *clp = s->sc_client;

 might_lock(&clp->cl_lock);

 if (!refcount_dec_and_lock(&s->sc_count, &clp->cl_lock)) {
  wake_up_all(&close_wq);
  return;
 }
 idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
 if (s->sc_status & SC_STATUS_ADMIN_REVOKED)
  atomic_dec(&s->sc_client->cl_admin_revoked);
 nfs4_free_cpntf_statelist(clp->net, s);
 spin_unlock(&clp->cl_lock);
 s->sc_free(s);
 if (fp)
  put_nfs4_file(fp);
}

void
nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid)
{
 stateid_t *src = &stid->sc_stateid;

 spin_lock(&stid->sc_lock);
 if (unlikely(++src->si_generation == 0))
  src->si_generation = 1;
 memcpy(dst, src, sizeof(*dst));
 spin_unlock(&stid->sc_lock);
}

static void put_deleg_file(struct nfs4_file *fp)
{
 struct nfsd_file *rnf = NULL;
 struct nfsd_file *nf = NULL;

 spin_lock(&fp->fi_lock);
 if (--fp->fi_delegees == 0) {
  swap(nf, fp->fi_deleg_file);
  swap(rnf, fp->fi_rdeleg_file);
 }
 spin_unlock(&fp->fi_lock);

 if (nf)
  nfsd_file_put(nf);
 if (rnf)
  nfs4_file_put_access(fp, NFS4_SHARE_ACCESS_READ);
}

static void nfs4_unlock_deleg_lease(struct nfs4_delegation *dp)
{
 struct nfs4_file *fp = dp->dl_stid.sc_file;
 struct nfsd_file *nf = fp->fi_deleg_file;

 WARN_ON_ONCE(!fp->fi_delegees);

 kernel_setlease(nf->nf_file, F_UNLCK, NULL, (void **)&dp);
 put_deleg_file(fp);
}

static void destroy_unhashed_deleg(struct nfs4_delegation *dp)
{
 put_clnt_odstate(dp->dl_clnt_odstate);
 nfs4_unlock_deleg_lease(dp);
 nfs4_put_stid(&dp->dl_stid);
}

/**
 * nfs4_delegation_exists - Discover if this delegation already exists
 * @clp:     a pointer to the nfs4_client we're granting a delegation to
 * @fp:      a pointer to the nfs4_file we're granting a delegation on
 *
 * Return:
 *      On success: true iff an existing delegation is found
 */


static bool
nfs4_delegation_exists(struct nfs4_client *clp, struct nfs4_file *fp)
{
 struct nfs4_delegation *searchdp = NULL;
 struct nfs4_client *searchclp = NULL;

 lockdep_assert_held(&state_lock);
 lockdep_assert_held(&fp->fi_lock);

 list_for_each_entry(searchdp, &fp->fi_delegations, dl_perfile) {
  searchclp = searchdp->dl_stid.sc_client;
  if (clp == searchclp) {
   return true;
  }
 }
 return false;
}

/**
 * hash_delegation_locked - Add a delegation to the appropriate lists
 * @dp:     a pointer to the nfs4_delegation we are adding.
 * @fp:     a pointer to the nfs4_file we're granting a delegation on
 *
 * Return:
 *      On success: NULL if the delegation was successfully hashed.
 *
 *      On error: -EAGAIN if one was previously granted to this
 *                 nfs4_client for this nfs4_file. Delegation is not hashed.
 *
 */


static int
hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)
{
 struct nfs4_client *clp = dp->dl_stid.sc_client;

 lockdep_assert_held(&state_lock);
 lockdep_assert_held(&fp->fi_lock);
 lockdep_assert_held(&clp->cl_lock);

 if (nfs4_delegation_exists(clp, fp))
  return -EAGAIN;
 refcount_inc(&dp->dl_stid.sc_count);
 dp->dl_stid.sc_type = SC_TYPE_DELEG;
 list_add(&dp->dl_perfile, &fp->fi_delegations);
 list_add(&dp->dl_perclnt, &clp->cl_delegations);
 return 0;
}

static bool delegation_hashed(struct nfs4_delegation *dp)
{
 return !(list_empty(&dp->dl_perfile));
}

static bool
unhash_delegation_locked(struct nfs4_delegation *dp, unsigned short statusmask)
{
 struct nfs4_file *fp = dp->dl_stid.sc_file;

 lockdep_assert_held(&state_lock);

 if (!delegation_hashed(dp))
  return false;

 if (statusmask == SC_STATUS_REVOKED &&
     dp->dl_stid.sc_client->cl_minorversion == 0)
  statusmask = SC_STATUS_CLOSED;
 dp->dl_stid.sc_status |= statusmask;
 if (statusmask & SC_STATUS_ADMIN_REVOKED)
  atomic_inc(&dp->dl_stid.sc_client->cl_admin_revoked);

 /* Ensure that deleg break won't try to requeue it */
 ++dp->dl_time;
 spin_lock(&fp->fi_lock);
 list_del_init(&dp->dl_perclnt);
 list_del_init(&dp->dl_recall_lru);
 list_del_init(&dp->dl_perfile);
 spin_unlock(&fp->fi_lock);
 return true;
}

static void destroy_delegation(struct nfs4_delegation *dp)
{
 bool unhashed;

 spin_lock(&state_lock);
 unhashed = unhash_delegation_locked(dp, SC_STATUS_CLOSED);
 spin_unlock(&state_lock);
 if (unhashed)
  destroy_unhashed_deleg(dp);
}

/**
 * revoke_delegation - perform nfs4 delegation structure cleanup
 * @dp: pointer to the delegation
 *
 * This function assumes that it's called either from the administrative
 * interface (nfsd4_revoke_states()) that's revoking a specific delegation
 * stateid or it's called from a laundromat thread (nfsd4_landromat()) that
 * determined that this specific state has expired and needs to be revoked
 * (both mark state with the appropriate stid sc_status mode). It is also
 * assumed that a reference was taken on the @dp state.
 *
 * If this function finds that the @dp state is SC_STATUS_FREED it means
 * that a FREE_STATEID operation for this stateid has been processed and
 * we can proceed to removing it from recalled list. However, if @dp state
 * isn't marked SC_STATUS_FREED, it means we need place it on the cl_revoked
 * list and wait for the FREE_STATEID to arrive from the client. At the same
 * time, we need to mark it as SC_STATUS_FREEABLE to indicate to the
 * nfsd4_free_stateid() function that this stateid has already been added
 * to the cl_revoked list and that nfsd4_free_stateid() is now responsible
 * for removing it from the list. Inspection of where the delegation state
 * in the revocation process is protected by the clp->cl_lock.
 */

static void revoke_delegation(struct nfs4_delegation *dp)
{
 struct nfs4_client *clp = dp->dl_stid.sc_client;

 WARN_ON(!list_empty(&dp->dl_recall_lru));
 WARN_ON_ONCE(dp->dl_stid.sc_client->cl_minorversion > 0 &&
       !(dp->dl_stid.sc_status &
       (SC_STATUS_REVOKED | SC_STATUS_ADMIN_REVOKED)));

 trace_nfsd_stid_revoke(&dp->dl_stid);

 spin_lock(&clp->cl_lock);
 if (dp->dl_stid.sc_status & SC_STATUS_FREED) {
  list_del_init(&dp->dl_recall_lru);
  goto out;
 }
 list_add(&dp->dl_recall_lru, &clp->cl_revoked);
 dp->dl_stid.sc_status |= SC_STATUS_FREEABLE;
out:
 spin_unlock(&clp->cl_lock);
 destroy_unhashed_deleg(dp);
}

/*
 * SETCLIENTID state
 */


static unsigned int clientid_hashval(u32 id)
{
 return id & CLIENT_HASH_MASK;
}

static unsigned int clientstr_hashval(struct xdr_netobj name)
{
 return opaque_hashval(name.data, 8) & CLIENT_HASH_MASK;
}

/*
 * A stateid that had a deny mode associated with it is being released
 * or downgraded. Recalculate the deny mode on the file.
 */

static void
recalculate_deny_mode(struct nfs4_file *fp)
{
 struct nfs4_ol_stateid *stp;
 u32 old_deny;

 spin_lock(&fp->fi_lock);
 old_deny = fp->fi_share_deny;
 fp->fi_share_deny = 0;
 list_for_each_entry(stp, &fp->fi_stateids, st_perfile) {
  fp->fi_share_deny |= bmap_to_share_mode(stp->st_deny_bmap);
  if (fp->fi_share_deny == old_deny)
   break;
 }
 spin_unlock(&fp->fi_lock);
}

static void
reset_union_bmap_deny(u32 deny, struct nfs4_ol_stateid *stp)
{
 int i;
 bool change = false;

 for (i = 1; i < 4; i++) {
  if ((i & deny) != i) {
   change = true;
   clear_deny(i, stp);
  }
 }

 /* Recalculate per-file deny mode if there was a change */
 if (change)
  recalculate_deny_mode(stp->st_stid.sc_file);
}

/* release all access and file references for a given stateid */
static void
release_all_access(struct nfs4_ol_stateid *stp)
{
 int i;
 struct nfs4_file *fp = stp->st_stid.sc_file;

 if (fp && stp->st_deny_bmap != 0)
  recalculate_deny_mode(fp);

 for (i = 1; i < 4; i++) {
  if (test_access(i, stp))
   nfs4_file_put_access(stp->st_stid.sc_file, i);
  clear_access(i, stp);
 }
}

static inline void nfs4_free_stateowner(struct nfs4_stateowner *sop)
{
 kfree(sop->so_owner.data);
 sop->so_ops->so_free(sop);
}

static void nfs4_put_stateowner(struct nfs4_stateowner *sop)
{
 struct nfs4_client *clp = sop->so_client;

 might_lock(&clp->cl_lock);

 if (!atomic_dec_and_lock(&sop->so_count, &clp->cl_lock))
  return;
 sop->so_ops->so_unhash(sop);
 spin_unlock(&clp->cl_lock);
 nfs4_free_stateowner(sop);
}

static bool
nfs4_ol_stateid_unhashed(const struct nfs4_ol_stateid *stp)
{
 return list_empty(&stp->st_perfile);
}

static bool unhash_ol_stateid(struct nfs4_ol_stateid *stp)
{
 struct nfs4_file *fp = stp->st_stid.sc_file;

 lockdep_assert_held(&stp->st_stateowner->so_client->cl_lock);

 if (list_empty(&stp->st_perfile))
  return false;

 spin_lock(&fp->fi_lock);
 list_del_init(&stp->st_perfile);
 spin_unlock(&fp->fi_lock);
 list_del(&stp->st_perstateowner);
 return true;
}

static void nfs4_free_ol_stateid(struct nfs4_stid *stid)
{
 struct nfs4_ol_stateid *stp = openlockstateid(stid);

 put_clnt_odstate(stp->st_clnt_odstate);
 release_all_access(stp);
 if (stp->st_stateowner)
  nfs4_put_stateowner(stp->st_stateowner);
 if (!list_empty(&stid->sc_cp_list))
  nfs4_free_cpntf_statelist(stid->sc_client->net, stid);
 kmem_cache_free(stateid_slab, stid);
}

static void nfs4_free_lock_stateid(struct nfs4_stid *stid)
{
 struct nfs4_ol_stateid *stp = openlockstateid(stid);
 struct nfs4_lockowner *lo = lockowner(stp->st_stateowner);
 struct nfsd_file *nf;

 nf = find_any_file(stp->st_stid.sc_file);
 if (nf) {
  get_file(nf->nf_file);
  filp_close(nf->nf_file, (fl_owner_t)lo);
  nfsd_file_put(nf);
 }
 nfs4_free_ol_stateid(stid);
}

/*
 * Put the persistent reference to an already unhashed generic stateid, while
 * holding the cl_lock. If it's the last reference, then put it onto the
 * reaplist for later destruction.
 */

static void put_ol_stateid_locked(struct nfs4_ol_stateid *stp,
           struct list_head *reaplist)
{
 struct nfs4_stid *s = &stp->st_stid;
 struct nfs4_client *clp = s->sc_client;

 lockdep_assert_held(&clp->cl_lock);

 WARN_ON_ONCE(!list_empty(&stp->st_locks));

 if (!refcount_dec_and_test(&s->sc_count)) {
  wake_up_all(&close_wq);
  return;
 }

 idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
 if (s->sc_status & SC_STATUS_ADMIN_REVOKED)
  atomic_dec(&s->sc_client->cl_admin_revoked);
 list_add(&stp->st_locks, reaplist);
}

static bool unhash_lock_stateid(struct nfs4_ol_stateid *stp)
{
 lockdep_assert_held(&stp->st_stid.sc_client->cl_lock);

 if (!unhash_ol_stateid(stp))
  return false;
 list_del_init(&stp->st_locks);
 stp->st_stid.sc_status |= SC_STATUS_CLOSED;
 return true;
}

static void release_lock_stateid(struct nfs4_ol_stateid *stp)
{
 struct nfs4_client *clp = stp->st_stid.sc_client;
 bool unhashed;

 spin_lock(&clp->cl_lock);
 unhashed = unhash_lock_stateid(stp);
 spin_unlock(&clp->cl_lock);
 if (unhashed)
  nfs4_put_stid(&stp->st_stid);
}

static void unhash_lockowner_locked(struct nfs4_lockowner *lo)
{
 struct nfs4_client *clp = lo->lo_owner.so_client;

 lockdep_assert_held(&clp->cl_lock);

 list_del_init(&lo->lo_owner.so_strhash);
}

/*
 * Free a list of generic stateids that were collected earlier after being
 * fully unhashed.
 */

static void
free_ol_stateid_reaplist(struct list_head *reaplist)
{
 struct nfs4_ol_stateid *stp;
 struct nfs4_file *fp;

 might_sleep();

 while (!list_empty(reaplist)) {
  stp = list_first_entry(reaplist, struct nfs4_ol_stateid,
           st_locks);
  list_del(&stp->st_locks);
  fp = stp->st_stid.sc_file;
  stp->st_stid.sc_free(&stp->st_stid);
  if (fp)
   put_nfs4_file(fp);
 }
}

static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp,
           struct list_head *reaplist)
{
 struct nfs4_ol_stateid *stp;

 lockdep_assert_held(&open_stp->st_stid.sc_client->cl_lock);

 while (!list_empty(&open_stp->st_locks)) {
  stp = list_entry(open_stp->st_locks.next,
    struct nfs4_ol_stateid, st_locks);
  unhash_lock_stateid(stp);
  put_ol_stateid_locked(stp, reaplist);
 }
}

static bool unhash_open_stateid(struct nfs4_ol_stateid *stp,
    struct list_head *reaplist)
{
 lockdep_assert_held(&stp->st_stid.sc_client->cl_lock);

 if (!unhash_ol_stateid(stp))
  return false;
 release_open_stateid_locks(stp, reaplist);
 return true;
}

static void release_open_stateid(struct nfs4_ol_stateid *stp)
{
 LIST_HEAD(reaplist);

 spin_lock(&stp->st_stid.sc_client->cl_lock);
 stp->st_stid.sc_status |= SC_STATUS_CLOSED;
 if (unhash_open_stateid(stp, &reaplist))
  put_ol_stateid_locked(stp, &reaplist);
 spin_unlock(&stp->st_stid.sc_client->cl_lock);
 free_ol_stateid_reaplist(&reaplist);
}

static bool nfs4_openowner_unhashed(struct nfs4_openowner *oo)
{
 lockdep_assert_held(&oo->oo_owner.so_client->cl_lock);

 return list_empty(&oo->oo_owner.so_strhash) &&
  list_empty(&oo->oo_perclient);
}

static void unhash_openowner_locked(struct nfs4_openowner *oo)
{
 struct nfs4_client *clp = oo->oo_owner.so_client;

 lockdep_assert_held(&clp->cl_lock);

 list_del_init(&oo->oo_owner.so_strhash);
 list_del_init(&oo->oo_perclient);
}

static void release_last_closed_stateid(struct nfs4_openowner *oo)
{
 struct nfsd_net *nn = net_generic(oo->oo_owner.so_client->net,
       nfsd_net_id);
 struct nfs4_ol_stateid *s;

 spin_lock(&nn->client_lock);
 s = oo->oo_last_closed_stid;
 if (s) {
  list_del_init(&oo->oo_close_lru);
  oo->oo_last_closed_stid = NULL;
 }
 spin_unlock(&nn->client_lock);
 if (s)
  nfs4_put_stid(&s->st_stid);
}

static void release_openowner(struct nfs4_openowner *oo)
{
 struct nfs4_ol_stateid *stp;
 struct nfs4_client *clp = oo->oo_owner.so_client;
 LIST_HEAD(reaplist);

 spin_lock(&clp->cl_lock);
 unhash_openowner_locked(oo);
 while (!list_empty(&oo->oo_owner.so_stateids)) {
  stp = list_first_entry(&oo->oo_owner.so_stateids,
    struct nfs4_ol_stateid, st_perstateowner);
  if (unhash_open_stateid(stp, &reaplist))
   put_ol_stateid_locked(stp, &reaplist);
 }
 spin_unlock(&clp->cl_lock);
 free_ol_stateid_reaplist(&reaplist);
 release_last_closed_stateid(oo);
 nfs4_put_stateowner(&oo->oo_owner);
}

static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp,
       struct super_block *sb,
       unsigned int sc_types)
{
 unsigned long id, tmp;
 struct nfs4_stid *stid;

 spin_lock(&clp->cl_lock);
 idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id)
  if ((stid->sc_type & sc_types) &&
      stid->sc_status == 0 &&
      stid->sc_file->fi_inode->i_sb == sb) {
   refcount_inc(&stid->sc_count);
   break;
  }
 spin_unlock(&clp->cl_lock);
 return stid;
}

/**
 * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem
 * @net:  used to identify instance of nfsd (there is one per net namespace)
 * @sb:   super_block used to identify target filesystem
 *
 * All nfs4 states (open, lock, delegation, layout) held by the server instance
 * and associated with a file on the given filesystem will be revoked resulting
 * in any files being closed and so all references from nfsd to the filesystem
 * being released.  Thus nfsd will no longer prevent the filesystem from being
 * unmounted.
 *
 * The clients which own the states will subsequently being notified that the
 * states have been "admin-revoked".
 */

void nfsd4_revoke_states(struct net *net, struct super_block *sb)
{
 struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 unsigned int idhashval;
 unsigned int sc_types;

 sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT;

 spin_lock(&nn->client_lock);
 for (idhashval = 0; idhashval < CLIENT_HASH_MASK; idhashval++) {
  struct list_head *head = &nn->conf_id_hashtbl[idhashval];
  struct nfs4_client *clp;
 retry:
  list_for_each_entry(clp, head, cl_idhash) {
   struct nfs4_stid *stid = find_one_sb_stid(clp, sb,
          sc_types);
   if (stid) {
    struct nfs4_ol_stateid *stp;
    struct nfs4_delegation *dp;
    struct nfs4_layout_stateid *ls;

    spin_unlock(&nn->client_lock);
    switch (stid->sc_type) {
    case SC_TYPE_OPEN:
     stp = openlockstateid(stid);
     mutex_lock_nested(&stp->st_mutex,
         OPEN_STATEID_MUTEX);

     spin_lock(&clp->cl_lock);
     if (stid->sc_status == 0) {
      stid->sc_status |=
       SC_STATUS_ADMIN_REVOKED;
      atomic_inc(&clp->cl_admin_revoked);
      spin_unlock(&clp->cl_lock);
      release_all_access(stp);
     } else
      spin_unlock(&clp->cl_lock);
     mutex_unlock(&stp->st_mutex);
     break;
    case SC_TYPE_LOCK:
     stp = openlockstateid(stid);
     mutex_lock_nested(&stp->st_mutex,
         LOCK_STATEID_MUTEX);
     spin_lock(&clp->cl_lock);
     if (stid->sc_status == 0) {
      struct nfs4_lockowner *lo =
       lockowner(stp->st_stateowner);
      struct nfsd_file *nf;

      stid->sc_status |=
       SC_STATUS_ADMIN_REVOKED;
      atomic_inc(&clp->cl_admin_revoked);
      spin_unlock(&clp->cl_lock);
      nf = find_any_file(stp->st_stid.sc_file);
      if (nf) {
       get_file(nf->nf_file);
       filp_close(nf->nf_file,
           (fl_owner_t)lo);
       nfsd_file_put(nf);
      }
      release_all_access(stp);
     } else
      spin_unlock(&clp->cl_lock);
     mutex_unlock(&stp->st_mutex);
     break;
    case SC_TYPE_DELEG:
     refcount_inc(&stid->sc_count);
     dp = delegstateid(stid);
     spin_lock(&state_lock);
     if (!unhash_delegation_locked(
          dp, SC_STATUS_ADMIN_REVOKED))
      dp = NULL;
     spin_unlock(&state_lock);
     if (dp)
      revoke_delegation(dp);
     break;
    case SC_TYPE_LAYOUT:
     ls = layoutstateid(stid);
     nfsd4_close_layout(ls);
     break;
    }
    nfs4_put_stid(stid);
    spin_lock(&nn->client_lock);
    if (clp->cl_minorversion == 0)
     /* Allow cleanup after a lease period.
 * store_release ensures cleanup will
 * see any newly revoked states if it
 * sees the time updated.
 */

     nn->nfs40_last_revoke =
      ktime_get_boottime_seconds();
    goto retry;
   }
  }
 }
 spin_unlock(&nn->client_lock);
}

static inline int
hash_sessionid(struct nfs4_sessionid *sessionid)
{
 struct nfsd4_sessionid *sid = (struct nfsd4_sessionid *)sessionid;

 return sid->sequence % SESSION_HASH_SIZE;
}

#ifdef CONFIG_SUNRPC_DEBUG
static inline void
dump_sessionid(const char *fn, struct nfs4_sessionid *sessionid)
{
 u32 *ptr = (u32 *)(&sessionid->data[0]);
 dprintk("%s: %u:%u:%u:%u\n", fn, ptr[0], ptr[1], ptr[2], ptr[3]);
}
#else
static inline void
dump_sessionid(const char *fn, struct nfs4_sessionid *sessionid)
{
}
#endif

/*
 * Bump the seqid on cstate->replay_owner, and clear replay_owner if it
 * won't be used for replay.
 */

void nfsd4_bump_seqid(struct nfsd4_compound_state *cstate, __be32 nfserr)
{
 struct nfs4_stateowner *so = cstate->replay_owner;

 if (nfserr == nfserr_replay_me)
  return;

 if (!seqid_mutating_err(ntohl(nfserr))) {
  nfsd4_cstate_clear_replay(cstate);
  return;
 }
 if (!so)
  return;
 if (so->so_is_open_owner)
  release_last_closed_stateid(openowner(so));
 so->so_seqid++;
 return;
}

static void
gen_sessionid(struct nfsd4_session *ses)
{
 struct nfs4_client *clp = ses->se_client;
 struct nfsd4_sessionid *sid;

 sid = (struct nfsd4_sessionid *)ses->se_sessionid.data;
 sid->clientid = clp->cl_clientid;
 sid->sequence = current_sessionid++;
 sid->reserved = 0;
}

/*
 * The protocol defines ca_maxresponssize_cached to include the size of
 * the rpc header, but all we need to cache is the data starting after
 * the end of the initial SEQUENCE operation--the rest we regenerate
 * each time.  Therefore we can advertise a ca_maxresponssize_cached
 * value that is the number of bytes in our cache plus a few additional
 * bytes.  In order to stay on the safe side, and not promise more than
 * we can cache, those additional bytes must be the minimum possible: 24
 * bytes of rpc header (xid through accept state, with AUTH_NULL
 * verifier), 12 for the compound header (with zero-length tag), and 44
 * for the SEQUENCE op response:
 */

#define NFSD_MIN_HDR_SEQ_SZ  (24 + 12 + 44)

static struct shrinker *nfsd_slot_shrinker;
static DEFINE_SPINLOCK(nfsd_session_list_lock);
static LIST_HEAD(nfsd_session_list);
/* The sum of "target_slots-1" on every session.  The shrinker can push this
 * down, though it can take a little while for the memory to actually
 * be freed.  The "-1" is because we can never free slot 0 while the
 * session is active.
 */

static atomic_t nfsd_total_target_slots = ATOMIC_INIT(0);

static void
free_session_slots(struct nfsd4_session *ses, int from)
{
 int i;

 if (from >= ses->se_fchannel.maxreqs)
  return;

 for (i = from; i < ses->se_fchannel.maxreqs; i++) {
  struct nfsd4_slot *slot = xa_load(&ses->se_slots, i);

  /*
 * Save the seqid in case we reactivate this slot.
 * This will never require a memory allocation so GFP
 * flag is irrelevant
 */

  xa_store(&ses->se_slots, i, xa_mk_value(slot->sl_seqid), 0);
  free_svc_cred(&slot->sl_cred);
  kfree(slot);
 }
 ses->se_fchannel.maxreqs = from;
 if (ses->se_target_maxslots > from) {
  int new_target = from ?: 1;
  atomic_sub(ses->se_target_maxslots - new_target, &nfsd_total_target_slots);
  ses->se_target_maxslots = new_target;
 }
}

/**
 * reduce_session_slots - reduce the target max-slots of a session if possible
 * @ses:  The session to affect
 * @dec:  how much to decrease the target by
 *
 * This interface can be used by a shrinker to reduce the target max-slots
 * for a session so that some slots can eventually be freed.
 * It uses spin_trylock() as it may be called in a context where another
 * spinlock is held that has a dependency on client_lock.  As shrinkers are
 * best-effort, skiping a session is client_lock is already held has no
 * great coast
 *
 * Return value:
 *   The number of slots that the target was reduced by.
 */

static int
reduce_session_slots(struct nfsd4_session *ses, int dec)
{
 struct nfsd_net *nn = net_generic(ses->se_client->net,
       nfsd_net_id);
 int ret = 0;

 if (ses->se_target_maxslots <= 1)
  return ret;
 if (!spin_trylock(&nn->client_lock))
  return ret;
 ret = min(dec, ses->se_target_maxslots-1);
 ses->se_target_maxslots -= ret;
 atomic_sub(ret, &nfsd_total_target_slots);
 ses->se_slot_gen += 1;
 if (ses->se_slot_gen == 0) {
  int i;
  ses->se_slot_gen = 1;
  for (i = 0; i < ses->se_fchannel.maxreqs; i++) {
   struct nfsd4_slot *slot = xa_load(&ses->se_slots, i);
   slot->sl_generation = 0;
  }
 }
 spin_unlock(&nn->client_lock);
 return ret;
}

static struct nfsd4_slot *nfsd4_alloc_slot(struct nfsd4_channel_attrs *fattrs,
        int index, gfp_t gfp)
{
 struct nfsd4_slot *slot;
 size_t size;

 /*
 * The RPC and NFS session headers are never saved in
 * the slot reply cache buffer.
 */

 size = fattrs->maxresp_cached < NFSD_MIN_HDR_SEQ_SZ ?
  0 : fattrs->maxresp_cached - NFSD_MIN_HDR_SEQ_SZ;

 slot = kzalloc(struct_size(slot, sl_data, size), gfp);
 if (!slot)
  return NULL;
 slot->sl_index = index;
 return slot;
}

static struct nfsd4_session *alloc_session(struct nfsd4_channel_attrs *fattrs,
        struct nfsd4_channel_attrs *battrs)
{
 int numslots = fattrs->maxreqs;
 struct nfsd4_session *new;
 struct nfsd4_slot *slot;
 int i;

 new = kzalloc(sizeof(*new), GFP_KERNEL);
 if (!new)
  return NULL;
 xa_init(&new->se_slots);

 slot = nfsd4_alloc_slot(fattrs, 0, GFP_KERNEL);
 if (!slot || xa_is_err(xa_store(&new->se_slots, 0, slot, GFP_KERNEL)))
  goto out_free;

 for (i = 1; i < numslots; i++) {
  const gfp_t gfp = GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN;
  slot = nfsd4_alloc_slot(fattrs, i, gfp);
  if (!slot)
   break;
  if (xa_is_err(xa_store(&new->se_slots, i, slot, gfp))) {
   kfree(slot);
   break;
  }
 }
 fattrs->maxreqs = i;
 memcpy(&new->se_fchannel, fattrs, sizeof(struct nfsd4_channel_attrs));
 new->se_target_maxslots = i;
 atomic_add(i - 1, &nfsd_total_target_slots);
 new->se_cb_slot_avail = ~0U;
 new->se_cb_highest_slot = min(battrs->maxreqs - 1,
          NFSD_BC_SLOT_TABLE_SIZE - 1);
 spin_lock_init(&new->se_lock);
 return new;
out_free:
 kfree(slot);
 xa_destroy(&new->se_slots);
 kfree(new);
 return NULL;
}

static void free_conn(struct nfsd4_conn *c)
{
 svc_xprt_put(c->cn_xprt);
 kfree(c);
}

static void nfsd4_conn_lost(struct svc_xpt_user *u)
{
 struct nfsd4_conn *c = container_of(u, struct nfsd4_conn, cn_xpt_user);
 struct nfs4_client *clp = c->cn_session->se_client;

 trace_nfsd_cb_lost(clp);

 spin_lock(&clp->cl_lock);
 if (!list_empty(&c->cn_persession)) {
  list_del(&c->cn_persession);
  free_conn(c);
 }
 nfsd4_probe_callback(clp);
 spin_unlock(&clp->cl_lock);
}

static struct nfsd4_conn *alloc_conn(struct svc_rqst *rqstp, u32 flags)
{
 struct nfsd4_conn *conn;

 conn = kmalloc(sizeof(struct nfsd4_conn), GFP_KERNEL);
 if (!conn)
  return NULL;
 svc_xprt_get(rqstp->rq_xprt);
 conn->cn_xprt = rqstp->rq_xprt;
 conn->cn_flags = flags;
 INIT_LIST_HEAD(&conn->cn_xpt_user.list);
 return conn;
}

static void __nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
{
 conn->cn_session = ses;
 list_add(&conn->cn_persession, &ses->se_conns);
}

static void nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
{
 struct nfs4_client *clp = ses->se_client;

 spin_lock(&clp->cl_lock);
 __nfsd4_hash_conn(conn, ses);
 spin_unlock(&clp->cl_lock);
}

static int nfsd4_register_conn(struct nfsd4_conn *conn)
{
 conn->cn_xpt_user.callback = nfsd4_conn_lost;
 return register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user);
}

static void nfsd4_init_conn(struct svc_rqst *rqstp, struct nfsd4_conn *conn, struct nfsd4_session *ses)
{
 int ret;

 nfsd4_hash_conn(conn, ses);
 ret = nfsd4_register_conn(conn);
 if (ret)
  /* oops; xprt is already down: */
  nfsd4_conn_lost(&conn->cn_xpt_user);
 /* We may have gained or lost a callback channel: */
 nfsd4_probe_callback_sync(ses->se_client);
}

static struct nfsd4_conn *alloc_conn_from_crses(struct svc_rqst *rqstp, struct nfsd4_create_session *cses)
{
 u32 dir = NFS4_CDFC4_FORE;

 if (cses->flags & SESSION4_BACK_CHAN)
  dir |= NFS4_CDFC4_BACK;
 return alloc_conn(rqstp, dir);
}

/* must be called under client_lock */
static void nfsd4_del_conns(struct nfsd4_session *s)
{
 struct nfs4_client *clp = s->se_client;
 struct nfsd4_conn *c;

 spin_lock(&clp->cl_lock);
 while (!list_empty(&s->se_conns)) {
  c = list_first_entry(&s->se_conns, struct nfsd4_conn, cn_persession);
  list_del_init(&c->cn_persession);
  spin_unlock(&clp->cl_lock);

  unregister_xpt_user(c->cn_xprt, &c->cn_xpt_user);
  free_conn(c);

  spin_lock(&clp->cl_lock);
 }
 spin_unlock(&clp->cl_lock);
}

static void __free_session(struct nfsd4_session *ses)
{
 free_session_slots(ses, 0);
 xa_destroy(&ses->se_slots);
 kfree(ses);
}

static void free_session(struct nfsd4_session *ses)
{
 nfsd4_del_conns(ses);
 __free_session(ses);
}

static unsigned long
nfsd_slot_count(struct shrinker *s, struct shrink_control *sc)
{
 unsigned long cnt = atomic_read(&nfsd_total_target_slots);

 return cnt ? cnt : SHRINK_EMPTY;
}

static unsigned long
nfsd_slot_scan(struct shrinker *s, struct shrink_control *sc)
{
 struct nfsd4_session *ses;
 unsigned long scanned = 0;
 unsigned long freed = 0;

 spin_lock(&nfsd_session_list_lock);
 list_for_each_entry(ses, &nfsd_session_list, se_all_sessions) {
  freed += reduce_session_slots(ses, 1);
  scanned += 1;
  if (scanned >= sc->nr_to_scan) {
   /* Move starting point for next scan */
   list_move(&nfsd_session_list, &ses->se_all_sessions);
   break;
  }
 }
 spin_unlock(&nfsd_session_list_lock);
 sc->nr_scanned = scanned;
 return freed;
}

static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *newstruct nfs4_client *clp, struct nfsd4_create_session *cses)
{
 int idx;
 struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);

 new->se_client = clp;
 gen_sessionid(new);

 INIT_LIST_HEAD(&new->se_conns);

 atomic_set(&new->se_ref, 0);
 new->se_dead = false;
 new->se_cb_prog = cses->callback_prog;
 new->se_cb_sec = cses->cb_sec;

 for (idx = 0; idx < NFSD_BC_SLOT_TABLE_SIZE; ++idx)
  new->se_cb_seq_nr[idx] = 1;

 idx = hash_sessionid(&new->se_sessionid);
 list_add(&new->se_hash, &nn->sessionid_hashtbl[idx]);
 spin_lock(&clp->cl_lock);
 list_add(&new->se_perclnt, &clp->cl_sessions);
 spin_unlock(&clp->cl_lock);

 spin_lock(&nfsd_session_list_lock);
 list_add_tail(&new->se_all_sessions, &nfsd_session_list);
 spin_unlock(&nfsd_session_list_lock);

 {
  struct sockaddr *sa = svc_addr(rqstp);
  /*
 * This is a little silly; with sessions there's no real
 * use for the callback address.  Use the peer address
 * as a reasonable default for now, but consider fixing
 * the rpc client not to require an address in the
 * future:
 */

  rpc_copy_addr((struct sockaddr *)&clp->cl_cb_conn.cb_addr, sa);
  clp->cl_cb_conn.cb_addrlen = svc_addr_len(sa);
 }
}

/* caller must hold client_lock */
static struct nfsd4_session *
__find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)
{
 struct nfsd4_session *elem;
 int idx;
 struct nfsd_net *nn = net_generic(net, nfsd_net_id);

 lockdep_assert_held(&nn->client_lock);

 dump_sessionid(__func__, sessionid);
 idx = hash_sessionid(sessionid);
 /* Search in the appropriate list */
 list_for_each_entry(elem, &nn->sessionid_hashtbl[idx], se_hash) {
  if (!memcmp(elem->se_sessionid.data, sessionid->data,
       NFS4_MAX_SESSIONID_LEN)) {
   return elem;
  }
 }

 dprintk("%s: session not found\n", __func__);
 return NULL;
}

static struct nfsd4_session *
find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net,
  __be32 *ret)
{
 struct nfsd4_session *session;
 __be32 status = nfserr_badsession;

 session = __find_in_sessionid_hashtbl(sessionid, net);
 if (!session)
  goto out;
 status = nfsd4_get_session_locked(session);
 if (status)
  session = NULL;
out:
 *ret = status;
 return session;
}

/* caller must hold client_lock */
static void
unhash_session(struct nfsd4_session *ses)
{
 struct nfs4_client *clp = ses->se_client;
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 lockdep_assert_held(&nn->client_lock);

 list_del(&ses->se_hash);
 spin_lock(&ses->se_client->cl_lock);
 list_del(&ses->se_perclnt);
 spin_unlock(&ses->se_client->cl_lock);
 spin_lock(&nfsd_session_list_lock);
 list_del(&ses->se_all_sessions);
 spin_unlock(&nfsd_session_list_lock);
}

/* SETCLIENTID and SETCLIENTID_CONFIRM Helper functions */
static int
STALE_CLIENTID(clientid_t *clid, struct nfsd_net *nn)
{
 /*
 * We're assuming the clid was not given out from a boot
 * precisely 2^32 (about 136 years) before this one.  That seems
 * a safe assumption:
 */

 if (clid->cl_boot == (u32)nn->boot_time)
  return 0;
 trace_nfsd_clid_stale(clid);
 return 1;
}

static struct nfs4_client *alloc_client(struct xdr_netobj name,
    struct nfsd_net *nn)
{
 struct nfs4_client *clp;
 int i;

 if (atomic_read(&nn->nfs4_client_count) >= nn->nfs4_max_clients &&
     atomic_read(&nn->nfsd_courtesy_clients) > 0)
  mod_delayed_work(laundry_wq, &nn->laundromat_work, 0);

 clp = kmem_cache_zalloc(client_slab, GFP_KERNEL);
 if (clp == NULL)
  return NULL;
 xdr_netobj_dup(&clp->cl_name, &name, GFP_KERNEL);
 if (clp->cl_name.data == NULL)
  goto err_no_name;
 clp->cl_ownerstr_hashtbl = kmalloc_array(OWNER_HASH_SIZE,
       sizeof(struct list_head),
       GFP_KERNEL);
 if (!clp->cl_ownerstr_hashtbl)
  goto err_no_hashtbl;
 clp->cl_callback_wq = alloc_ordered_workqueue("nfsd4_callbacks", 0);
 if (!clp->cl_callback_wq)
  goto err_no_callback_wq;

 for (i = 0; i < OWNER_HASH_SIZE; i++)
  INIT_LIST_HEAD(&clp->cl_ownerstr_hashtbl[i]);
 INIT_LIST_HEAD(&clp->cl_sessions);
 idr_init(&clp->cl_stateids);
 atomic_set(&clp->cl_rpc_users, 0);
 clp->cl_cb_state = NFSD4_CB_UNKNOWN;
 clp->cl_state = NFSD4_ACTIVE;
 atomic_inc(&nn->nfs4_client_count);
 atomic_set(&clp->cl_delegs_in_recall, 0);
 INIT_LIST_HEAD(&clp->cl_idhash);
 INIT_LIST_HEAD(&clp->cl_openowners);
 INIT_LIST_HEAD(&clp->cl_delegations);
 INIT_LIST_HEAD(&clp->cl_lru);
 INIT_LIST_HEAD(&clp->cl_revoked);
#ifdef CONFIG_NFSD_PNFS
 INIT_LIST_HEAD(&clp->cl_lo_states);
#endif
 INIT_LIST_HEAD(&clp->async_copies);
 spin_lock_init(&clp->async_lock);
 spin_lock_init(&clp->cl_lock);
 rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");
 return clp;
err_no_callback_wq:
 kfree(clp->cl_ownerstr_hashtbl);
err_no_hashtbl:
 kfree(clp->cl_name.data);
err_no_name:
 kmem_cache_free(client_slab, clp);
 return NULL;
}

static void __free_client(struct kref *k)
{
 struct nfsdfs_client *c = container_of(k, struct nfsdfs_client, cl_ref);
 struct nfs4_client *clp = container_of(c, struct nfs4_client, cl_nfsdfs);

 free_svc_cred(&clp->cl_cred);
 destroy_workqueue(clp->cl_callback_wq);
 kfree(clp->cl_ownerstr_hashtbl);
 kfree(clp->cl_name.data);
 kfree(clp->cl_nii_domain.data);
 kfree(clp->cl_nii_name.data);
 idr_destroy(&clp->cl_stateids);
 kfree(clp->cl_ra);
 kmem_cache_free(client_slab, clp);
}

static void drop_client(struct nfs4_client *clp)
{
 kref_put(&clp->cl_nfsdfs.cl_ref, __free_client);
}

static void
free_client(struct nfs4_client *clp)
{
 while (!list_empty(&clp->cl_sessions)) {
  struct nfsd4_session *ses;
  ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
    se_perclnt);
  list_del(&ses->se_perclnt);
  WARN_ON_ONCE(atomic_read(&ses->se_ref));
  free_session(ses);
 }
 rpc_destroy_wait_queue(&clp->cl_cb_waitq);
 if (clp->cl_nfsd_dentry) {
  nfsd_client_rmdir(clp->cl_nfsd_dentry);
  clp->cl_nfsd_dentry = NULL;
  wake_up_all(&expiry_wq);
 }
 drop_client(clp);
}

/* must be called under the client_lock */
static void
unhash_client_locked(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
 struct nfsd4_session *ses;

 lockdep_assert_held(&nn->client_lock);

 /* Mark the client as expired! */
 clp->cl_time = 0;
 /* Make it invisible */
 if (!list_empty(&clp->cl_idhash)) {
  list_del_init(&clp->cl_idhash);
  if (test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags))
   rb_erase(&clp->cl_namenode, &nn->conf_name_tree);
  else
   rb_erase(&clp->cl_namenode, &nn->unconf_name_tree);
 }
 list_del_init(&clp->cl_lru);
 spin_lock(&clp->cl_lock);
 spin_lock(&nfsd_session_list_lock);
 list_for_each_entry(ses, &clp->cl_sessions, se_perclnt) {
  list_del_init(&ses->se_hash);
  list_del_init(&ses->se_all_sessions);
 }
 spin_unlock(&nfsd_session_list_lock);
 spin_unlock(&clp->cl_lock);
}

static void
unhash_client(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);

 spin_lock(&nn->client_lock);
 unhash_client_locked(clp);
 spin_unlock(&nn->client_lock);
}

static __be32 mark_client_expired_locked(struct nfs4_client *clp)
{
 int users = atomic_read(&clp->cl_rpc_users);

 trace_nfsd_mark_client_expired(clp, users);

 if (users)
  return nfserr_jukebox;
 unhash_client_locked(clp);
 return nfs_ok;
}

static void
__destroy_client(struct nfs4_client *clp)
{
 struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
 int i;
 struct nfs4_openowner *oo;
 struct nfs4_delegation *dp;
 LIST_HEAD(reaplist);

 spin_lock(&state_lock);
 while (!list_empty(&clp->cl_delegations)) {
  dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt);
  unhash_delegation_locked(dp, SC_STATUS_CLOSED);
  list_add(&dp->dl_recall_lru, &reaplist);
 }
 spin_unlock(&state_lock);
 while (!list_empty(&reaplist)) {
  dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=96 G=95

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.