Merge tag 'nfsd-5.2' of git://linux-nfs.org/~bfields/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 16 May 2019 01:21:43 +0000 (18:21 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 16 May 2019 01:21:43 +0000 (18:21 -0700)
Pull nfsd updates from Bruce Fields:
 "This consists mostly of nfsd container work:

  Scott Mayhew revived an old api that communicates with a userspace
  daemon to manage some on-disk state that's used to track clients
  across server reboots. We've been using a usermode_helper upcall for
  that, but it's tough to run those with the right namespaces, so a
  daemon is much friendlier to container use cases.

  Trond fixed nfsd's handling of user credentials in user namespaces. He
  also contributed patches that allow containers to support different
  sets of NFS protocol versions.

  The only remaining container bug I'm aware of is that the NFS reply
  cache is shared between all containers. If anyone's aware of other
  gaps in our container support, let me know.

  The rest of this is miscellaneous bugfixes"

* tag 'nfsd-5.2' of git://linux-nfs.org/~bfields/linux: (23 commits)
  nfsd: update callback done processing
  locks: move checks from locks_free_lock() to locks_release_private()
  nfsd: fh_drop_write in nfsd_unlink
  nfsd: allow fh_want_write to be called twice
  nfsd: knfsd must use the container user namespace
  SUNRPC: rsi_parse() should use the current user namespace
  SUNRPC: Fix the server AUTH_UNIX userspace mappings
  lockd: Pass the user cred from knfsd when starting the lockd server
  SUNRPC: Temporary sockets should inherit the cred from their parent
  SUNRPC: Cache the process user cred in the RPC server listener
  nfsd: Allow containers to set supported nfs versions
  nfsd: Add custom rpcbind callbacks for knfsd
  SUNRPC: Allow further customisation of RPC program registration
  SUNRPC: Clean up generic dispatcher code
  SUNRPC: Add a callback to initialise server requests
  SUNRPC/nfs: Fix return value for nfs4_callback_compound()
  nfsd: handle legacy client tracking records sent by nfsdcld
  nfsd: re-order client tracking method selection
  nfsd: keep a tally of RECLAIM_COMPLETE operations when using nfsdcld
  nfsd: un-deprecate nfsdcld
  ...

1  2 
fs/lockd/clntlock.c
fs/locks.c
fs/nfs/client.c
fs/nfsd/nfs4callback.c
fs/nfsd/nfs4proc.c
fs/nfsd/nfs4recover.c
fs/nfsd/nfs4state.c

diff --combined fs/lockd/clntlock.c
@@@ -56,14 -56,14 +56,14 @@@ struct nlm_host *nlmclnt_init(const str
        u32 nlm_version = (nlm_init->nfs_version == 2) ? 1 : 4;
        int status;
  
-       status = lockd_up(nlm_init->net);
+       status = lockd_up(nlm_init->net, nlm_init->cred);
        if (status < 0)
                return ERR_PTR(status);
  
        host = nlmclnt_lookup_host(nlm_init->address, nlm_init->addrlen,
                                   nlm_init->protocol, nlm_version,
                                   nlm_init->hostname, nlm_init->noresvport,
 -                                 nlm_init->net);
 +                                 nlm_init->net, nlm_init->cred);
        if (host == NULL)
                goto out_nohost;
        if (host->h_rpcclnt == NULL && nlm_bind_host(host) == NULL)
