root/fs/nfs/nfs2xdr.c
// SPDX-License-Identifier: GPL-2.0
/*
 * linux/fs/nfs/nfs2xdr.c
 *
 * XDR functions to encode/decode NFS RPC arguments and results.
 *
 * Copyright (C) 1992, 1993, 1994  Rick Sladkey
 * Copyright (C) 1996 Olaf Kirch
 * 04 Aug 1998  Ion Badulescu <ionut@cs.columbia.edu>
 *              FIFO's need special handling in NFSv2
 */

#include <linux/param.h>
#include <linux/time.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/in.h>
#include <linux/pagemap.h>
#include <linux/proc_fs.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs2.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_common.h>
#include "internal.h"
#include "nfstrace.h"

#define NFSDBG_FACILITY         NFSDBG_XDR

/*
 * Declare the space requirements for NFS arguments and replies as
 * number of 32bit-words
 */
#define NFS_pagepad_sz          (1) /* Page padding */
#define NFS_fhandle_sz          (8)
#define NFS_sattr_sz            (8)
#define NFS_filename_sz         (1+(NFS2_MAXNAMLEN>>2))
#define NFS_path_sz             (1+(NFS2_MAXPATHLEN>>2))
#define NFS_fattr_sz            (17)
#define NFS_info_sz             (5)
#define NFS_entry_sz            (NFS_filename_sz+3)

#define NFS_diropargs_sz        (NFS_fhandle_sz+NFS_filename_sz)
#define NFS_removeargs_sz       (NFS_fhandle_sz+NFS_filename_sz)
#define NFS_sattrargs_sz        (NFS_fhandle_sz+NFS_sattr_sz)
#define NFS_readlinkargs_sz     (NFS_fhandle_sz)
#define NFS_readargs_sz         (NFS_fhandle_sz+3)
#define NFS_writeargs_sz        (NFS_fhandle_sz+4)
#define NFS_createargs_sz       (NFS_diropargs_sz+NFS_sattr_sz)
#define NFS_renameargs_sz       (NFS_diropargs_sz+NFS_diropargs_sz)
#define NFS_linkargs_sz         (NFS_fhandle_sz+NFS_diropargs_sz)
#define NFS_symlinkargs_sz      (NFS_diropargs_sz+1+NFS_sattr_sz)
#define NFS_readdirargs_sz      (NFS_fhandle_sz+2)

#define NFS_attrstat_sz         (1+NFS_fattr_sz)
#define NFS_diropres_sz         (1+NFS_fhandle_sz+NFS_fattr_sz)
#define NFS_readlinkres_sz      (2+NFS_pagepad_sz)
#define NFS_readres_sz          (1+NFS_fattr_sz+1+NFS_pagepad_sz)
#define NFS_writeres_sz         (NFS_attrstat_sz)
#define NFS_stat_sz             (1)
#define NFS_readdirres_sz       (1+NFS_pagepad_sz)
#define NFS_statfsres_sz        (1+NFS_info_sz)

/*
 * Encode/decode NFSv2 basic data types
 *
 * Basic NFSv2 data types are defined in section 2.3 of RFC 1094:
 * "NFS: Network File System Protocol Specification".
 *
 * Not all basic data types have their own encoding and decoding
 * functions.  For run-time efficiency, some data types are encoded
 * or decoded inline.
 */

static struct user_namespace *rpc_userns(const struct rpc_clnt *clnt)
{
        if (clnt && clnt->cl_cred)
                return clnt->cl_cred->user_ns;
        return &init_user_ns;
}

static struct user_namespace *rpc_rqst_userns(const struct rpc_rqst *rqstp)
{
        if (rqstp->rq_task)
                return rpc_userns(rqstp->rq_task->tk_client);
        return &init_user_ns;
}

/*
 *      typedef opaque  nfsdata<>;
 */
