]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - fs/nfsd/nfs4recover.c
Merge branch 'for-5.5/core' into for-linus
[linux.git] / fs / nfsd / nfs4recover.c
index 87679557d0d6a1e05aa41ebbe7b1926fb3ae95bf..cdc75ad4438b6442fb8bf76b9687b662f3a44bc6 100644 (file)
@@ -59,8 +59,13 @@ struct nfsd4_client_tracking_ops {
        void (*remove)(struct nfs4_client *);
        int (*check)(struct nfs4_client *);
        void (*grace_done)(struct nfsd_net *);
+       uint8_t version;
+       size_t msglen;
 };
 
+static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops;
+static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops_v2;
+
 /* Globals */
 static char user_recovery_dirname[PATH_MAX] = "/var/lib/nfs/v4recovery";
 
@@ -173,6 +178,7 @@ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
                const char *dname, int len, struct nfsd_net *nn)
 {
        struct xdr_netobj name;
+       struct xdr_netobj princhash = { .len = 0, .data = NULL };
        struct nfs4_client_reclaim *crp;
 
        name.data = kmemdup(dname, len, GFP_KERNEL);
@@ -182,7 +188,7 @@ __nfsd4_create_reclaim_record_grace(struct nfs4_client *clp,
                return;
        }
        name.len = len;
-       crp = nfs4_client_to_reclaim(name, nn);
+       crp = nfs4_client_to_reclaim(name, princhash, nn);
        if (!crp) {
                kfree(name.data);
                return;
@@ -482,6 +488,7 @@ static int
 load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
 {
        struct xdr_netobj name;
+       struct xdr_netobj princhash = { .len = 0, .data = NULL };
 
        if (child->d_name.len != HEXDIR_LEN - 1) {
                printk("%s: illegal name %pd in recovery directory\n",
@@ -496,7 +503,7 @@ load_recdir(struct dentry *parent, struct dentry *child, struct nfsd_net *nn)
                goto out;
        }
        name.len = HEXDIR_LEN;
-       if (!nfs4_client_to_reclaim(name, nn))
+       if (!nfs4_client_to_reclaim(name, princhash, nn))
                kfree(name.data);
 out:
        return 0;
@@ -718,6 +725,8 @@ static const struct nfsd4_client_tracking_ops nfsd4_legacy_tracking_ops = {
        .remove         = nfsd4_remove_clid_dir,
        .check          = nfsd4_check_legacy_client,
        .grace_done     = nfsd4_recdir_purge_old,
+       .version        = 1,
+       .msglen         = 0,
 };
 
 /* Globals */
@@ -731,25 +740,32 @@ struct cld_net {
        struct list_head         cn_list;
        unsigned int             cn_xid;
        bool                     cn_has_legacy;
+       struct crypto_shash     *cn_tfm;
 };
 
 struct cld_upcall {
        struct list_head         cu_list;
        struct cld_net          *cu_net;
        struct completion        cu_done;
-       struct cld_msg           cu_msg;
+       union {
+               struct cld_msg_hdr       cu_hdr;
+               struct cld_msg           cu_msg;
+               struct cld_msg_v2        cu_msg_v2;
+       } cu_u;
 };
 
 static int
-__cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
+__cld_pipe_upcall(struct rpc_pipe *pipe, void *cmsg)
 {
        int ret;
        struct rpc_pipe_msg msg;
-       struct cld_upcall *cup = container_of(cmsg, struct cld_upcall, cu_msg);
+       struct cld_upcall *cup = container_of(cmsg, struct cld_upcall, cu_u);
+       struct nfsd_net *nn = net_generic(pipe->dentry->d_sb->s_fs_info,
+                                         nfsd_net_id);
 
        memset(&msg, 0, sizeof(msg));
        msg.data = cmsg;
-       msg.len = sizeof(*cmsg);
+       msg.len = nn->client_tracking_ops->msglen;
 
        ret = rpc_queue_upcall(pipe, &msg);
        if (ret < 0) {
@@ -765,7 +781,7 @@ __cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
 }
 
 static int
-cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
+cld_pipe_upcall(struct rpc_pipe *pipe, void *cmsg)
 {
        int ret;
 
@@ -781,11 +797,11 @@ cld_pipe_upcall(struct rpc_pipe *pipe, struct cld_msg *cmsg)
 }
 
 static ssize_t
-__cld_pipe_inprogress_downcall(const struct cld_msg __user *cmsg,
+__cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg,
                struct nfsd_net *nn)
 {
-       uint8_t cmd;
-       struct xdr_netobj name;
+       uint8_t cmd, princhashlen;
+       struct xdr_netobj name, princhash = { .len = 0, .data = NULL };
        uint16_t namelen;
        struct cld_net *cn = nn->cld_net;
 
@@ -794,22 +810,48 @@ __cld_pipe_inprogress_downcall(const struct cld_msg __user *cmsg,
                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 (nn->client_tracking_ops->version >= 2) {
+                       const struct cld_clntinfo __user *ci;
+
+                       ci = &cmsg->cm_u.cm_clntinfo;
+                       if (get_user(namelen, &ci->cc_name.cn_len))
+                               return -EFAULT;
+                       name.data = memdup_user(&ci->cc_name.cn_id, namelen);
+                       if (IS_ERR_OR_NULL(name.data))
+                               return -EFAULT;
+                       name.len = namelen;
+                       get_user(princhashlen, &ci->cc_princhash.cp_len);
+                       if (princhashlen > 0) {
+                               princhash.data = memdup_user(
+                                               &ci->cc_princhash.cp_data,
+                                               princhashlen);
+                               if (IS_ERR_OR_NULL(princhash.data))
+                                       return -EFAULT;
+                               princhash.len = princhashlen;
+                       } else
+                               princhash.len = 0;
+               } else {
+                       const struct cld_name __user *cnm;
+
+                       cnm = &cmsg->cm_u.cm_name;
+                       if (get_user(namelen, &cnm->cn_len))
+                               return -EFAULT;
+                       name.data = memdup_user(&cnm->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)) {
+               if (!nfs4_client_to_reclaim(name, princhash, nn)) {
                        kfree(name.data);
+                       kfree(princhash.data);
                        return -EFAULT;
                }
-               return sizeof(*cmsg);
+               return nn->client_tracking_ops->msglen;
        }
        return -EFAULT;
 }
@@ -818,21 +860,22 @@ static ssize_t
 cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
 {
        struct cld_upcall *tmp, *cup;
-       struct cld_msg __user *cmsg = (struct cld_msg __user *)src;
+       struct cld_msg_hdr __user *hdr = (struct cld_msg_hdr __user *)src;
+       struct cld_msg_v2 __user *cmsg = (struct cld_msg_v2 __user *)src;
        uint32_t xid;
        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)) {
+       if (mlen != nn->client_tracking_ops->msglen) {
                dprintk("%s: got %zu bytes, expected %zu\n", __func__, mlen,
-                       sizeof(*cmsg));
+                       nn->client_tracking_ops->msglen);
                return -EINVAL;
        }
 
        /* copy just the xid so we can try to find that */
-       if (copy_from_user(&xid, &cmsg->cm_xid, sizeof(xid)) != 0) {
+       if (copy_from_user(&xid, &hdr->cm_xid, sizeof(xid)) != 0) {
                dprintk("%s: error when copying xid from userspace", __func__);
                return -EFAULT;
        }
@@ -842,7 +885,7 @@ cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
         * 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)) {
+       if (get_user(status, &hdr->cm_status)) {
                dprintk("%s: error when copying status from userspace", __func__);
                return -EFAULT;
        }
@@ -851,7 +894,7 @@ cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
        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) {
+               if (get_unaligned(&tmp->cu_u.cu_hdr.cm_xid) == xid) {
                        cup = tmp;
                        if (status != -EINPROGRESS)
                                list_del_init(&cup->cu_list);
@@ -869,7 +912,7 @@ cld_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
        if (status == -EINPROGRESS)
                return __cld_pipe_inprogress_downcall(cmsg, nn);
 
-       if (copy_from_user(&cup->cu_msg, src, mlen) != 0)
+       if (copy_from_user(&cup->cu_u.cu_msg_v2, src, mlen) != 0)
                return -EFAULT;
 
        complete(&cup->cu_done);
@@ -881,7 +924,7 @@ cld_pipe_destroy_msg(struct rpc_pipe_msg *msg)
 {
        struct cld_msg *cmsg = msg->data;
        struct cld_upcall *cup = container_of(cmsg, struct cld_upcall,
-                                                cu_msg);
+                                                cu_u.cu_msg);
 
        /* errno >= 0 means we got a downcall */
        if (msg->errno >= 0)
@@ -1007,14 +1050,17 @@ nfsd4_remove_cld_pipe(struct net *net)
 
        nfsd4_cld_unregister_net(net, cn->cn_pipe);
        rpc_destroy_pipe_data(cn->cn_pipe);
+       if (cn->cn_tfm)
+               crypto_free_shash(cn->cn_tfm);
        kfree(nn->cld_net);
        nn->cld_net = NULL;
 }
 
 static struct cld_upcall *
-alloc_cld_upcall(struct cld_net *cn)
+alloc_cld_upcall(struct nfsd_net *nn)
 {
        struct cld_upcall *new, *tmp;
+       struct cld_net *cn = nn->cld_net;
 
        new = kzalloc(sizeof(*new), GFP_KERNEL);
        if (!new)
@@ -1024,20 +1070,20 @@ alloc_cld_upcall(struct cld_net *cn)
 restart_search:
        spin_lock(&cn->cn_lock);
        list_for_each_entry(tmp, &cn->cn_list, cu_list) {
-               if (tmp->cu_msg.cm_xid == cn->cn_xid) {
+               if (tmp->cu_u.cu_msg.cm_xid == cn->cn_xid) {
                        cn->cn_xid++;
                        spin_unlock(&cn->cn_lock);
                        goto restart_search;
                }
        }
        init_completion(&new->cu_done);
-       new->cu_msg.cm_vers = CLD_UPCALL_VERSION;
-       put_unaligned(cn->cn_xid++, &new->cu_msg.cm_xid);
+       new->cu_u.cu_msg.cm_vers = nn->client_tracking_ops->version;
+       put_unaligned(cn->cn_xid++, &new->cu_u.cu_msg.cm_xid);
        new->cu_net = cn;
        list_add(&new->cu_list, &cn->cn_list);
        spin_unlock(&cn->cn_lock);
 
-       dprintk("%s: allocated xid %u\n", __func__, new->cu_msg.cm_xid);
+       dprintk("%s: allocated xid %u\n", __func__, new->cu_u.cu_msg.cm_xid);
 
        return new;
 }
@@ -1066,20 +1112,20 @@ nfsd4_cld_create(struct nfs4_client *clp)
        if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
                return;
 
-       cup = alloc_cld_upcall(cn);
+       cup = alloc_cld_upcall(nn);
        if (!cup) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       cup->cu_msg.cm_cmd = Cld_Create;
-       cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
-       memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
+       cup->cu_u.cu_msg.cm_cmd = Cld_Create;
+       cup->cu_u.cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
+       memcpy(cup->cu_u.cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
                        clp->cl_name.len);
 
-       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
        if (!ret) {
-               ret = cup->cu_msg.cm_status;
+               ret = cup->cu_u.cu_msg.cm_status;
                set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
        }
 
@@ -1090,6 +1136,75 @@ nfsd4_cld_create(struct nfs4_client *clp)
                                "record on stable storage: %d\n", ret);
 }
 
+/* Ask daemon to create a new record */
+static void
+nfsd4_cld_create_v2(struct nfs4_client *clp)
+{
+       int ret;
+       struct cld_upcall *cup;
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+       struct cld_net *cn = nn->cld_net;
+       struct cld_msg_v2 *cmsg;
+       struct crypto_shash *tfm = cn->cn_tfm;
+       struct xdr_netobj cksum;
+       char *principal = NULL;
+       SHASH_DESC_ON_STACK(desc, tfm);
+
+       /* Don't upcall if it's already stored */
+       if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
+               return;
+
+       cup = alloc_cld_upcall(nn);
+       if (!cup) {
+               ret = -ENOMEM;
+               goto out_err;
+       }
+
+       cmsg = &cup->cu_u.cu_msg_v2;
+       cmsg->cm_cmd = Cld_Create;
+       cmsg->cm_u.cm_clntinfo.cc_name.cn_len = clp->cl_name.len;
+       memcpy(cmsg->cm_u.cm_clntinfo.cc_name.cn_id, clp->cl_name.data,
+                       clp->cl_name.len);
+       if (clp->cl_cred.cr_raw_principal)
+               principal = clp->cl_cred.cr_raw_principal;
+       else if (clp->cl_cred.cr_principal)
+               principal = clp->cl_cred.cr_principal;
+       if (principal) {
+               desc->tfm = tfm;
+               cksum.len = crypto_shash_digestsize(tfm);
+               cksum.data = kmalloc(cksum.len, GFP_KERNEL);
+               if (cksum.data == NULL) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               ret = crypto_shash_digest(desc, principal, strlen(principal),
+                                         cksum.data);
+               shash_desc_zero(desc);
+               if (ret) {
+                       kfree(cksum.data);
+                       goto out;
+               }
+               cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len = cksum.len;
+               memcpy(cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data,
+                      cksum.data, cksum.len);
+               kfree(cksum.data);
+       } else
+               cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len = 0;
+
+       ret = cld_pipe_upcall(cn->cn_pipe, cmsg);
+       if (!ret) {
+               ret = cmsg->cm_status;
+               set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
+       }
+
+out:
+       free_cld_upcall(cup);
+out_err:
+       if (ret)
+               pr_err("NFSD: Unable to create client record on stable storage: %d\n",
+                               ret);
+}
+
 /* Ask daemon to create a new record */
 static void
 nfsd4_cld_remove(struct nfs4_client *clp)
@@ -1103,20 +1218,20 @@ nfsd4_cld_remove(struct nfs4_client *clp)
        if (!test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
                return;
 
-       cup = alloc_cld_upcall(cn);
+       cup = alloc_cld_upcall(nn);
        if (!cup) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       cup->cu_msg.cm_cmd = Cld_Remove;
-       cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
-       memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
+       cup->cu_u.cu_msg.cm_cmd = Cld_Remove;
+       cup->cu_u.cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
+       memcpy(cup->cu_u.cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
                        clp->cl_name.len);
 
-       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
        if (!ret) {
-               ret = cup->cu_msg.cm_status;
+               ret = cup->cu_u.cu_msg.cm_status;
                clear_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
        }
 
@@ -1145,21 +1260,21 @@ nfsd4_cld_check_v0(struct nfs4_client *clp)
        if (test_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags))
                return 0;
 
-       cup = alloc_cld_upcall(cn);
+       cup = alloc_cld_upcall(nn);
        if (!cup) {
                printk(KERN_ERR "NFSD: Unable to check client record on "
                                "stable storage: %d\n", -ENOMEM);
                return -ENOMEM;
        }
 
-       cup->cu_msg.cm_cmd = Cld_Check;
-       cup->cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
-       memcpy(cup->cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
+       cup->cu_u.cu_msg.cm_cmd = Cld_Check;
+       cup->cu_u.cu_msg.cm_u.cm_name.cn_len = clp->cl_name.len;
+       memcpy(cup->cu_u.cu_msg.cm_u.cm_name.cn_id, clp->cl_name.data,
                        clp->cl_name.len);
 
-       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
        if (!ret) {
-               ret = cup->cu_msg.cm_status;
+               ret = cup->cu_u.cu_msg.cm_status;
                set_bit(NFSD4_CLIENT_STABLE, &clp->cl_flags);
        }
 
@@ -1216,6 +1331,79 @@ nfsd4_cld_check(struct nfs4_client *clp)
        return 0;
 }
 
+static int
+nfsd4_cld_check_v2(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;
+       struct crypto_shash *tfm = cn->cn_tfm;
+       struct xdr_netobj cksum;
+       char *principal = NULL;
+       SHASH_DESC_ON_STACK(desc, tfm);
+
+       /* 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:
+       if (crp->cr_princhash.len) {
+               if (clp->cl_cred.cr_raw_principal)
+                       principal = clp->cl_cred.cr_raw_principal;
+               else if (clp->cl_cred.cr_principal)
+                       principal = clp->cl_cred.cr_principal;
+               if (principal == NULL)
+                       return -ENOENT;
+               desc->tfm = tfm;
+               cksum.len = crypto_shash_digestsize(tfm);
+               cksum.data = kmalloc(cksum.len, GFP_KERNEL);
+               if (cksum.data == NULL)
+                       return -ENOENT;
+               status = crypto_shash_digest(desc, principal, strlen(principal),
+                                            cksum.data);
+               shash_desc_zero(desc);
+               if (status) {
+                       kfree(cksum.data);
+                       return -ENOENT;
+               }
+               if (memcmp(crp->cr_princhash.data, cksum.data,
+                               crp->cr_princhash.len)) {
+                       kfree(cksum.data);
+                       return -ENOENT;
+               }
+               kfree(cksum.data);
+       }
+       crp->cr_clp = clp;
+       return 0;
+}
+
 static int
 nfsd4_cld_grace_start(struct nfsd_net *nn)
 {
@@ -1223,16 +1411,16 @@ nfsd4_cld_grace_start(struct nfsd_net *nn)
        struct cld_upcall *cup;
        struct cld_net *cn = nn->cld_net;
 
-       cup = alloc_cld_upcall(cn);
+       cup = alloc_cld_upcall(nn);
        if (!cup) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       cup->cu_msg.cm_cmd = Cld_GraceStart;
-       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       cup->cu_u.cu_msg.cm_cmd = Cld_GraceStart;
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
        if (!ret)
-               ret = cup->cu_msg.cm_status;
+               ret = cup->cu_u.cu_msg.cm_status;
 
        free_cld_upcall(cup);
 out_err:
@@ -1250,17 +1438,17 @@ nfsd4_cld_grace_done_v0(struct nfsd_net *nn)
        struct cld_upcall *cup;
        struct cld_net *cn = nn->cld_net;
 
-       cup = alloc_cld_upcall(cn);
+       cup = alloc_cld_upcall(nn);
        if (!cup) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       cup->cu_msg.cm_cmd = Cld_GraceDone;
-       cup->cu_msg.cm_u.cm_gracetime = (int64_t)nn->boot_time;
-       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       cup->cu_u.cu_msg.cm_cmd = Cld_GraceDone;
+       cup->cu_u.cu_msg.cm_u.cm_gracetime = (int64_t)nn->boot_time;
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
        if (!ret)
-               ret = cup->cu_msg.cm_status;
+               ret = cup->cu_u.cu_msg.cm_status;
 
        free_cld_upcall(cup);
 out_err:
@@ -1279,16 +1467,16 @@ nfsd4_cld_grace_done(struct nfsd_net *nn)
        struct cld_upcall *cup;
        struct cld_net *cn = nn->cld_net;
 
-       cup = alloc_cld_upcall(cn);
+       cup = alloc_cld_upcall(nn);
        if (!cup) {
                ret = -ENOMEM;
                goto out_err;
        }
 
-       cup->cu_msg.cm_cmd = Cld_GraceDone;
-       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_msg);
+       cup->cu_u.cu_msg.cm_cmd = Cld_GraceDone;
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
        if (!ret)
-               ret = cup->cu_msg.cm_status;
+               ret = cup->cu_u.cu_msg.cm_status;
 
        free_cld_upcall(cup);
 out_err:
@@ -1336,6 +1524,53 @@ cld_running(struct nfsd_net *nn)
        return pipe->nreaders || pipe->nwriters;
 }
 
+static int
+nfsd4_cld_get_version(struct nfsd_net *nn)
+{
+       int ret = 0;
+       struct cld_upcall *cup;
+       struct cld_net *cn = nn->cld_net;
+       uint8_t version;
+
+       cup = alloc_cld_upcall(nn);
+       if (!cup) {
+               ret = -ENOMEM;
+               goto out_err;
+       }
+       cup->cu_u.cu_msg.cm_cmd = Cld_GetVersion;
+       ret = cld_pipe_upcall(cn->cn_pipe, &cup->cu_u.cu_msg);
+       if (!ret) {
+               ret = cup->cu_u.cu_msg.cm_status;
+               if (ret)
+                       goto out_free;
+               version = cup->cu_u.cu_msg.cm_u.cm_version;
+               dprintk("%s: userspace returned version %u\n",
+                               __func__, version);
+               if (version < 1)
+                       version = 1;
+               else if (version > CLD_UPCALL_VERSION)
+                       version = CLD_UPCALL_VERSION;
+
+               switch (version) {
+               case 1:
+                       nn->client_tracking_ops = &nfsd4_cld_tracking_ops;
+                       break;
+               case 2:
+                       nn->client_tracking_ops = &nfsd4_cld_tracking_ops_v2;
+                       break;
+               default:
+                       break;
+               }
+       }
+out_free:
+       free_cld_upcall(cup);
+out_err:
+       if (ret)
+               dprintk("%s: Unable to get version from userspace: %d\n",
+                       __func__, ret);
+       return ret;
+}
+
 static int
 nfsd4_cld_tracking_init(struct net *net)
 {
@@ -1351,6 +1586,11 @@ nfsd4_cld_tracking_init(struct net *net)
        status = __nfsd4_init_cld_pipe(net);
        if (status)
                goto err_shutdown;
+       nn->cld_net->cn_tfm = crypto_alloc_shash("sha256", 0, 0);
+       if (IS_ERR(nn->cld_net->cn_tfm)) {
+               status = PTR_ERR(nn->cld_net->cn_tfm);
+               goto err_remove;
+       }
 
        /*
         * rpc pipe upcalls take 30 seconds to time out, so we don't want to
@@ -1368,10 +1608,14 @@ nfsd4_cld_tracking_init(struct net *net)
                goto err_remove;
        }
 
+       status = nfsd4_cld_get_version(nn);
+       if (status == -EOPNOTSUPP)
+               pr_warn("NFSD: nfsdcld GetVersion upcall failed. Please upgrade nfsdcld.\n");
+
        status = nfsd4_cld_grace_start(nn);
        if (status) {
                if (status == -EOPNOTSUPP)
-                       printk(KERN_WARNING "NFSD: Please upgrade nfsdcld.\n");
+                       pr_warn("NFSD: nfsdcld GraceStart upcall failed. Please upgrade nfsdcld.\n");
                nfs4_release_reclaim(nn);
                goto err_remove;
        } else
@@ -1403,6 +1647,8 @@ static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops_v0 = {
        .remove         = nfsd4_cld_remove,
        .check          = nfsd4_cld_check_v0,
        .grace_done     = nfsd4_cld_grace_done_v0,
+       .version        = 1,
+       .msglen         = sizeof(struct cld_msg),
 };
 
 /* For newer nfsdcld's */
@@ -1413,6 +1659,20 @@ static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops = {
        .remove         = nfsd4_cld_remove,
        .check          = nfsd4_cld_check,
        .grace_done     = nfsd4_cld_grace_done,
+       .version        = 1,
+       .msglen         = sizeof(struct cld_msg),
+};
+
+/* v2 create/check ops include the principal, if available */
+static const struct nfsd4_client_tracking_ops nfsd4_cld_tracking_ops_v2 = {
+       .init           = nfsd4_cld_tracking_init,
+       .exit           = nfsd4_cld_tracking_exit,
+       .create         = nfsd4_cld_create_v2,
+       .remove         = nfsd4_cld_remove,
+       .check          = nfsd4_cld_check_v2,
+       .grace_done     = nfsd4_cld_grace_done,
+       .version        = 2,
+       .msglen         = sizeof(struct cld_msg_v2),
 };
 
 /* upcall via usermodehelper */
@@ -1760,6 +2020,8 @@ static const struct nfsd4_client_tracking_ops nfsd4_umh_tracking_ops = {
        .remove         = nfsd4_umh_cltrack_remove,
        .check          = nfsd4_umh_cltrack_check,
        .grace_done     = nfsd4_umh_cltrack_grace_done,
+       .version        = 1,
+       .msglen         = 0,
 };
 
 int