@@@ -241,7 -241,7 +241,7 @@@ reclaimer(void *ptr
        allow_signal(SIGKILL);
  
        down_write(&host->h_rwsem);
-       lockd_up(net);  /* note: this cannot fail as lockd is already running */
+       lockd_up(net, NULL);    /* note: this cannot fail as lockd is already running */
  
        dprintk("lockd: reclaiming locks for host %s\n", host->h_name);
  
diff --combined fs/locks.c
@@@ -352,6 -352,12 +352,12 @@@ EXPORT_SYMBOL_GPL(locks_alloc_lock)
  
  void locks_release_private(struct file_lock *fl)
  {
+       BUG_ON(waitqueue_active(&fl->fl_wait));
+       BUG_ON(!list_empty(&fl->fl_list));
+       BUG_ON(!list_empty(&fl->fl_blocked_requests));
+       BUG_ON(!list_empty(&fl->fl_blocked_member));
+       BUG_ON(!hlist_unhashed(&fl->fl_link));
        if (fl->fl_ops) {
                if (fl->fl_ops->fl_release_private)
                        fl->fl_ops->fl_release_private(fl);
@@@ -371,12 -377,6 +377,6 @@@ EXPORT_SYMBOL_GPL(locks_release_private
  /* Free a lock which is not in use. */
  void locks_free_lock(struct file_lock *fl)
  {
-       BUG_ON(waitqueue_active(&fl->fl_wait));
-       BUG_ON(!list_empty(&fl->fl_list));
-       BUG_ON(!list_empty(&fl->fl_blocked_requests));
-       BUG_ON(!list_empty(&fl->fl_blocked_member));
-       BUG_ON(!hlist_unhashed(&fl->fl_link));
        locks_release_private(fl);
        kmem_cache_free(filelock_cache, fl);
  }
@@@ -1476,7 -1476,7 +1476,7 @@@ static void lease_clear_pending(struct 
        switch (arg) {
        case F_UNLCK:
                fl->fl_flags &= ~FL_UNLOCK_PENDING;
 -              /* fall through: */
 +              /* fall through */
        case F_RDLCK:
                fl->fl_flags &= ~FL_DOWNGRADE_PENDING;
        }
diff --combined fs/nfs/client.c
@@@ -284,7 -284,6 +284,7 @@@ static struct nfs_client *nfs_match_cli
        struct nfs_client *clp;
        const struct sockaddr *sap = data->addr;
        struct nfs_net *nn = net_generic(data->net, nfs_net_id);
 +      int error;
  
  again:
        list_for_each_entry(clp, &nn->nfs_client_list, cl_share_link) {
                if (clp->cl_cons_state > NFS_CS_READY) {
                        refcount_inc(&clp->cl_count);
                        spin_unlock(&nn->nfs_client_lock);
 -                      nfs_wait_client_init_complete(clp);
 +                      error = nfs_wait_client_init_complete(clp);
                        nfs_put_client(clp);
                        spin_lock(&nn->nfs_client_lock);
 +                      if (error < 0)
 +                              return ERR_PTR(error);
                        goto again;
                }
  
@@@ -410,8 -407,6 +410,8 @@@ struct nfs_client *nfs_get_client(cons
                clp = nfs_match_client(cl_init);
                if (clp) {
                        spin_unlock(&nn->nfs_client_lock);
 +                      if (IS_ERR(clp))
 +                              return clp;
                        if (new)
                                new->rpc_ops->free_client(new);
                        return nfs_found_client(cl_init, clp);
@@@ -505,7 -500,6 +505,7 @@@ int nfs_create_rpc_client(struct nfs_cl
                .program        = &nfs_program,
                .version        = clp->rpc_ops->version,
                .authflavor     = flavor,
 +              .cred           = cl_init->cred,
        };
  
        if (test_bit(NFS_CS_DISCRTRY, &clp->cl_flags))
@@@ -558,6 -552,7 +558,7 @@@ static int nfs_start_lockd(struct nfs_s
                                        1 : 0,
                .net            = clp->cl_net,
                .nlmclnt_ops    = clp->cl_nfs_mod->rpc_ops->nlmclnt_ops,
+               .cred           = current_cred(),
        };
  
        if (nlm_init.nfs_version > 3)
@@@ -604,8 -599,6 +605,8 @@@ int nfs_init_server_rpcclient(struct nf
                        sizeof(server->client->cl_timeout_default));
        server->client->cl_timeout = &server->client->cl_timeout_default;
        server->client->cl_softrtry = 0;
 +      if (server->flags & NFS_MOUNT_SOFTERR)
 +              server->client->cl_softerr = 1;
        if (server->flags & NFS_MOUNT_SOFT)
                server->client->cl_softrtry = 1;
  
@@@ -660,7 -653,6 +661,7 @@@ static int nfs_init_server(struct nfs_s
                .proto = data->nfs_server.protocol,
                .net = data->net,
                .timeparms = &timeparms,
 +              .cred = server->cred,
        };
        struct nfs_client *clp;
        int error;
@@@ -929,7 -921,6 +930,7 @@@ void nfs_free_server(struct nfs_server 
        ida_destroy(&server->lockowner_id);
        ida_destroy(&server->openowner_id);
        nfs_free_iostats(server->io_stats);
 +      put_cred(server->cred);
        kfree(server);
        nfs_release_automount_timer();
  }
@@@ -950,8 -941,6 +951,8 @@@ struct nfs_server *nfs_create_server(st
        if (!server)
                return ERR_PTR(-ENOMEM);
  
 +      server->cred = get_cred(current_cred());
 +
        error = -ENOMEM;
        fattr = nfs_alloc_fattr();
        if (fattr == NULL)
@@@ -1018,8 -1007,6 +1019,8 @@@ struct nfs_server *nfs_clone_server(str
        if (!server)
                return ERR_PTR(-ENOMEM);
  
 +      server->cred = get_cred(source->cred);
 +
        error = -ENOMEM;
        fattr_fsinfo = nfs_alloc_fattr();
        if (fattr_fsinfo == NULL)
diff --combined fs/nfsd/nfs4callback.c
@@@ -868,7 -868,6 +868,7 @@@ static int setup_callback_client(struc
                .program        = &cb_program,
                .version        = 1,
                .flags          = (RPC_CLNT_CREATE_NOPING | RPC_CLNT_CREATE_QUIET),
 +              .cred           = current_cred(),
        };
        struct rpc_clnt *client;
        const struct cred *cred;
@@@ -1034,7 -1033,7 +1034,7 @@@ static bool nfsd4_cb_sequence_done(stru
                 * the submission code will error out, so we don't need to
                 * handle that case here.
                 */
 -              if (task->tk_flags & RPC_TASK_KILLED)
 +              if (RPC_SIGNALLED(task))
                        goto need_restart;
  
                return true;
        dprintk("%s: freed slot, new seqid=%d\n", __func__,
                clp->cl_cb_session->se_cb_seq_nr);
  
 -      if (task->tk_flags & RPC_TASK_KILLED)
 +      if (RPC_SIGNALLED(task))
                goto need_restart;
  out:
        return ret;
@@@ -1123,10 -1122,11 +1123,11 @@@ static void nfsd4_cb_done(struct rpc_ta
                rpc_restart_call_prepare(task);
                return;
        case 1:
-               break;
-       case -1:
-               /* Network partition? */
-               nfsd4_mark_cb_down(clp, task->tk_status);
+               switch (task->tk_status) {
+               case -EIO:
+               case -ETIMEDOUT:
+                       nfsd4_mark_cb_down(clp, task->tk_status);
+               }
                break;
        default:
                BUG();
diff --combined fs/nfsd/nfs4proc.c
@@@ -427,7 -427,6 +427,7 @@@ nfsd4_open(struct svc_rqst *rqstp, stru
                                goto out;
                        open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED;
                        reclaim = true;
 +                      /* fall through */
                case NFS4_OPEN_CLAIM_FH:
                case NFS4_OPEN_CLAIM_DELEG_CUR_FH:
                        status = do_open_fhandle(rqstp, cstate, open);
@@@ -1927,6 -1926,7 +1927,7 @@@ nfsd4_proc_compound(struct svc_rqst *rq
        struct nfsd4_compound_state *cstate = &resp->cstate;
        struct svc_fh *current_fh = &cstate->current_fh;
        struct svc_fh *save_fh = &cstate->save_fh;
+       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
        __be32          status;
  
        svcxdr_init_encode(rqstp, resp);
         * According to RFC3010, this takes precedence over all other errors.
         */
        status = nfserr_minor_vers_mismatch;
-       if (nfsd_minorversion(args->minorversion, NFSD_TEST) <= 0)
+       if (nfsd_minorversion(nn, args->minorversion, NFSD_TEST) <= 0)
                goto out;
        status = nfserr_resource;
        if (args->opcnt > NFSD_MAX_OPS_PER_COMPOUND)
diff --combined fs/nfsd/nfs4recover.c
@@@ -126,6 -126,7 +126,6 @@@ nfs4_make_rec_clidname(char *dname, con
                SHASH_DESC_ON_STACK(desc, tfm);
  
                desc->tfm = tfm;
 -              desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
  
                status = crypto_shash_digest(desc, clname->data, clname->len,
                                             cksum.data);
@@@ -169,12 -170,33 +169,33 @@@ legacy_recdir_name_error(struct nfs4_cl
  }
  
  static void
+ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
+               const char *dname, int len, struct nfsd_net *nn)
+ {
+       struct xdr_netobj name;
+       struct nfs4_client_reclaim *crp;
+       name.data = kmemdup(dname, len, GFP_KERNEL);
+       if (!name.data) {
+               dprintk("%s: failed to allocate memory for name.data!\n",
+                       __func__);
+               return;
+       }
+       name.len = len;
+       crp = nfs4_client_to_reclaim(name, nn);
+       if (!crp) {
+               kfree(name.data);
+               return;
+       }
+       crp->cr_clp = clp;
+ }
+ static void
  nfsd4_create_clid_dir(struct nfs4_client *clp)
  {
        const struct cred *original_cred;
        char dname[HEXDIR_LEN];
        struct dentry *dir, *dentry;
-       struct nfs4_client_reclaim *crp;
        int status;
        struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
  
@@@ -220,11 -242,9 +241,9 @@@ out_put
  out_unlock:
        inode_unlock(d_inode(dir));
        if (status == 0) {
-               if (nn->in_grace) {
-                       crp = nfs4_client_to_reclaim(dname, nn);
-                       if (crp)
-                               crp->cr_clp = clp;
-               }
+               if (nn->in_grace)
+                       __nfsd4_create_reclaim_record_grace(clp, dname,
+                                       HEXDIR_LEN, nn);
                vfs_fsync(nn->rec_file, 0);
        } else {
                printk(KERN_ERR "NFSD: failed to write recovery record"
@@@ -345,10 -365,29 +364,29 @@@ out_unlock
  }
  
  static void
+ __nfsd4_remove_reclaim_record_grace(const char *dname, int len,
+               struct nfsd_net *nn)
+ {
+       struct xdr_netobj name;
+       struct nfs4_client_reclaim *crp;
+       name.data = kmemdup(dname, len, GFP_KERNEL);
+       if (!name.data) {
+               dprintk("%s: failed to allocate memory for name.data!\n",
+                       __func__);
+               return;
+       }
+       name.len = len;
+       crp = nfsd4_find_reclaim_client(name, nn);
+       kfree(name.data);
+       if (crp)
+               nfs4_remove_reclaim_record(crp, nn);
+ }
+ static void
  nfsd4_remove_clid_dir(struct nfs4_client *clp)
  {
        const struct cred *original_cred;
-       struct nfs4_client_reclaim *crp;
        char dname[HEXDIR_LEN];
        int status;
        struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
        nfs4_reset_creds(original_cred);
        if (status == 0) {
                vfs_fsync(nn->rec_file, 0);
-               if (nn->in_grace) {
-                       /* remove reclaim record */
-                       crp = nfsd4_find_reclaim_client(dname, nn);
-                       if (crp)
-                               nfs4_remove_reclaim_record(crp, nn);
-               }
+               if (nn->in_grace)
+                       __nfsd4_remove_reclaim_record_grace(dname,
+                                       HEXDIR_LEN, nn);
        }
  out_drop_write:
        mnt_drop_write_file(nn->rec_file);
@@@ -392,14 -428,31 +427,31 @@@ static in
  purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
  {
        int status;
+       struct xdr_netobj name;
  
-       if (nfs4_has_reclaimed_state(child->d_name.name, nn))
+       if (child->d_name.len != HEXDIR_LEN - 1) {
+               printk("%s: illegal name %pd in recovery directory\n",
+                               __func__, child);
+               /* Keep trying; maybe the others are OK: */
                return 0;
+       }
+       name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
+       if (!name.data) {
+               dprintk("%s: failed to allocate memory for name.data!\n",
+                       __func__);
+               goto out;
+       }
+       name.len = HEXDIR_LEN;
+       if (nfs4_has_reclaimed_state(name, nn))
+               goto out_free;
  
        status = vfs_rmdir(d_inode(parent), child);
        if (status)
                printk("failed to remove client recovery directory %pd\n",
                                child);
+ out_free:
+       kfree(name.data);
+ out:
        /* Keep trying, success or failure: */
        return 0;
  }
@@@ -429,13 -482,24 +481,24 @@@ out
  static int
  load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
  {
+       struct xdr_netobj name;
        if (child->d_name.len != HEXDIR_LEN - 1) {
-               printk("nfsd4: illegal name %pd in recovery directory\n",
-                               child);
+               printk("%s: illegal name %pd in recovery directory\n",
+                               __func__, child);
                /* Keep trying; maybe the others are OK: */
                return 0;
        }
-       nfs4_client_to_reclaim(child->d_name.name, nn);
+       name.data = kmemdup_nul(child->d_name.name, child->d_name.len, GFP_KERNEL);
+       if (!name.data) {
+               dprintk("%s: failed to allocate memory for name.data!\n",
+                       __func__);
+               goto out;
+       }
+       name.len = HEXDIR_LEN;
+       if (!nfs4_client_to_reclaim(name, nn))
+               kfree(name.data);
+ out:
        return 0;
  }
  
@@@ -564,6 -628,7 +627,7 @@@ nfsd4_legacy_tracking_init(struct net *
        status = nfsd4_load_reboot_recovery_data(net);
        if (status)
                goto err;
+       printk("NFSD: Using legacy client tracking operations.\n");
        return 0;
  
  err:
@@@ -615,6 -680,7 +679,7 @@@ nfsd4_check_legacy_client(struct nfs4_c
        char dname[HEXDIR_LEN];
        struct nfs4_client_reclaim *crp;
        struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+       struct xdr_netobj name;
  
        /* did we already find that this client is stable? */
        if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
        }
  
        /* look for it in the reclaim hashtable otherwise */
-       crp = nfsd4_find_reclaim_client(dname, nn);
+       name.data = kmemdup(dname, HEXDIR_LEN, GFP_KERNEL);
+       if (!name.data) {
+               dprintk("%s: failed to allocate memory for name.data!\n",
+                       __func__);
+               goto out_enoent;
+       }
+       name.len = HEXDIR_LEN;
+       crp = nfsd4_find_reclaim_client(name, nn);
+       kfree(name.data);
        if (crp) {
                set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
                crp->cr_clp = clp;
                return 0;
        }
  
+ out_enoent:
        return -ENOENT;
  }
  
@@@ -656,6 -731,7 +730,7 @@@ struct cld_net 
        spinlock_t               cn_lock;
        struct list_head         cn_list;
        unsigned int             cn_xid;
+       bool                     cn_has_legacy;
  };
  
  struct cld_upcall {
@@@ -706,6 -782,40 +781,40 @@@ cld_pipe_upcall(struct rpc_pipe *pipe, 
  }
  
  static ssize_t
+ __cld_pipe_inprogress_downcall(const struct cld_msg __user *cmsg,
+               struct nfsd_net *nn)
+ {
+       uint8_t cmd;
+       struct xdr_netobj name;
+       uint16_t namelen;
+       struct cld_net *cn = nn->cld_net;
+       if (get_user(cmd, &cmsg->cm_cmd)) {
+               dprintk("%s: error when copying cmd from userspace", __func__);
+               return -EFAULT;
+       }
+       if (cmd == Cld_GraceStart) {
+               if (get_user(namelen, &cmsg->cm_u.cm_name.cn_len))
+                       return -EFAULT;
+               name.data = memdup_user(&cmsg->cm_u.cm_name.cn_id, namelen);
+               if (IS_ERR_OR_NULL(name.data))
+                       return -EFAULT;
+               name.len = namelen;
+               if (name.len > 5 && memcmp(name.data, "hash:", 5) == 0) {
+                       name.len = name.len - 5;
+                       memmove(name.data, name.data + 5, name.len);
+                       cn->cn_has_legacy = true;
+               }
+               if (!nfs4_client_to_reclaim(name, nn)) {
+                       kfree(name.data);
+                       return -EFAULT;
+               }
+               return sizeof(*cmsg);
+       }
+       return -EFAULT;
+ }
+ static ssize_t
  cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
  {
        struct cld_upcall *tmp, *cup;
        struct nfsd_net *nn = net_generic(file_inode(filp)->i_sb->s_fs_info,
                                                nfsd_net_id);
        struct cld_net *cn = nn->cld_net;
+       int16_t status;
  
        if (mlen != sizeof(*cmsg)) {
                dprintk("%s: got %zu bytes, expected %zu\n", __func__, mlen,
                return -EFAULT;
        }
  
+       /*
+        * copy the status so we know whether to remove the upcall from the
+        * list (for -EINPROGRESS, we just want to make sure the xid is
+        * valid, not remove the upcall from the list)
+        */
+       if (get_user(status, &cmsg->cm_status)) {
+               dprintk("%s: error when copying status from userspace", __func__);
+               return -EFAULT;
+       }
        /* walk the list and find corresponding xid */
        cup = NULL;
        spin_lock(&cn->cn_lock);
        list_for_each_entry(tmp, &cn->cn_list, cu_list) {
                if (get_unaligned(&tmp->cu_msg.cm_xid) == xid) {
                        cup = tmp;
-                       list_del_init(&cup->cu_list);
+                       if (status != -EINPROGRESS)
+                               list_del_init(&cup->cu_list);
                        break;
                }
        }
                return -EINVAL;
        }
  
+       if (status == -EINPROGRESS)
+               return __cld_pipe_inprogress_downcall(cmsg, nn);
        if (copy_from_user(&cup->cu_msg, src, mlen) != 0)
                return -EFAULT;
  
@@@ -820,7 -945,7 +944,7 @@@ nfsd4_cld_unregister_net(struct net *ne
  
  /* Initialize rpc_pipefs pipe for communication with client tracking daemon */
  static int
- nfsd4_init_cld_pipe(struct net *net)
__nfsd4_init_cld_pipe(struct net *net)
  {
        int ret;
        struct dentry *dentry;
        }
  
        cn->cn_pipe->dentry = dentry;
+       cn->cn_has_legacy = false;
        nn->cld_net = cn;
        return 0;
  
@@@ -863,6 -989,17 +988,17 @@@ err
        return ret;
  }
  
+ static int
+ nfsd4_init_cld_pipe(struct net *net)
+ {
+       int status;
+       status = __nfsd4_init_cld_pipe(net);
+       if (!status)
+               printk("NFSD: Using old nfsdcld client tracking operations.\n");
+       return status;
+ }
  static void
  nfsd4_remove_cld_pipe(struct net *net)
  {
@@@ -991,9 -1128,14 +1127,14 @@@ out_err
                                "record from stable storage: %d\n", ret);
  }
  
- /* Check for presence of a record, and update its timestamp */
+ /*
+  * For older nfsdcld's that do not allow us to "slurp" the clients
+  * from the tracking database during startup.
+  *
+  * Check for presence of a record, and update its timestamp
+  */
  static int
- nfsd4_cld_check(struct nfs4_client *clp)
+ nfsd4_cld_check_v0(struct nfs4_client *clp)
  {
        int ret;
        struct cld_upcall *cup;
        return ret;
  }
  
+ /*
+  * For newer nfsdcld's that allow us to "slurp" the clients
+  * from the tracking database during startup.
+  *
+  * Check for presence of a record in the reclaim_str_hashtbl
+  */
+ static int
+ nfsd4_cld_check(struct nfs4_client *clp)
+ {
+       struct nfs4_client_reclaim *crp;
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+       struct cld_net *cn = nn->cld_net;
+       int status;
+       char dname[HEXDIR_LEN];
+       struct xdr_netobj name;
+       /* did we already find that this client is stable? */
+       if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
+               return 0;
+       /* look for it in the reclaim hashtable otherwise */
+       crp = nfsd4_find_reclaim_client(clp->cl_name, nn);
+       if (crp)
+               goto found;
+       if (cn->cn_has_legacy) {
+               status = nfs4_make_rec_clidname(dname, &clp->cl_name);
+               if (status)
+                       return -ENOENT;
+               name.data = kmemdup(dname, HEXDIR_LEN, GFP_KERNEL);
+               if (!name.data) {
+                       dprintk("%s: failed to allocate memory for name.data!\n",
+                               __func__);
+                       return -ENOENT;
+               }
+               name.len = HEXDIR_LEN;
+               crp = nfsd4_find_reclaim_client(name, nn);
+               kfree(name.data);
+               if (crp)
+                       goto found;
+       }
+       return -ENOENT;
+ found:
+       crp->cr_clp = clp;
+       return 0;
+ }
+ static int
+ nfsd4_cld_grace_start(struct nfsd_net *nn)
+ {
+       int ret;
+       struct cld_upcall *cup;
+       struct cld_net *cn = nn->cld_net;
+       cup = alloc_cld_upcall(cn);
+       if (!cup) {
+               ret = -ENOMEM;
+               goto out_err;
+       }
+       cup->cu_msg.cm_cmd = Cld_GraceStart;
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       if (!ret)
+               ret = cup->cu_msg.cm_status;
+       free_cld_upcall(cup);
+ out_err:
+       if (ret)
+               dprintk("%s: Unable to get clients from userspace: %d\n",
+                       __func__, ret);
+       return ret;
+ }
+ /* For older nfsdcld's that need cm_gracetime */
  static void
- nfsd4_cld_grace_done(struct nfsd_net *nn)
+ nfsd4_cld_grace_done_v0(struct nfsd_net *nn)
  {
        int ret;
        struct cld_upcall *cup;
@@@ -1051,11 -1269,149 +1268,149 @@@ out_err
                printk(KERN_ERR "NFSD: Unable to end grace period: %d\n", ret);
  }
  
- static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops = {
+ /*
+  * For newer nfsdcld's that do not need cm_gracetime.  We also need to call
+  * nfs4_release_reclaim() to clear out the reclaim_str_hashtbl.
+  */
+ static void
+ nfsd4_cld_grace_done(struct nfsd_net *nn)
+ {
+       int ret;
+       struct cld_upcall *cup;
+       struct cld_net *cn = nn->cld_net;
+       cup = alloc_cld_upcall(cn);
+       if (!cup) {
+               ret = -ENOMEM;
+               goto out_err;
+       }
+       cup->cu_msg.cm_cmd = Cld_GraceDone;
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       if (!ret)
+               ret = cup->cu_msg.cm_status;
+       free_cld_upcall(cup);
+ out_err:
+       nfs4_release_reclaim(nn);
+       if (ret)
+               printk(KERN_ERR "NFSD: Unable to end grace period: %d\n", ret);
+ }
+ static int
+ nfs4_cld_state_init(struct net *net)
+ {
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       int i;
+       nn->reclaim_str_hashtbl = kmalloc_array(CLIENT_HASH_SIZE,
+                                               sizeof(struct list_head),
+                                               GFP_KERNEL);
+       if (!nn->reclaim_str_hashtbl)
+               return -ENOMEM;
+       for (i = 0; i < CLIENT_HASH_SIZE; i++)
+               INIT_LIST_HEAD(&nn->reclaim_str_hashtbl[i]);
+       nn->reclaim_str_hashtbl_size = 0;
+       nn->track_reclaim_completes = true;
+       atomic_set(&nn->nr_reclaim_complete, 0);
+       return 0;
+ }
+ static void
+ nfs4_cld_state_shutdown(struct net *net)
+ {
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       nn->track_reclaim_completes = false;
+       kfree(nn->reclaim_str_hashtbl);
+ }
+ static bool
+ cld_running(struct nfsd_net *nn)
+ {
+       struct cld_net *cn = nn->cld_net;
+       struct rpc_pipe *pipe = cn->cn_pipe;
+       return pipe->nreaders || pipe->nwriters;
+ }
+ static int
+ nfsd4_cld_tracking_init(struct net *net)
+ {
+       int status;
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       bool running;
+       int retries = 10;
+       status = nfs4_cld_state_init(net);
+       if (status)
+               return status;
+       status = __nfsd4_init_cld_pipe(net);
+       if (status)
+               goto err_shutdown;
+       /*
+        * rpc pipe upcalls take 30 seconds to time out, so we don't want to
+        * queue an upcall unless we know that nfsdcld is running (because we
+        * want this to fail fast so that nfsd4_client_tracking_init() can try
+        * the next client tracking method).  nfsdcld should already be running
+        * before nfsd is started, so the wait here is for nfsdcld to open the
+        * pipefs file we just created.
+        */
+       while (!(running = cld_running(nn)) && retries--)
+               msleep(100);
+       if (!running) {
+               status = -ETIMEDOUT;
+               goto err_remove;
+       }
+       status = nfsd4_cld_grace_start(nn);
+       if (status) {
+               if (status == -EOPNOTSUPP)
+                       printk(KERN_WARNING "NFSD: Please upgrade nfsdcld.\n");
+               nfs4_release_reclaim(nn);
+               goto err_remove;
+       } else
+               printk("NFSD: Using nfsdcld client tracking operations.\n");
+       return 0;
+ err_remove:
+       nfsd4_remove_cld_pipe(net);
+ err_shutdown:
+       nfs4_cld_state_shutdown(net);
+       return status;
+ }
+ static void
+ nfsd4_cld_tracking_exit(struct net *net)
+ {
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       nfs4_release_reclaim(nn);
+       nfsd4_remove_cld_pipe(net);
+       nfs4_cld_state_shutdown(net);
+ }
+ /* For older nfsdcld's */
+ static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops_v0 = {
        .init           = nfsd4_init_cld_pipe,
        .exit           = nfsd4_remove_cld_pipe,
        .create         = nfsd4_cld_create,
        .remove         = nfsd4_cld_remove,
+       .check          = nfsd4_cld_check_v0,
+       .grace_done     = nfsd4_cld_grace_done_v0,
+ };
+ /* For newer nfsdcld's */
+ static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops = {
+       .init           = nfsd4_cld_tracking_init,
+       .exit           = nfsd4_cld_tracking_exit,
+       .create         = nfsd4_cld_create,
+       .remove         = nfsd4_cld_remove,
        .check          = nfsd4_cld_check,
        .grace_done     = nfsd4_cld_grace_done,
  };
@@@ -1267,6 -1623,8 +1622,8 @@@ nfsd4_umh_cltrack_init(struct net *net
  
        ret = nfsd4_umh_cltrack_upcall("init", NULL, grace_start, NULL);
        kfree(grace_start);
+       if (!ret)
+               printk("NFSD: Using UMH upcall client tracking operations.\n");
        return ret;
  }
  
@@@ -1416,9 -1774,20 +1773,20 @@@ nfsd4_client_tracking_init(struct net *
        if (nn->client_tracking_ops)
                goto do_init;
  
+       /* First, try to use nfsdcld */
+       nn->client_tracking_ops = &nfsd4_cld_tracking_ops;
+       status = nn->client_tracking_ops->init(net);
+       if (!status)
+               return status;
+       if (status != -ETIMEDOUT) {
+               nn->client_tracking_ops = &nfsd4_cld_tracking_ops_v0;
+               status = nn->client_tracking_ops->init(net);
+               if (!status)
+                       return status;
+       }
        /*
-        * First, try a UMH upcall. It should succeed or fail quickly, so
-        * there's little harm in trying that first.
+        * Next, try the UMH upcall.
         */
        nn->client_tracking_ops = &nfsd4_umh_tracking_ops;
        status = nn->client_tracking_ops->init(net);
                return status;
  
        /*
-        * See if the recoverydir exists and is a directory. If it is,
-        * then use the legacy ops.
+        * Finally, See if the recoverydir exists and is a directory.
+        * If it is, then use the legacy ops.
         */
        nn->client_tracking_ops = &nfsd4_legacy_tracking_ops;
        status = kern_path(nfs4_recoverydir(), LOOKUP_FOLLOW, &path);
        if (!status) {
                status = d_is_dir(path.dentry);
                path_put(&path);
-               if (status)
-                       goto do_init;
+               if (!status) {
+                       status = -EINVAL;
+                       goto out;
+               }
        }
  
-       /* Finally, try to use nfsdcld */
-       nn->client_tracking_ops = &nfsd4_cld_tracking_ops;
-       printk(KERN_WARNING "NFSD: the nfsdcld client tracking upcall will be "
-                       "removed in 3.10. Please transition to using "
-                       "nfsdcltrack.\n");
  do_init:
        status = nn->client_tracking_ops->init(net);
+ out:
        if (status) {
                printk(KERN_WARNING "NFSD: Unable to initialize client "
                                    "recovery tracking! (%d)\n", status);
diff --combined fs/nfsd/nfs4state.c
@@@ -77,6 -77,7 +77,7 @@@ static u64 current_sessionid = 1
  /* 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);
  
  /* Locking: */
  
@@@ -1067,9 -1068,9 +1068,9 @@@ static unsigned int clientid_hashval(u3
        return id & CLIENT_HASH_MASK;
  }
  
- static unsigned int clientstr_hashval(const char *name)
+ static unsigned int clientstr_hashval(struct xdr_netobj name)
  {
-       return opaque_hashval(name, 8) & CLIENT_HASH_MASK;
+       return opaque_hashval(name.data, 8) & CLIENT_HASH_MASK;
  }
  
  /*
@@@ -1997,6 -1998,22 +1998,22 @@@ destroy_client(struct nfs4_client *clp
        __destroy_client(clp);
  }
  
+ static void inc_reclaim_complete(struct nfs4_client *clp)
+ {
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+       if (!nn->track_reclaim_completes)
+               return;
+       if (!nfsd4_find_reclaim_client(clp->cl_name, nn))
+               return;
+       if (atomic_inc_return(&nn->nr_reclaim_complete) ==
+                       nn->reclaim_str_hashtbl_size) {
+               printk(KERN_INFO "NFSD: all clients done reclaiming, ending NFSv4 grace period (net %x)\n",
+                               clp->net->ns.inum);
+               nfsd4_end_grace(nn);
+       }
+ }
  static void expire_client(struct nfs4_client *clp)
  {
        unhash_client(clp);
@@@ -2048,11 -2065,6 +2065,6 @@@ compare_blob(const struct xdr_netobj *o
        return memcmp(o1->data, o2->data, o1->len);
  }
  
- static int same_name(const char *n1, const char *n2)
- {
-       return 0 == memcmp(n1, n2, HEXDIR_LEN);
- }
  static int
  same_verf(nfs4_verifier *v1, nfs4_verifier *v2)
  {
@@@ -2585,7 -2597,6 +2597,7 @@@ nfsd4_exchange_id(struct svc_rqst *rqst
                break;
        default:                                /* checked by xdr code */
                WARN_ON_ONCE(1);
 +              /* fall through */
        case SP4_SSV:
                status = nfserr_encr_alg_unsupp;
                goto out_nolock;
@@@ -3354,6 -3365,7 +3366,7 @@@ nfsd4_reclaim_complete(struct svc_rqst 
  
        status = nfs_ok;
        nfsd4_client_record_create(cstate->session->se_client);
+       inc_reclaim_complete(cstate->session->se_client);
  out:
        return status;
  }
@@@ -3958,6 -3970,9 +3971,9 @@@ static int nfsd4_cb_recall_done(struct 
        switch (task->tk_status) {
        case 0:
                return 1;
+       case -NFS4ERR_DELAY:
+               rpc_delay(task, 2 * HZ);
+               return 0;
        case -EBADHANDLE:
        case -NFS4ERR_BAD_STATEID:
                /*
                }
                /*FALLTHRU*/
        default:
-               return -1;
+               return 1;
        }
  }
  
@@@ -4713,7 -4728,6 +4729,6 @@@ nfsd4_end_grace(struct nfsd_net *nn
        if (nn->grace_ended)
                return;
  
-       dprintk("NFSD: end of grace period\n");
        nn->grace_ended = true;
        /*
         * If the server goes down again right now, an NFSv4
@@@ -4749,6 -4763,10 +4764,10 @@@ static bool clients_still_reclaiming(st
        unsigned long double_grace_period_end = nn->boot_time +
                                                2 * nn->nfsd4_lease;
  
+       if (nn->track_reclaim_completes &&
+                       atomic_read(&nn->nr_reclaim_complete) ==
+                       nn->reclaim_str_hashtbl_size)
+               return false;
        if (!nn->somebody_reclaimed)
                return false;
        nn->somebody_reclaimed = false;
@@@ -4779,6 -4797,7 +4798,7 @@@ nfs4_laundromat(struct nfsd_net *nn
                new_timeo = 0;
                goto out;
        }
+       dprintk("NFSD: end of grace period\n");
        nfsd4_end_grace(nn);
        INIT_LIST_HEAD(&reaplist);
        spin_lock(&nn->client_lock);
@@@ -6458,7 -6477,7 +6478,7 @@@ alloc_reclaim(void
  }
  
  bool
- nfs4_has_reclaimed_state(const char *name, struct nfsd_net *nn)
+ nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn)
  {
        struct nfs4_client_reclaim *crp;
  
  
  /*
   * failure => all reset bets are off, nfserr_no_grace...
+  *
+  * The caller is responsible for freeing name.data if NULL is returned (it
+  * will be freed in nfs4_remove_reclaim_record in the normal case).
   */
  struct nfs4_client_reclaim *
- nfs4_client_to_reclaim(const char *name, struct nfsd_net *nn)
+ nfs4_client_to_reclaim(struct xdr_netobj name, struct nfsd_net *nn)
  {
        unsigned int strhashval;
        struct nfs4_client_reclaim *crp;
  
-       dprintk("NFSD nfs4_client_to_reclaim NAME: %.*s\n", HEXDIR_LEN, name);
+       dprintk("NFSD nfs4_client_to_reclaim NAME: %.*s\n", name.len, name.data);
        crp = alloc_reclaim();
        if (crp) {
                strhashval = clientstr_hashval(name);
                INIT_LIST_HEAD(&crp->cr_strhash);
                list_add(&crp->cr_strhash, &nn->reclaim_str_hashtbl[strhashval]);
-               memcpy(crp->cr_recdir, name, HEXDIR_LEN);
+               crp->cr_name.data = name.data;
+               crp->cr_name.len = name.len;
                crp->cr_clp = NULL;
                nn->reclaim_str_hashtbl_size++;
        }
@@@ -6492,6 -6515,7 +6516,7 @@@ voi
  nfs4_remove_reclaim_record(struct nfs4_client_reclaim *crp, struct nfsd_net *nn)
  {
        list_del(&crp->cr_strhash);
+       kfree(crp->cr_name.data);
        kfree(crp);
        nn->reclaim_str_hashtbl_size--;
  }
@@@ -6515,16 -6539,16 +6540,16 @@@ nfs4_release_reclaim(struct nfsd_net *n
  /*
   * called from OPEN, CLAIM_PREVIOUS with a new clientid. */
  struct nfs4_client_reclaim *
- nfsd4_find_reclaim_client(const char *recdir, struct nfsd_net *nn)
+ nfsd4_find_reclaim_client(struct xdr_netobj name, struct nfsd_net *nn)
  {
        unsigned int strhashval;
        struct nfs4_client_reclaim *crp = NULL;
  
-       dprintk("NFSD: nfs4_find_reclaim_client for recdir %s\n", recdir);
+       dprintk("NFSD: nfs4_find_reclaim_client for name %.*s\n", name.len, name.data);
  
-       strhashval = clientstr_hashval(recdir);
+       strhashval = clientstr_hashval(name);
        list_for_each_entry(crp, &nn->reclaim_str_hashtbl[strhashval], cr_strhash) {
-               if (same_name(crp->cr_recdir, recdir)) {
+               if (compare_blob(&crp->cr_name, &name) == 0) {
                        return crp;
                }
        }
@@@ -7262,10 -7286,19 +7287,19 @@@ nfs4_state_start_net(struct net *net
                return ret;
        locks_start_grace(net, &nn->nfsd4_manager);
        nfsd4_client_tracking_init(net);
+       if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size == 0)
+               goto skip_grace;
        printk(KERN_INFO "NFSD: starting %ld-second grace period (net %x)\n",
               nn->nfsd4_grace, net->ns.inum);
        queue_delayed_work(laundry_wq, &nn->laundromat_work, nn->nfsd4_grace * HZ);
        return 0;
+ skip_grace:
+       printk(KERN_INFO "NFSD: no clients to reclaim, skipping NFSv4 grace period (net %x)\n",
+                       net->ns.inum);
+       queue_delayed_work(laundry_wq, &nn->laundromat_work, nn->nfsd4_lease * HZ);
+       nfsd4_end_grace(nn);
+       return 0;
  }
  
  /* initialization to perform when the nfsd service is started: */