static int decode_nfsdata(struct xdr_stream *xdr, struct nfs_pgio_res *result)
{
        u32 recvd, count;
        __be32 *p;

        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EIO;
        count = be32_to_cpup(p);
        recvd = xdr_read_pages(xdr, count);
        if (unlikely(count > recvd))
                goto out_cheating;
out:
        result->eof = 0;        /* NFSv2 does not pass EOF flag on the wire. */
        result->count = count;
        return count;
out_cheating:
        dprintk("NFS: server cheating in read result: "
                "count %u > recvd %u\n", count, recvd);
        count = recvd;
        goto out;
}

/*
 *      enum stat {
 *              NFS_OK = 0,
 *              NFSERR_PERM = 1,
 *              NFSERR_NOENT = 2,
 *              NFSERR_IO = 5,
 *              NFSERR_NXIO = 6,
 *              NFSERR_ACCES = 13,
 *              NFSERR_EXIST = 17,
 *              NFSERR_NODEV = 19,
 *              NFSERR_NOTDIR = 20,
 *              NFSERR_ISDIR = 21,
 *              NFSERR_FBIG = 27,
 *              NFSERR_NOSPC = 28,
 *              NFSERR_ROFS = 30,
 *              NFSERR_NAMETOOLONG = 63,
 *              NFSERR_NOTEMPTY = 66,
 *              NFSERR_DQUOT = 69,
 *              NFSERR_STALE = 70,
 *              NFSERR_WFLUSH = 99
 *      };
 */
static int decode_stat(struct xdr_stream *xdr, enum nfs_stat *status)
{
        __be32 *p;

        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EIO;
        if (unlikely(*p != cpu_to_be32(NFS_OK)))
                goto out_status;
        *status = 0;
        return 0;
out_status:
        *status = be32_to_cpup(p);
        trace_nfs_xdr_status(xdr, (int)*status);
        return 0;
}

/*
 * 2.3.2.  ftype
 *
 *      enum ftype {
 *              NFNON = 0,
 *              NFREG = 1,
 *              NFDIR = 2,
 *              NFBLK = 3,
 *              NFCHR = 4,
 *              NFLNK = 5
 *      };
 *
 */
static __be32 *xdr_decode_ftype(__be32 *p, u32 *type)
{
        *type = be32_to_cpup(p++);
        if (unlikely(*type > NF2FIFO))
                *type = NFBAD;
        return p;
}

/*
 * 2.3.3.  fhandle
 *
 *      typedef opaque fhandle[FHSIZE];
 */
static void encode_fhandle(struct xdr_stream *xdr, const struct nfs_fh *fh)
{
        __be32 *p;

        p = xdr_reserve_space(xdr, NFS2_FHSIZE);
        memcpy(p, fh->data, NFS2_FHSIZE);
}

static int decode_fhandle(struct xdr_stream *xdr, struct nfs_fh *fh)
{
        __be32 *p;

        p = xdr_inline_decode(xdr, NFS2_FHSIZE);
        if (unlikely(!p))
                return -EIO;
        fh->size = NFS2_FHSIZE;
        memcpy(fh->data, p, NFS2_FHSIZE);
        return 0;
}

/*
 * 2.3.4.  timeval
 *
 *      struct timeval {
 *              unsigned int seconds;
 *              unsigned int useconds;
 *      };
 */
static __be32 *xdr_encode_time(__be32 *p, const struct timespec64 *timep)
{
        *p++ = cpu_to_be32((u32)timep->tv_sec);
        if (timep->tv_nsec != 0)
                *p++ = cpu_to_be32(timep->tv_nsec / NSEC_PER_USEC);
        else
                *p++ = cpu_to_be32(0);
        return p;
}

/*
 * Passing the invalid value useconds=1000000 is a Sun convention for
 * "set to current server time".  It's needed to make permissions checks
 * for the "touch" program across v2 mounts to Solaris and Irix servers
 * work correctly.  See description of sattr in section 6.1 of "NFS
 * Illustrated" by Brent Callaghan, Addison-Wesley, ISBN 0-201-32750-5.
 */
static __be32 *xdr_encode_current_server_time(__be32 *p,
                                              const struct timespec64 *timep)
{
        *p++ = cpu_to_be32(timep->tv_sec);
        *p++ = cpu_to_be32(1000000);
        return p;
}

