Merge branch 'for-2.6.38' of git://linux-nfs.org/~bfields/linux
[pandora-kernel.git] / fs / nfsd / nfs4callback.c
index ae93c5c..3be975e 100644 (file)
@@ -50,11 +50,6 @@ enum {
        NFSPROC4_CLNT_CB_SEQUENCE,
 };
 
-enum nfs_cb_opnum4 {
-       OP_CB_RECALL            = 4,
-       OP_CB_SEQUENCE          = 11,
-};
-
 #define NFS4_MAXTAGLEN         20
 
 #define NFS4_enc_cb_null_sz            0
@@ -79,61 +74,6 @@ enum nfs_cb_opnum4 {
                                        cb_sequence_dec_sz +            \
                                        op_dec_sz)
 
-/*
-* Generic encode routines from fs/nfs/nfs4xdr.c
-*/
-static inline __be32 *
-xdr_writemem(__be32 *p, const void *ptr, int nbytes)
-{
-       int tmp = XDR_QUADLEN(nbytes);
-       if (!tmp)
-               return p;
-       p[tmp-1] = 0;
-       memcpy(p, ptr, nbytes);
-       return p + tmp;
-}
-
-#define WRITE32(n)               *p++ = htonl(n)
-#define WRITEMEM(ptr,nbytes)     do {                           \
-       p = xdr_writemem(p, ptr, nbytes);                       \
-} while (0)
-#define RESERVE_SPACE(nbytes)   do {                            \
-       p = xdr_reserve_space(xdr, nbytes);                     \
-       if (!p) dprintk("NFSD: RESERVE_SPACE(%d) failed in function %s\n", (int) (nbytes), __func__); \
-       BUG_ON(!p);                                             \
-} while (0)
-
-/*
- * Generic decode routines from fs/nfs/nfs4xdr.c
- */
-#define DECODE_TAIL                             \
-       status = 0;                             \
-out:                                            \
-       return status;                          \
-xdr_error:                                      \
-       dprintk("NFSD: xdr error! (%s:%d)\n", __FILE__, __LINE__); \
-       status = -EIO;                          \
-       goto out
-
-#define READ32(x)         (x) = ntohl(*p++)
-#define READ64(x)         do {                  \
-       (x) = (u64)ntohl(*p++) << 32;           \
-       (x) |= ntohl(*p++);                     \
-} while (0)
-#define READTIME(x)       do {                  \
-       p++;                                    \
-       (x.tv_sec) = ntohl(*p++);               \
-       (x.tv_nsec) = ntohl(*p++);              \
-} while (0)
-#define READ_BUF(nbytes)  do { \
-       p = xdr_inline_decode(xdr, nbytes); \
-       if (!p) { \
-               dprintk("NFSD: %s: reply buffer overflowed in line %d.\n", \
-                       __func__, __LINE__); \
-               return -EIO; \
-       } \
-} while (0)
-
 struct nfs4_cb_compound_hdr {
        /* args */
        u32             ident;  /* minorversion 0 only */
@@ -144,295 +84,513 @@ struct nfs4_cb_compound_hdr {
        int             status;
 };
 
-static struct {
-int stat;
-int errno;
-} nfs_cb_errtbl[] = {
-       { NFS4_OK,              0               },
-       { NFS4ERR_PERM,         EPERM           },
-       { NFS4ERR_NOENT,        ENOENT          },
-       { NFS4ERR_IO,           EIO             },
-       { NFS4ERR_NXIO,         ENXIO           },
-       { NFS4ERR_ACCESS,       EACCES          },
-       { NFS4ERR_EXIST,        EEXIST          },
-       { NFS4ERR_XDEV,         EXDEV           },
-       { NFS4ERR_NOTDIR,       ENOTDIR         },
-       { NFS4ERR_ISDIR,        EISDIR          },
-       { NFS4ERR_INVAL,        EINVAL          },
-       { NFS4ERR_FBIG,         EFBIG           },
-       { NFS4ERR_NOSPC,        ENOSPC          },
-       { NFS4ERR_ROFS,         EROFS           },
-       { NFS4ERR_MLINK,        EMLINK          },
-       { NFS4ERR_NAMETOOLONG,  ENAMETOOLONG    },
-       { NFS4ERR_NOTEMPTY,     ENOTEMPTY       },
-       { NFS4ERR_DQUOT,        EDQUOT          },
-       { NFS4ERR_STALE,        ESTALE          },
-       { NFS4ERR_BADHANDLE,    EBADHANDLE      },
-       { NFS4ERR_BAD_COOKIE,   EBADCOOKIE      },
-       { NFS4ERR_NOTSUPP,      ENOTSUPP        },
-       { NFS4ERR_TOOSMALL,     ETOOSMALL       },
-       { NFS4ERR_SERVERFAULT,  ESERVERFAULT    },
-       { NFS4ERR_BADTYPE,      EBADTYPE        },
-       { NFS4ERR_LOCKED,       EAGAIN          },
-       { NFS4ERR_RESOURCE,     EREMOTEIO       },
-       { NFS4ERR_SYMLINK,      ELOOP           },
-       { NFS4ERR_OP_ILLEGAL,   EOPNOTSUPP      },
-       { NFS4ERR_DEADLOCK,     EDEADLK         },
-       { -1,                   EIO             }
-};
+/*
+ * Handle decode buffer overflows out-of-line.
+ */
+static void print_overflow_msg(const char *func, const struct xdr_stream *xdr)
+{
+       dprintk("NFS: %s prematurely hit the end of our receive buffer. "
+               "Remaining buffer length is %tu words.\n",
+               func, xdr->end - xdr->p);
+}
 
-static int
-nfs_cb_stat_to_errno(int stat)
+static __be32 *xdr_encode_empty_array(__be32 *p)
 {
-       int i;
-       for (i = 0; nfs_cb_errtbl[i].stat != -1; i++) {
-               if (nfs_cb_errtbl[i].stat == stat)
-                       return nfs_cb_errtbl[i].errno;
-       }
-       /* If we cannot translate the error, the recovery routines should
-       * handle it.
-       * Note: remaining NFSv4 error codes have values > 10000, so should
-       * not conflict with native Linux error codes.
-       */
-       return stat;
+       *p++ = xdr_zero;
+       return p;
 }
 
 /*
- * XDR encode
+ * Encode/decode NFSv4 CB basic data types
+ *
+ * Basic NFSv4 callback data types are defined in section 15 of RFC
+ * 3530: "Network File System (NFS) version 4 Protocol" and section
+ * 20 of RFC 5661: "Network File System (NFS) Version 4 Minor Version
+ * 1 Protocol"
+ */
+
+/*
+ *     nfs_cb_opnum4
+ *
+ *     enum nfs_cb_opnum4 {
+ *             OP_CB_GETATTR           = 3,
+ *               ...
+ *     };
  */
+enum nfs_cb_opnum4 {
+       OP_CB_GETATTR                   = 3,
+       OP_CB_RECALL                    = 4,
+       OP_CB_LAYOUTRECALL              = 5,
+       OP_CB_NOTIFY                    = 6,
+       OP_CB_PUSH_DELEG                = 7,
+       OP_CB_RECALL_ANY                = 8,
+       OP_CB_RECALLABLE_OBJ_AVAIL      = 9,
+       OP_CB_RECALL_SLOT               = 10,
+       OP_CB_SEQUENCE                  = 11,
+       OP_CB_WANTS_CANCELLED           = 12,
+       OP_CB_NOTIFY_LOCK               = 13,
+       OP_CB_NOTIFY_DEVICEID           = 14,
+       OP_CB_ILLEGAL                   = 10044
+};
 
-static void
-encode_stateid(struct xdr_stream *xdr, stateid_t *sid)
+static void encode_nfs_cb_opnum4(struct xdr_stream *xdr, enum nfs_cb_opnum4 op)
 {
        __be32 *p;
 
-       RESERVE_SPACE(sizeof(stateid_t));
-       WRITE32(sid->si_generation);
-       WRITEMEM(&sid->si_opaque, sizeof(stateid_opaque_t));
+       p = xdr_reserve_space(xdr, 4);
+       *p = cpu_to_be32(op);
 }
 
-static void
-encode_cb_compound_hdr(struct xdr_stream *xdr, struct nfs4_cb_compound_hdr *hdr)
+/*
+ * nfs_fh4
+ *
+ *     typedef opaque nfs_fh4<NFS4_FHSIZE>;
+ */
+static void encode_nfs_fh4(struct xdr_stream *xdr, const struct knfsd_fh *fh)
 {
-       __be32 * p;
+       u32 length = fh->fh_size;
+       __be32 *p;
 
-       RESERVE_SPACE(16);
-       WRITE32(0);            /* tag length is always 0 */
-       WRITE32(hdr->minorversion);
-       WRITE32(hdr->ident);
-       hdr->nops_p = p;
-       WRITE32(hdr->nops);
+       BUG_ON(length > NFS4_FHSIZE);
+       p = xdr_reserve_space(xdr, 4 + length);
+       xdr_encode_opaque(p, &fh->fh_base, length);
 }
 
-static void encode_cb_nops(struct nfs4_cb_compound_hdr *hdr)
+/*
+ * stateid4
+ *
+ *     struct stateid4 {
+ *             uint32_t        seqid;
+ *             opaque          other[12];
+ *     };
+ */
+static void encode_stateid4(struct xdr_stream *xdr, const stateid_t *sid)
 {
-       *hdr->nops_p = htonl(hdr->nops);
+       __be32 *p;
+
+       p = xdr_reserve_space(xdr, NFS4_STATEID_SIZE);
+       *p++ = cpu_to_be32(sid->si_generation);
+       xdr_encode_opaque_fixed(p, &sid->si_opaque, NFS4_STATEID_OTHER_SIZE);
 }
 
-static void
-encode_cb_recall(struct xdr_stream *xdr, struct nfs4_delegation *dp,
-               struct nfs4_cb_compound_hdr *hdr)
+/*
+ * sessionid4
+ *
+ *     typedef opaque sessionid4[NFS4_SESSIONID_SIZE];
+ */
+static void encode_sessionid4(struct xdr_stream *xdr,
+                             const struct nfsd4_session *session)
 {
        __be32 *p;
-       int len = dp->dl_fh.fh_size;
-
-       RESERVE_SPACE(4);
-       WRITE32(OP_CB_RECALL);
-       encode_stateid(xdr, &dp->dl_stateid);
-       RESERVE_SPACE(8 + (XDR_QUADLEN(len) << 2));
-       WRITE32(0); /* truncate optimization not implemented */
-       WRITE32(len);
-       WRITEMEM(&dp->dl_fh.fh_base, len);
-       hdr->nops++;
+
+       p = xdr_reserve_space(xdr, NFS4_MAX_SESSIONID_LEN);
+       xdr_encode_opaque_fixed(p, session->se_sessionid.data,
+                                       NFS4_MAX_SESSIONID_LEN);
 }
 
-static void
-encode_cb_sequence(struct xdr_stream *xdr, struct nfsd4_callback *cb,
-                  struct nfs4_cb_compound_hdr *hdr)
-{
-       __be32 *p;
-       struct nfsd4_session *ses = cb->cb_clp->cl_cb_session;
+/*
+ * nfsstat4
+ */
+static const struct {
+       int stat;
+       int errno;
+} nfs_cb_errtbl[] = {
+       { NFS4_OK,              0               },
+       { NFS4ERR_PERM,         -EPERM          },
+       { NFS4ERR_NOENT,        -ENOENT         },
+       { NFS4ERR_IO,           -EIO            },
+       { NFS4ERR_NXIO,         -ENXIO          },
+       { NFS4ERR_ACCESS,       -EACCES         },
+       { NFS4ERR_EXIST,        -EEXIST         },
+       { NFS4ERR_XDEV,         -EXDEV          },
+       { NFS4ERR_NOTDIR,       -ENOTDIR        },
+       { NFS4ERR_ISDIR,        -EISDIR         },
+       { NFS4ERR_INVAL,        -EINVAL         },
+       { NFS4ERR_FBIG,         -EFBIG          },
+       { NFS4ERR_NOSPC,        -ENOSPC         },
+       { NFS4ERR_ROFS,         -EROFS          },
+       { NFS4ERR_MLINK,        -EMLINK         },
+       { NFS4ERR_NAMETOOLONG,  -ENAMETOOLONG   },
+       { NFS4ERR_NOTEMPTY,     -ENOTEMPTY      },
+       { NFS4ERR_DQUOT,        -EDQUOT         },
+       { NFS4ERR_STALE,        -ESTALE         },
+       { NFS4ERR_BADHANDLE,    -EBADHANDLE     },
+       { NFS4ERR_BAD_COOKIE,   -EBADCOOKIE     },
+       { NFS4ERR_NOTSUPP,      -ENOTSUPP       },
+       { NFS4ERR_TOOSMALL,     -ETOOSMALL      },
+       { NFS4ERR_SERVERFAULT,  -ESERVERFAULT   },
+       { NFS4ERR_BADTYPE,      -EBADTYPE       },
+       { NFS4ERR_LOCKED,       -EAGAIN         },
+       { NFS4ERR_RESOURCE,     -EREMOTEIO      },
+       { NFS4ERR_SYMLINK,      -ELOOP          },
+       { NFS4ERR_OP_ILLEGAL,   -EOPNOTSUPP     },
+       { NFS4ERR_DEADLOCK,     -EDEADLK        },
+       { -1,                   -EIO            }
+};
 
-       if (hdr->minorversion == 0)
-               return;
+/*
+ * If we cannot translate the error, the recovery routines should
+ * handle it.
+ *
+ * Note: remaining NFSv4 error codes have values > 10000, so should
+ * not conflict with native Linux error codes.
+ */
+static int nfs_cb_stat_to_errno(int status)
+{
+       int i;
 
-       RESERVE_SPACE(1 + NFS4_MAX_SESSIONID_LEN + 20);
+       for (i = 0; nfs_cb_errtbl[i].stat != -1; i++) {
+               if (nfs_cb_errtbl[i].stat == status)
+                       return nfs_cb_errtbl[i].errno;
+       }
 
-       WRITE32(OP_CB_SEQUENCE);
-       WRITEMEM(ses->se_sessionid.data, NFS4_MAX_SESSIONID_LEN);
-       WRITE32(ses->se_cb_seq_nr);
-       WRITE32(0);             /* slotid, always 0 */
-       WRITE32(0);             /* highest slotid always 0 */
-       WRITE32(0);             /* cachethis always 0 */
-       WRITE32(0); /* FIXME: support referring_call_lists */
-       hdr->nops++;
+       dprintk("NFSD: Unrecognized NFS CB status value: %u\n", status);
+       return -status;
 }
 
-static int
-nfs4_xdr_enc_cb_null(struct rpc_rqst *req, __be32 *p)
+static int decode_cb_op_status(struct xdr_stream *xdr, enum nfs_opnum4 expected,
+                              enum nfsstat4 *status)
 {
-       struct xdr_stream xdrs, *xdr = &xdrs;
+       __be32 *p;
+       u32 op;
 
-       xdr_init_encode(&xdrs, &req->rq_snd_buf, p);
-        RESERVE_SPACE(0);
+       p = xdr_inline_decode(xdr, 4 + 4);
+       if (unlikely(p == NULL))
+               goto out_overflow;
+       op = be32_to_cpup(p++);
+       if (unlikely(op != expected))
+               goto out_unexpected;
+       *status = be32_to_cpup(p);
        return 0;
+out_overflow:
+       print_overflow_msg(__func__, xdr);
+       return -EIO;
+out_unexpected:
+       dprintk("NFSD: Callback server returned operation %d but "
+               "we issued a request for %d\n", op, expected);
+       return -EIO;
+}
+
+/*
+ * CB_COMPOUND4args
+ *
+ *     struct CB_COMPOUND4args {
+ *             utf8str_cs      tag;
+ *             uint32_t        minorversion;
+ *             uint32_t        callback_ident;
+ *             nfs_cb_argop4   argarray<>;
+ *     };
+*/
+static void encode_cb_compound4args(struct xdr_stream *xdr,
+                                   struct nfs4_cb_compound_hdr *hdr)
+{
+       __be32 * p;
+
+       p = xdr_reserve_space(xdr, 4 + 4 + 4 + 4);
+       p = xdr_encode_empty_array(p);          /* empty tag */
+       *p++ = cpu_to_be32(hdr->minorversion);
+       *p++ = cpu_to_be32(hdr->ident);
+
+       hdr->nops_p = p;
+       *p = cpu_to_be32(hdr->nops);            /* argarray element count */
 }
 
-static int
-nfs4_xdr_enc_cb_recall(struct rpc_rqst *req, __be32 *p,
-               struct nfsd4_callback *cb)
+/*
+ * Update argarray element count
+ */
+static void encode_cb_nops(struct nfs4_cb_compound_hdr *hdr)
 {
-       struct xdr_stream xdr;
-       struct nfs4_delegation *args = cb->cb_op;
-       struct nfs4_cb_compound_hdr hdr = {
-               .ident = cb->cb_clp->cl_cb_ident,
-               .minorversion = cb->cb_minorversion,
-       };
+       BUG_ON(hdr->nops > NFS4_MAX_BACK_CHANNEL_OPS);
+       *hdr->nops_p = cpu_to_be32(hdr->nops);
+}
 
-       xdr_init_encode(&xdr, &req->rq_snd_buf, p);
-       encode_cb_compound_hdr(&xdr, &hdr);
-       encode_cb_sequence(&xdr, cb, &hdr);
-       encode_cb_recall(&xdr, args, &hdr);
-       encode_cb_nops(&hdr);
+/*
+ * CB_COMPOUND4res
+ *
+ *     struct CB_COMPOUND4res {
+ *             nfsstat4        status;
+ *             utf8str_cs      tag;
+ *             nfs_cb_resop4   resarray<>;
+ *     };
+ */
+static int decode_cb_compound4res(struct xdr_stream *xdr,
+                                 struct nfs4_cb_compound_hdr *hdr)
+{
+       u32 length;
+       __be32 *p;
+
+       p = xdr_inline_decode(xdr, 4 + 4);
+       if (unlikely(p == NULL))
+               goto out_overflow;
+       hdr->status = be32_to_cpup(p++);
+       /* Ignore the tag */
+       length = be32_to_cpup(p++);
+       p = xdr_inline_decode(xdr, length + 4);
+       if (unlikely(p == NULL))
+               goto out_overflow;
+       hdr->nops = be32_to_cpup(p);
        return 0;
+out_overflow:
+       print_overflow_msg(__func__, xdr);
+       return -EIO;
 }
 
+/*
+ * CB_RECALL4args
+ *
+ *     struct CB_RECALL4args {
+ *             stateid4        stateid;
+ *             bool            truncate;
+ *             nfs_fh4         fh;
+ *     };
+ */
+static void encode_cb_recall4args(struct xdr_stream *xdr,
+                                 const struct nfs4_delegation *dp,
+                                 struct nfs4_cb_compound_hdr *hdr)
+{
+       __be32 *p;
+
+       encode_nfs_cb_opnum4(xdr, OP_CB_RECALL);
+       encode_stateid4(xdr, &dp->dl_stateid);
 
-static int
-decode_cb_compound_hdr(struct xdr_stream *xdr, struct nfs4_cb_compound_hdr *hdr){
-        __be32 *p;
-       u32 taglen;
+       p = xdr_reserve_space(xdr, 4);
+       *p++ = xdr_zero;                        /* truncate */
 
-        READ_BUF(8);
-        READ32(hdr->status);
-       /* We've got no use for the tag; ignore it: */
-        READ32(taglen);
-        READ_BUF(taglen + 4);
-        p += XDR_QUADLEN(taglen);
-        READ32(hdr->nops);
-        return 0;
+       encode_nfs_fh4(xdr, &dp->dl_fh);
+
+       hdr->nops++;
 }
 
-static int
-decode_cb_op_hdr(struct xdr_stream *xdr, enum nfs_opnum4 expected)
+/*
+ * CB_SEQUENCE4args
+ *
+ *     struct CB_SEQUENCE4args {
+ *             sessionid4              csa_sessionid;
+ *             sequenceid4             csa_sequenceid;
+ *             slotid4                 csa_slotid;
+ *             slotid4                 csa_highest_slotid;
+ *             bool                    csa_cachethis;
+ *             referring_call_list4    csa_referring_call_lists<>;
+ *     };
+ */
+static void encode_cb_sequence4args(struct xdr_stream *xdr,
+                                   const struct nfsd4_callback *cb,
+                                   struct nfs4_cb_compound_hdr *hdr)
 {
+       struct nfsd4_session *session = cb->cb_clp->cl_cb_session;
        __be32 *p;
-       u32 op;
-       int32_t nfserr;
-
-       READ_BUF(8);
-       READ32(op);
-       if (op != expected) {
-               dprintk("NFSD: decode_cb_op_hdr: Callback server returned "
-                        " operation %d but we issued a request for %d\n",
-                        op, expected);
-               return -EIO;
-       }
-       READ32(nfserr);
-       if (nfserr != NFS_OK)
-               return -nfs_cb_stat_to_errno(nfserr);
-       return 0;
+
+       if (hdr->minorversion == 0)
+               return;
+
+       encode_nfs_cb_opnum4(xdr, OP_CB_SEQUENCE);
+       encode_sessionid4(xdr, session);
+
+       p = xdr_reserve_space(xdr, 4 + 4 + 4 + 4 + 4);
+       *p++ = cpu_to_be32(session->se_cb_seq_nr);      /* csa_sequenceid */
+       *p++ = xdr_zero;                        /* csa_slotid */
+       *p++ = xdr_zero;                        /* csa_highest_slotid */
+       *p++ = xdr_zero;                        /* csa_cachethis */
+       xdr_encode_empty_array(p);              /* csa_referring_call_lists */
+
+       hdr->nops++;
 }
 
 /*
+ * CB_SEQUENCE4resok
+ *
+ *     struct CB_SEQUENCE4resok {
+ *             sessionid4      csr_sessionid;
+ *             sequenceid4     csr_sequenceid;
+ *             slotid4         csr_slotid;
+ *             slotid4         csr_highest_slotid;
+ *             slotid4         csr_target_highest_slotid;
+ *     };
+ *
+ *     union CB_SEQUENCE4res switch (nfsstat4 csr_status) {
+ *     case NFS4_OK:
+ *             CB_SEQUENCE4resok       csr_resok4;
+ *     default:
+ *             void;
+ *     };
+ *
  * Our current back channel implmentation supports a single backchannel
  * with a single slot.
  */
-static int
-decode_cb_sequence(struct xdr_stream *xdr, struct nfsd4_callback *cb,
-                  struct rpc_rqst *rqstp)
+static int decode_cb_sequence4resok(struct xdr_stream *xdr,
+                                   struct nfsd4_callback *cb)
 {
-       struct nfsd4_session *ses = cb->cb_clp->cl_cb_session;
+       struct nfsd4_session *session = cb->cb_clp->cl_cb_session;
        struct nfs4_sessionid id;
        int status;
-       u32 dummy;
        __be32 *p;
+       u32 dummy;
 
-       if (cb->cb_minorversion == 0)
-               return 0;
-
-       status = decode_cb_op_hdr(xdr, OP_CB_SEQUENCE);
-       if (status)
-               return status;
+       status = -ESERVERFAULT;
 
        /*
         * If the server returns different values for sessionID, slotID or
         * sequence number, the server is looney tunes.
         */
-       status = -ESERVERFAULT;
-
-       READ_BUF(NFS4_MAX_SESSIONID_LEN + 16);
+       p = xdr_inline_decode(xdr, NFS4_MAX_SESSIONID_LEN + 4 + 4);
+       if (unlikely(p == NULL))
+               goto out_overflow;
        memcpy(id.data, p, NFS4_MAX_SESSIONID_LEN);
-       p += XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN);
-       if (memcmp(id.data, ses->se_sessionid.data, NFS4_MAX_SESSIONID_LEN)) {
-               dprintk("%s Invalid session id\n", __func__);
+       if (memcmp(id.data, session->se_sessionid.data,
+                                       NFS4_MAX_SESSIONID_LEN) != 0) {
+               dprintk("NFS: %s Invalid session id\n", __func__);
                goto out;
        }
-       READ32(dummy);
-       if (dummy != ses->se_cb_seq_nr) {
-               dprintk("%s Invalid sequence number\n", __func__);
+       p += XDR_QUADLEN(NFS4_MAX_SESSIONID_LEN);
+
+       dummy = be32_to_cpup(p++);
+       if (dummy != session->se_cb_seq_nr) {
+               dprintk("NFS: %s Invalid sequence number\n", __func__);
                goto out;
        }
-       READ32(dummy);  /* slotid must be 0 */
+
+       dummy = be32_to_cpup(p++);
        if (dummy != 0) {
-               dprintk("%s Invalid slotid\n", __func__);
+               dprintk("NFS: %s Invalid slotid\n", __func__);
                goto out;
        }
-       /* FIXME: process highest slotid and target highest slotid */
+
+       /*
+        * FIXME: process highest slotid and target highest slotid
+        */
        status = 0;
 out:
        return status;
+out_overflow:
+       print_overflow_msg(__func__, xdr);
+       return -EIO;
 }
 
+static int decode_cb_sequence4res(struct xdr_stream *xdr,
+                                 struct nfsd4_callback *cb)
+{
+       enum nfsstat4 nfserr;
+       int status;
+
+       if (cb->cb_minorversion == 0)
+               return 0;
+
+       status = decode_cb_op_status(xdr, OP_CB_SEQUENCE, &nfserr);
+       if (unlikely(status))
+               goto out;
+       if (unlikely(nfserr != NFS4_OK))
+               goto out_default;
+       status = decode_cb_sequence4resok(xdr, cb);
+out:
+       return status;
+out_default:
+       return nfs_cb_stat_to_errno(status);
+}
+
+/*
+ * NFSv4.0 and NFSv4.1 XDR encode functions
+ *
+ * NFSv4.0 callback argument types are defined in section 15 of RFC
+ * 3530: "Network File System (NFS) version 4 Protocol" and section 20
+ * of RFC 5661:  "Network File System (NFS) Version 4 Minor Version 1
+ * Protocol".
+ */
+
+/*
+ * NB: Without this zero space reservation, callbacks over krb5p fail
+ */
+static void nfs4_xdr_enc_cb_null(struct rpc_rqst *req, struct xdr_stream *xdr,
+                                void *__unused)
+{
+       xdr_reserve_space(xdr, 0);
+}
+
+/*
+ * 20.2. Operation 4: CB_RECALL - Recall a Delegation
+ */
+static void nfs4_xdr_enc_cb_recall(struct rpc_rqst *req, struct xdr_stream *xdr,
+                                  const struct nfsd4_callback *cb)
+{
+       const struct nfs4_delegation *args = cb->cb_op;
+       struct nfs4_cb_compound_hdr hdr = {
+               .ident = cb->cb_clp->cl_cb_ident,
+               .minorversion = cb->cb_minorversion,
+       };
 
-static int
-nfs4_xdr_dec_cb_null(struct rpc_rqst *req, __be32 *p)
+       encode_cb_compound4args(xdr, &hdr);
+       encode_cb_sequence4args(xdr, cb, &hdr);
+       encode_cb_recall4args(xdr, args, &hdr);
+       encode_cb_nops(&hdr);
+}
+
+
+/*
+ * NFSv4.0 and NFSv4.1 XDR decode functions
+ *
+ * NFSv4.0 callback result types are defined in section 15 of RFC
+ * 3530: "Network File System (NFS) version 4 Protocol" and section 20
+ * of RFC 5661:  "Network File System (NFS) Version 4 Minor Version 1
+ * Protocol".
+ */
+
+static int nfs4_xdr_dec_cb_null(struct rpc_rqst *req, struct xdr_stream *xdr,
+                               void *__unused)
 {
        return 0;
 }
 
-static int
-nfs4_xdr_dec_cb_recall(struct rpc_rqst *rqstp, __be32 *p,
-               struct nfsd4_callback *cb)
+/*
+ * 20.2. Operation 4: CB_RECALL - Recall a Delegation
+ */
+static int nfs4_xdr_dec_cb_recall(struct rpc_rqst *rqstp,
+                                 struct xdr_stream *xdr,
+                                 struct nfsd4_callback *cb)
 {
-       struct xdr_stream xdr;
        struct nfs4_cb_compound_hdr hdr;
+       enum nfsstat4 nfserr;
        int status;
 
-       xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p);
-       status = decode_cb_compound_hdr(&xdr, &hdr);
-       if (status)
+       status = decode_cb_compound4res(xdr, &hdr);
+       if (unlikely(status))
                goto out;
-       if (cb) {
-               status = decode_cb_sequence(&xdr, cb, rqstp);
-               if (status)
+
+       if (cb != NULL) {
+               status = decode_cb_sequence4res(xdr, cb);
+               if (unlikely(status))
                        goto out;
        }
-       status = decode_cb_op_hdr(&xdr, OP_CB_RECALL);
+
+       status = decode_cb_op_status(xdr, OP_CB_RECALL, &nfserr);
+       if (unlikely(status))
+               goto out;
+       if (unlikely(nfserr != NFS4_OK))
+               goto out_default;
 out:
        return status;
+out_default:
+       return nfs_cb_stat_to_errno(status);
 }
 
 /*
  * RPC procedure tables
  */
-#define PROC(proc, call, argtype, restype)                              \
-[NFSPROC4_CLNT_##proc] = {                                             \
-        .p_proc   = NFSPROC4_CB_##call,                                        \
-        .p_encode = (kxdrproc_t) nfs4_xdr_##argtype,                    \
-        .p_decode = (kxdrproc_t) nfs4_xdr_##restype,                    \
-        .p_arglen = NFS4_##argtype##_sz,                                \
-        .p_replen = NFS4_##restype##_sz,                                \
-        .p_statidx = NFSPROC4_CB_##call,                               \
-       .p_name   = #proc,                                              \
-}
-
-static struct rpc_procinfo     nfs4_cb_procedures[] = {
-    PROC(CB_NULL,      NULL,     enc_cb_null,     dec_cb_null),
-    PROC(CB_RECALL,    COMPOUND,   enc_cb_recall,      dec_cb_recall),
+#define PROC(proc, call, argtype, restype)                             \
+[NFSPROC4_CLNT_##proc] = {                                             \
+       .p_proc    = NFSPROC4_CB_##call,                                \
+       .p_encode  = (kxdreproc_t)nfs4_xdr_enc_##argtype,               \
+       .p_decode  = (kxdrdproc_t)nfs4_xdr_dec_##restype,               \
+       .p_arglen  = NFS4_enc_##argtype##_sz,                           \
+       .p_replen  = NFS4_dec_##restype##_sz,                           \
+       .p_statidx = NFSPROC4_CB_##call,                                \
+       .p_name    = #proc,                                             \
+}
+
+static struct rpc_procinfo nfs4_cb_procedures[] = {
+       PROC(CB_NULL,   NULL,           cb_null,        cb_null),
+       PROC(CB_RECALL, COMPOUND,       cb_recall,      cb_recall),
 };
 
-static struct rpc_version       nfs_cb_version4 = {
+static struct rpc_version nfs_cb_version4 = {
 /*
  * Note on the callback rpc program version number: despite language in rfc
  * 5661 section 18.36.3 requiring servers to use 4 in this field, the
@@ -440,29 +598,29 @@ static struct rpc_version       nfs_cb_version4 = {
  * in practice that appears to be what implementations use.  The section
  * 18.36.3 language is expected to be fixed in an erratum.
  */
-        .number                 = 1,
-        .nrprocs                = ARRAY_SIZE(nfs4_cb_procedures),
-        .procs                  = nfs4_cb_procedures
+       .number                 = 1,
+       .nrprocs                = ARRAY_SIZE(nfs4_cb_procedures),
+       .procs                  = nfs4_cb_procedures
 };
 
-static struct rpc_version *    nfs_cb_version[] = {
+static struct rpc_version *nfs_cb_version[] = {
        &nfs_cb_version4,
 };
 
 static struct rpc_program cb_program;
 
 static struct rpc_stat cb_stats = {
-               .program        = &cb_program
+       .program                = &cb_program
 };
 
 #define NFS4_CALLBACK 0x40000000
 static struct rpc_program cb_program = {
-               .name           = "nfs4_cb",
-               .number         = NFS4_CALLBACK,
-               .nrvers         = ARRAY_SIZE(nfs_cb_version),
-               .version        = nfs_cb_version,
-               .stats          = &cb_stats,
-               .pipe_dir_name  = "/nfsd4_cb",
+       .name                   = "nfs4_cb",
+       .number                 = NFS4_CALLBACK,
+       .nrvers                 = ARRAY_SIZE(nfs_cb_version),
+       .version                = nfs_cb_version,
+       .stats                  = &cb_stats,
+       .pipe_dir_name          = "/nfsd4_cb",
 };
 
 static int max_cb_time(void)