f67b55b658
Commit64cfca85ba
asserts the only valid return values for nfs2/3_decode_dirent should not include -ENAMETOOLONG, but for a server that sends a filename3 which exceeds MAXNAMELEN in a READDIR response the client's behavior will be to endlessly retry the operation. We could map -ENAMETOOLONG into -EBADCOOKIE, but that would produce truncated listings without any error. The client should return an error for this case to clearly assert that the server implementation must be corrected. Fixes:64cfca85ba
("NFS: Return valid errors from nfs2/3_decode_dirent()") Signed-off-by: Benjamin Coddington <bcodding@redhat.com> Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
1157 lines
26 KiB
C
1157 lines
26 KiB
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 "nfstrace.h"
|
|
#include "internal.h"
|
|
|
|
#define NFSDBG_FACILITY NFSDBG_XDR
|
|
|
|
/* Mapping from NFS error code to "errno" error code. */
|
|
#define errno_NFSERR_IO EIO
|
|
|
|
/*
|
|
* 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)
|
|
|
|
static int nfs_stat_to_errno(enum nfs_stat);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
|
|
/*
|
|
* We need to translate between nfs status return values and
|
|
* the local errno values which may not be the same.
|
|
*/
|
|
static const struct {
|
|
int stat;
|
|
int errno;
|
|
} nfs_errtbl[] = {
|
|
{ NFS_OK, 0 },
|
|
{ NFSERR_PERM, -EPERM },
|
|
{ NFSERR_NOENT, -ENOENT },
|
|
{ NFSERR_IO, -errno_NFSERR_IO},
|
|
{ NFSERR_NXIO, -ENXIO },
|
|
/* { NFSERR_EAGAIN, -EAGAIN }, */
|
|
{ NFSERR_ACCES, -EACCES },
|
|
{ NFSERR_EXIST, -EEXIST },
|
|
{ NFSERR_XDEV, -EXDEV },
|
|
{ NFSERR_NODEV, -ENODEV },
|
|
{ NFSERR_NOTDIR, -ENOTDIR },
|
|
{ NFSERR_ISDIR, -EISDIR },
|
|
{ NFSERR_INVAL, -EINVAL },
|
|
{ NFSERR_FBIG, -EFBIG },
|
|
{ NFSERR_NOSPC, -ENOSPC },
|
|
{ NFSERR_ROFS, -EROFS },
|
|
{ NFSERR_MLINK, -EMLINK },
|
|
{ NFSERR_NAMETOOLONG, -ENAMETOOLONG },
|
|
{ NFSERR_NOTEMPTY, -ENOTEMPTY },
|
|
{ NFSERR_DQUOT, -EDQUOT },
|
|
{ NFSERR_STALE, -ESTALE },
|
|
{ NFSERR_REMOTE, -EREMOTE },
|
|
#ifdef EWFLUSH
|
|
{ NFSERR_WFLUSH, -EWFLUSH },
|
|
#endif
|
|
{ NFSERR_BADHANDLE, -EBADHANDLE },
|
|
{ NFSERR_NOT_SYNC, -ENOTSYNC },
|
|
{ NFSERR_BAD_COOKIE, -EBADCOOKIE },
|
|
{ NFSERR_NOTSUPP, -ENOTSUPP },
|
|
{ NFSERR_TOOSMALL, -ETOOSMALL },
|
|
{ NFSERR_SERVERFAULT, -EREMOTEIO },
|
|
{ NFSERR_BADTYPE, -EBADTYPE },
|
|
{ NFSERR_JUKEBOX, -EJUKEBOX },
|
|
{ -1, -EIO }
|
|
};
|
|
|
|
/**
|
|
* nfs_stat_to_errno - convert an NFS status code to a local errno
|
|
* @status: NFS status code to convert
|
|
*
|
|
* Returns a local errno value, or -EIO if the NFS status code is
|
|
* not recognized. This function is used jointly by NFSv2 and NFSv3.
|
|
*/
|
|
static int nfs_stat_to_errno(enum nfs_stat status)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; nfs_errtbl[i].stat != -1; i++) {
|
|
if (nfs_errtbl[i].stat == (int)status)
|
|
return nfs_errtbl[i].errno;
|
|
}
|
|
dprintk("NFS: Unrecognized nfs status value: %u\n", status);
|
|
return nfs_errtbl[i].errno;
|
|
}
|
|
|
|
#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,
|
|
};
|