static __be32 *xdr_decode_time(__be32 *p, struct timespec64 *timep)
{
        timep->tv_sec = be32_to_cpup(p++);
        timep->tv_nsec = be32_to_cpup(p++) * NSEC_PER_USEC;
        return p;
}

/*
 * 2.3.5.  fattr
 *
 *      struct fattr {
 *              ftype           type;
 *              unsigned int    mode;
 *              unsigned int    nlink;
 *              unsigned int    uid;
 *              unsigned int    gid;
 *              unsigned int    size;
 *              unsigned int    blocksize;
 *              unsigned int    rdev;
 *              unsigned int    blocks;
 *              unsigned int    fsid;
 *              unsigned int    fileid;
 *              timeval         atime;
 *              timeval         mtime;
 *              timeval         ctime;
 *      };
 *
 */
static int decode_fattr(struct xdr_stream *xdr, struct nfs_fattr *fattr,
                struct user_namespace *userns)
{
        u32 rdev, type;
        __be32 *p;

        p = xdr_inline_decode(xdr, NFS_fattr_sz << 2);
        if (unlikely(!p))
                return -EIO;

        fattr->valid |= NFS_ATTR_FATTR_V2;

        p = xdr_decode_ftype(p, &type);

        fattr->mode = be32_to_cpup(p++);
        fattr->nlink = be32_to_cpup(p++);
        fattr->uid = make_kuid(userns, be32_to_cpup(p++));
        if (!uid_valid(fattr->uid))
                goto out_uid;
        fattr->gid = make_kgid(userns, be32_to_cpup(p++));
        if (!gid_valid(fattr->gid))
                goto out_gid;
                
        fattr->size = be32_to_cpup(p++);
        fattr->du.nfs2.blocksize = be32_to_cpup(p++);

        rdev = be32_to_cpup(p++);
        fattr->rdev = new_decode_dev(rdev);
        if (type == (u32)NFCHR && rdev == (u32)NFS2_FIFO_DEV) {
                fattr->mode = (fattr->mode & ~S_IFMT) | S_IFIFO;
                fattr->rdev = 0;
        }

        fattr->du.nfs2.blocks = be32_to_cpup(p++);
        fattr->fsid.major = be32_to_cpup(p++);
        fattr->fsid.minor = 0;
        fattr->fileid = be32_to_cpup(p++);

        p = xdr_decode_time(p, &fattr->atime);
        p = xdr_decode_time(p, &fattr->mtime);
        xdr_decode_time(p, &fattr->ctime);
        fattr->change_attr = nfs_timespec_to_change_attr(&fattr->ctime);

        return 0;
out_uid:
        dprintk("NFS: returned invalid uid\n");
        return -EINVAL;
out_gid:
        dprintk("NFS: returned invalid gid\n");
        return -EINVAL;
}

/*
 * 2.3.6.  sattr
 *
 *      struct sattr {
 *              unsigned int    mode;
 *              unsigned int    uid;
 *              unsigned int    gid;
 *              unsigned int    size;
 *              timeval         atime;
 *              timeval         mtime;
 *      };
 */

#define NFS2_SATTR_NOT_SET      (0xffffffff)

static __be32 *xdr_time_not_set(__be32 *p)
{
        *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET);
        *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET);
        return p;
}

static void encode_sattr(struct xdr_stream *xdr, const struct iattr *attr,
                struct user_namespace *userns)
{
        __be32 *p;

        p = xdr_reserve_space(xdr, NFS_sattr_sz << 2);

        if (attr->ia_valid & ATTR_MODE)
                *p++ = cpu_to_be32(attr->ia_mode);
        else
                *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET);
        if (attr->ia_valid & ATTR_UID)
                *p++ = cpu_to_be32(from_kuid_munged(userns, attr->ia_uid));
        else
                *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET);
        if (attr->ia_valid & ATTR_GID)
                *p++ = cpu_to_be32(from_kgid_munged(userns, attr->ia_gid));
        else
                *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET);
        if (attr->ia_valid & ATTR_SIZE)
                *p++ = cpu_to_be32((u32)attr->ia_size);
        else
                *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET);

        if (attr->ia_valid & ATTR_ATIME_SET)
                p = xdr_encode_time(p, &attr->ia_atime);
        else if (attr->ia_valid & ATTR_ATIME)
                p = xdr_encode_current_server_time(p, &attr->ia_atime);
        else
                p = xdr_time_not_set(p);
        if (attr->ia_valid & ATTR_MTIME_SET)
                xdr_encode_time(p, &attr->ia_mtime);
        else if (attr->ia_valid & ATTR_MTIME)
                xdr_encode_current_server_time(p, &attr->ia_mtime);
        else
                xdr_time_not_set(p);
}

/*
 * 2.3.7.  filename
 *
 *      typedef string filename<MAXNAMLEN>;
 */
static void encode_filename(struct xdr_stream *xdr,
                            const char *name, u32 length)
{
        __be32 *p;

        WARN_ON_ONCE(length > NFS2_MAXNAMLEN);
        p = xdr_reserve_space(xdr, 4 + length);
        xdr_encode_opaque(p, name, length);
}

static int decode_filename_inline(struct xdr_stream *xdr,
                                  const char **name, u32 *length)
{
        __be32 *p;
        u32 count;

        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EIO;
        count = be32_to_cpup(p);
        if (count > NFS3_MAXNAMLEN)
                goto out_nametoolong;
        p = xdr_inline_decode(xdr, count);
        if (unlikely(!p))
                return -EIO;
        *name = (const char *)p;
        *length = count;
        return 0;
out_nametoolong:
        dprintk("NFS: returned filename too long: %u\n", count);
        return -ENAMETOOLONG;
}

/*
 * 2.3.8.  path
 *
 *      typedef string path<MAXPATHLEN>;
 */
static void encode_path(struct xdr_stream *xdr, struct page **pages, u32 length)
{
        __be32 *p;

        p = xdr_reserve_space(xdr, 4);
        *p = cpu_to_be32(length);
        xdr_write_pages(xdr, pages, 0, length);
}

static int decode_path(struct xdr_stream *xdr)
{
        u32 length, recvd;
        __be32 *p;

        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EIO;
        length = be32_to_cpup(p);
        if (unlikely(length >= xdr->buf->page_len || length > NFS_MAXPATHLEN))
                goto out_size;
        recvd = xdr_read_pages(xdr, length);
        if (unlikely(length > recvd))
                goto out_cheating;
        xdr_terminate_string(xdr->buf, length);
        return 0;
out_size:
        dprintk("NFS: returned pathname too long: %u\n", length);
        return -ENAMETOOLONG;
out_cheating:
        dprintk("NFS: server cheating in pathname result: "
                "length %u > received %u\n", length, recvd);
        return -EIO;
}

/*
 * 2.3.9.  attrstat
 *
 *      union attrstat switch (stat status) {
 *      case NFS_OK:
 *              fattr attributes;
 *      default:
 *              void;
 *      };
 */
static int decode_attrstat(struct xdr_stream *xdr, struct nfs_fattr *result,
                           __u32 *op_status,
                           struct user_namespace *userns)
{
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        if (op_status)
                *op_status = status;
        if (status != NFS_OK)
                goto out_default;
        error = decode_fattr(xdr, result, userns);
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}

/*
 * 2.3.10.  diropargs
 *
 *      struct diropargs {
 *              fhandle  dir;
 *              filename name;
 *      };
 */
static void encode_diropargs(struct xdr_stream *xdr, const struct nfs_fh *fh,
                             const char *name, u32 length)
{
        encode_fhandle(xdr, fh);
        encode_filename(xdr, name, length);
}

/*
 * 2.3.11.  diropres
 *
 *      union diropres switch (stat status) {
 *      case NFS_OK:
 *              struct {
 *                      fhandle file;
 *                      fattr   attributes;
 *              } diropok;
 *      default:
 *              void;
 *      };
 */
static int decode_diropok(struct xdr_stream *xdr, struct nfs_diropok *result,
                struct user_namespace *userns)
{
        int error;

        error = decode_fhandle(xdr, result->fh);
        if (unlikely(error))
                goto out;
        error = decode_fattr(xdr, result->fattr, userns);
out:
        return error;
}

static int decode_diropres(struct xdr_stream *xdr, struct nfs_diropok *result,
                struct user_namespace *userns)
{
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        if (status != NFS_OK)
                goto out_default;
        error = decode_diropok(xdr, result, userns);
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}


/*
 * NFSv2 XDR encode functions
 *
 * NFSv2 argument types are defined in section 2.2 of RFC 1094:
 * "NFS: Network File System Protocol Specification".
 */

static void nfs2_xdr_enc_fhandle(struct rpc_rqst *req,
                                 struct xdr_stream *xdr,
                                 const void *data)
{
        const struct nfs_fh *fh = data;

        encode_fhandle(xdr, fh);
}

/*
 * 2.2.3.  sattrargs
 *
 *      struct sattrargs {
 *              fhandle file;
 *              sattr attributes;
 *      };
 */
static void nfs2_xdr_enc_sattrargs(struct rpc_rqst *req,
                                   struct xdr_stream *xdr,
                                   const void *data)
{
        const struct nfs_sattrargs *args = data;

        encode_fhandle(xdr, args->fh);
        encode_sattr(xdr, args->sattr, rpc_rqst_userns(req));
}

static void nfs2_xdr_enc_diropargs(struct rpc_rqst *req,
                                   struct xdr_stream *xdr,
                                   const void *data)
{
        const struct nfs_diropargs *args = data;

        encode_diropargs(xdr, args->fh, args->name, args->len);
}

static void nfs2_xdr_enc_readlinkargs(struct rpc_rqst *req,
                                      struct xdr_stream *xdr,
                                      const void *data)
{
        const struct nfs_readlinkargs *args = data;

        encode_fhandle(xdr, args->fh);
        rpc_prepare_reply_pages(req, args->pages, args->pgbase, args->pglen,
                                NFS_readlinkres_sz - NFS_pagepad_sz);
}

/*
 * 2.2.7.  readargs
 *
 *      struct readargs {
 *              fhandle file;
 *              unsigned offset;
 *              unsigned count;
 *              unsigned totalcount;
 *      };
 */
static void encode_readargs(struct xdr_stream *xdr,
                            const struct nfs_pgio_args *args)
{
        u32 offset = args->offset;
        u32 count = args->count;
        __be32 *p;

        encode_fhandle(xdr, args->fh);

        p = xdr_reserve_space(xdr, 4 + 4 + 4);
        *p++ = cpu_to_be32(offset);
        *p++ = cpu_to_be32(count);
        *p = cpu_to_be32(count);
}

static void nfs2_xdr_enc_readargs(struct rpc_rqst *req,
                                  struct xdr_stream *xdr,
                                  const void *data)
{
        const struct nfs_pgio_args *args = data;

        encode_readargs(xdr, args);
        rpc_prepare_reply_pages(req, args->pages, args->pgbase, args->count,
                                NFS_readres_sz - NFS_pagepad_sz);
        req->rq_rcv_buf.flags |= XDRBUF_READ;
}

/*
 * 2.2.9.  writeargs
 *
 *      struct writeargs {
 *              fhandle file;
 *              unsigned beginoffset;
 *              unsigned offset;
 *              unsigned totalcount;
 *              nfsdata data;
 *      };
 */
static void encode_writeargs(struct xdr_stream *xdr,
                             const struct nfs_pgio_args *args)
{
        u32 offset = args->offset;
        u32 count = args->count;
        __be32 *p;

        encode_fhandle(xdr, args->fh);

        p = xdr_reserve_space(xdr, 4 + 4 + 4 + 4);
        *p++ = cpu_to_be32(offset);
        *p++ = cpu_to_be32(offset);
        *p++ = cpu_to_be32(count);

        /* nfsdata */
        *p = cpu_to_be32(count);
        xdr_write_pages(xdr, args->pages, args->pgbase, count);
}

static void nfs2_xdr_enc_writeargs(struct rpc_rqst *req,
                                   struct xdr_stream *xdr,
                                   const void *data)
{
        const struct nfs_pgio_args *args = data;

        encode_writeargs(xdr, args);
        xdr->buf->flags |= XDRBUF_WRITE;
}

/*
 * 2.2.10.  createargs
 *
 *      struct createargs {
 *              diropargs where;
 *              sattr attributes;
 *      };
 */
static void nfs2_xdr_enc_createargs(struct rpc_rqst *req,
                                    struct xdr_stream *xdr,
                                    const void *data)
{
        const struct nfs_createargs *args = data;

        encode_diropargs(xdr, args->fh, args->name, args->len);
        encode_sattr(xdr, args->sattr, rpc_rqst_userns(req));
}

static void nfs2_xdr_enc_removeargs(struct rpc_rqst *req,
                                    struct xdr_stream *xdr,
                                    const void *data)
{
        const struct nfs_removeargs *args = data;

        encode_diropargs(xdr, args->fh, args->name.name, args->name.len);
}

/*
 * 2.2.12.  renameargs
 *
 *      struct renameargs {
 *              diropargs from;
 *              diropargs to;
 *      };
 */
static void nfs2_xdr_enc_renameargs(struct rpc_rqst *req,
                                    struct xdr_stream *xdr,
                                    const void *data)
{
        const struct nfs_renameargs *args = data;
        const struct qstr *old = args->old_name;
        const struct qstr *new = args->new_name;

        encode_diropargs(xdr, args->old_dir, old->name, old->len);
        encode_diropargs(xdr, args->new_dir, new->name, new->len);
}

/*
 * 2.2.13.  linkargs
 *
 *      struct linkargs {
 *              fhandle from;
 *              diropargs to;
 *      };
 */
static void nfs2_xdr_enc_linkargs(struct rpc_rqst *req,
                                  struct xdr_stream *xdr,
                                  const void *data)
{
        const struct nfs_linkargs *args = data;

        encode_fhandle(xdr, args->fromfh);
        encode_diropargs(xdr, args->tofh, args->toname, args->tolen);
}

/*
 * 2.2.14.  symlinkargs
 *
 *      struct symlinkargs {
 *              diropargs from;
 *              path to;
 *              sattr attributes;
 *      };
 */
static void nfs2_xdr_enc_symlinkargs(struct rpc_rqst *req,
                                     struct xdr_stream *xdr,
                                     const void *data)
{
        const struct nfs_symlinkargs *args = data;

        encode_diropargs(xdr, args->fromfh, args->fromname, args->fromlen);
        encode_path(xdr, args->pages, args->pathlen);
        encode_sattr(xdr, args->sattr, rpc_rqst_userns(req));
}

/*
 * 2.2.17.  readdirargs
 *
 *      struct readdirargs {
 *              fhandle dir;
 *              nfscookie cookie;
 *              unsigned count;
 *      };
 */
static void encode_readdirargs(struct xdr_stream *xdr,
                               const struct nfs_readdirargs *args)
{
        __be32 *p;

        encode_fhandle(xdr, args->fh);

        p = xdr_reserve_space(xdr, 4 + 4);
        *p++ = cpu_to_be32(args->cookie);
        *p = cpu_to_be32(args->count);
}

static void nfs2_xdr_enc_readdirargs(struct rpc_rqst *req,
                                     struct xdr_stream *xdr,
                                     const void *data)
{
        const struct nfs_readdirargs *args = data;

        encode_readdirargs(xdr, args);
        rpc_prepare_reply_pages(req, args->pages, 0, args->count,
                                NFS_readdirres_sz - NFS_pagepad_sz);
}

/*
 * NFSv2 XDR decode functions
 *
 * NFSv2 result types are defined in section 2.2 of RFC 1094:
 * "NFS: Network File System Protocol Specification".
 */

static int nfs2_xdr_dec_stat(struct rpc_rqst *req, struct xdr_stream *xdr,
                             void *__unused)
{
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        if (status != NFS_OK)
                goto out_default;
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}

static int nfs2_xdr_dec_attrstat(struct rpc_rqst *req, struct xdr_stream *xdr,
                                 void *result)
{
        return decode_attrstat(xdr, result, NULL, rpc_rqst_userns(req));
}

static int nfs2_xdr_dec_diropres(struct rpc_rqst *req, struct xdr_stream *xdr,
                                 void *result)
{
        return decode_diropres(xdr, result, rpc_rqst_userns(req));
}

/*
 * 2.2.6.  readlinkres
 *
 *      union readlinkres switch (stat status) {
 *      case NFS_OK:
 *              path data;
 *      default:
 *              void;
 *      };
 */
static int nfs2_xdr_dec_readlinkres(struct rpc_rqst *req,
                                    struct xdr_stream *xdr, void *__unused)
{
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        if (status != NFS_OK)
                goto out_default;
        error = decode_path(xdr);
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}

/*
 * 2.2.7.  readres
 *
 *      union readres switch (stat status) {
 *      case NFS_OK:
 *              fattr attributes;
 *              nfsdata data;
 *      default:
 *              void;
 *      };
 */
static int nfs2_xdr_dec_readres(struct rpc_rqst *req, struct xdr_stream *xdr,
                                void *data)
{
        struct nfs_pgio_res *result = data;
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        result->op_status = status;
        if (status != NFS_OK)
                goto out_default;
        error = decode_fattr(xdr, result->fattr, rpc_rqst_userns(req));
        if (unlikely(error))
                goto out;
        error = decode_nfsdata(xdr, result);
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}

static int nfs2_xdr_dec_writeres(struct rpc_rqst *req, struct xdr_stream *xdr,
                                 void *data)
{
        struct nfs_pgio_res *result = data;

        /* All NFSv2 writes are "file sync" writes */
        result->verf->committed = NFS_FILE_SYNC;
        return decode_attrstat(xdr, result->fattr, &result->op_status,
                        rpc_rqst_userns(req));
}

/**
 * nfs2_decode_dirent - Decode a single NFSv2 directory entry stored in
 *                      the local page cache.
 * @xdr: XDR stream where entry resides
 * @entry: buffer to fill in with entry data
 * @plus: boolean indicating whether this should be a readdirplus entry
 *
 * Returns zero if successful, otherwise a negative errno value is
 * returned.
 *
 * This function is not invoked during READDIR reply decoding, but
 * rather whenever an application invokes the getdents(2) system call
 * on a directory already in our cache.
 *
 * 2.2.17.  entry
 *
 *      struct entry {
 *              unsigned        fileid;
 *              filename        name;
 *              nfscookie       cookie;
 *              entry           *nextentry;
 *      };
 */
int nfs2_decode_dirent(struct xdr_stream *xdr, struct nfs_entry *entry,
                       bool plus)
{
        __be32 *p;
        int error;

        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EAGAIN;
        if (*p++ == xdr_zero) {
                p = xdr_inline_decode(xdr, 4);
                if (unlikely(!p))
                        return -EAGAIN;
                if (*p++ == xdr_zero)
                        return -EAGAIN;
                entry->eof = 1;
                return -EBADCOOKIE;
        }

        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EAGAIN;
        entry->ino = be32_to_cpup(p);

        error = decode_filename_inline(xdr, &entry->name, &entry->len);
        if (unlikely(error))
                return error == -ENAMETOOLONG ? -ENAMETOOLONG : -EAGAIN;

        /*
         * The type (size and byte order) of nfscookie isn't defined in
         * RFC 1094.  This implementation assumes that it's an XDR uint32.
         */
        p = xdr_inline_decode(xdr, 4);
        if (unlikely(!p))
                return -EAGAIN;
        entry->cookie = be32_to_cpup(p);

        entry->d_type = DT_UNKNOWN;

        return 0;
}

/*
 * 2.2.17.  readdirres
 *
 *      union readdirres switch (stat status) {
 *      case NFS_OK:
 *              struct {
 *                      entry *entries;
 *                      bool eof;
 *              } readdirok;
 *      default:
 *              void;
 *      };
 *
 * Read the directory contents into the page cache, but don't
 * touch them.  The actual decoding is done by nfs2_decode_dirent()
 * during subsequent nfs_readdir() calls.
 */
static int decode_readdirok(struct xdr_stream *xdr)
{
        return xdr_read_pages(xdr, xdr->buf->page_len);
}

static int nfs2_xdr_dec_readdirres(struct rpc_rqst *req,
                                   struct xdr_stream *xdr, void *__unused)
{
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        if (status != NFS_OK)
                goto out_default;
        error = decode_readdirok(xdr);
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}

/*
 * 2.2.18.  statfsres
 *
 *      union statfsres (stat status) {
 *      case NFS_OK:
 *              struct {
 *                      unsigned tsize;
 *                      unsigned bsize;
 *                      unsigned blocks;
 *                      unsigned bfree;
 *                      unsigned bavail;
 *              } info;
 *      default:
 *              void;
 *      };
 */
static int decode_info(struct xdr_stream *xdr, struct nfs2_fsstat *result)
{
        __be32 *p;

        p = xdr_inline_decode(xdr, NFS_info_sz << 2);
        if (unlikely(!p))
                return -EIO;
        result->tsize  = be32_to_cpup(p++);
        result->bsize  = be32_to_cpup(p++);
        result->blocks = be32_to_cpup(p++);
        result->bfree  = be32_to_cpup(p++);
        result->bavail = be32_to_cpup(p);
        return 0;
}

static int nfs2_xdr_dec_statfsres(struct rpc_rqst *req, struct xdr_stream *xdr,
                                  void *result)
{
        enum nfs_stat status;
        int error;

        error = decode_stat(xdr, &status);
        if (unlikely(error))
                goto out;
        if (status != NFS_OK)
                goto out_default;
        error = decode_info(xdr, result);
out:
        return error;
out_default:
        return nfs_stat_to_errno(status);
}

#define PROC(proc, argtype, restype, timer)                             \
[NFSPROC_##proc] = {                                                    \
        .p_proc     =  NFSPROC_##proc,                                  \
        .p_encode   =  nfs2_xdr_enc_##argtype,                          \
        .p_decode   =  nfs2_xdr_dec_##restype,                          \
        .p_arglen   =  NFS_##argtype##_sz,                              \
        .p_replen   =  NFS_##restype##_sz,                              \
        .p_timer    =  timer,                                           \
        .p_statidx  =  NFSPROC_##proc,                                  \
        .p_name     =  #proc,                                           \
        }
const struct rpc_procinfo nfs_procedures[] = {
        PROC(GETATTR,   fhandle,        attrstat,       1),
        PROC(SETATTR,   sattrargs,      attrstat,       0),
        PROC(LOOKUP,    diropargs,      diropres,       2),
        PROC(READLINK,  readlinkargs,   readlinkres,    3),
        PROC(READ,      readargs,       readres,        3),
        PROC(WRITE,     writeargs,      writeres,       4),
        PROC(CREATE,    createargs,     diropres,       0),
        PROC(REMOVE,    removeargs,     stat,           0),
        PROC(RENAME,    renameargs,     stat,           0),
        PROC(LINK,      linkargs,       stat,           0),
        PROC(SYMLINK,   symlinkargs,    stat,           0),
        PROC(MKDIR,     createargs,     diropres,       0),
        PROC(RMDIR,     diropargs,      stat,           0),
        PROC(READDIR,   readdirargs,    readdirres,     3),
        PROC(STATFS,    fhandle,        statfsres,      0),
};

static unsigned int nfs_version2_counts[ARRAY_SIZE(nfs_procedures)];
const struct rpc_version nfs_version2 = {
        .number                 = 2,
        .nrprocs                = ARRAY_SIZE(nfs_procedures),
        .procs                  = nfs_procedures,
        .counts                 = nfs_version2_counts,
};