| /* | |
| * 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 "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_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) | |
| #define NFS_readres_sz (1+NFS_fattr_sz+1) | |
| #define NFS_writeres_sz (NFS_attrstat_sz) | |
| #define NFS_stat_sz (1) | |
| #define NFS_readdirres_sz (1) | |
| #define NFS_statfsres_sz (1+NFS_info_sz) | |
| static int nfs_stat_to_errno(enum nfs_stat); | |
| /* | |
| * While encoding arguments, set up the reply buffer in advance to | |
| * receive reply data directly into the page cache. | |
| */ | |
| static void prepare_reply_buffer(struct rpc_rqst *req, struct page **pages, | |
| unsigned int base, unsigned int len, | |
| unsigned int bufsize) | |
| { | |
| struct rpc_auth *auth = req->rq_cred->cr_auth; | |
| unsigned int replen; | |
| replen = RPC_REPHDRSIZE + auth->au_rslack + bufsize; | |
| xdr_inline_pages(&req->rq_rcv_buf, replen << 2, pages, base, len); | |
| } | |
| /* | |
| * 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); | |
| } | |
| /* | |
| * 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. | |
| */ | |
| /* | |
| * 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 == NULL)) | |
| goto out_overflow; | |
| 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; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EIO; | |
| } | |
| /* | |
| * 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 == NULL)) | |
| goto out_overflow; | |
| *status = be32_to_cpup(p); | |
| return 0; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EIO; | |
| } | |
| /* | |
| * 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 == NULL)) | |
| goto out_overflow; | |
| fh->size = NFS2_FHSIZE; | |
| memcpy(fh->data, p, NFS2_FHSIZE); | |
| return 0; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EIO; | |
| } | |
| /* | |
| * 2.3.4. timeval | |
| * | |
| * struct timeval { | |
| * unsigned int seconds; | |
| * unsigned int useconds; | |
| * }; | |
| */ | |
| static __be32 *xdr_encode_time(__be32 *p, const struct timespec *timep) | |
| { | |
| *p++ = cpu_to_be32(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 timespec *timep) | |
| { | |
| *p++ = cpu_to_be32(timep->tv_sec); | |
| *p++ = cpu_to_be32(1000000); | |
| return p; | |
| } | |
| static __be32 *xdr_decode_time(__be32 *p, struct timespec *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) | |
| { | |
| u32 rdev, type; | |
| __be32 *p; | |
| p = xdr_inline_decode(xdr, NFS_fattr_sz << 2); | |
| if (unlikely(p == NULL)) | |
| goto out_overflow; | |
| 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(&init_user_ns, be32_to_cpup(p++)); | |
| if (!uid_valid(fattr->uid)) | |
| goto out_uid; | |
| fattr->gid = make_kgid(&init_user_ns, 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; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EIO; | |
| } | |
| /* | |
| * 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) | |
| { | |
| __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(&init_user_ns, attr->ia_uid)); | |
| else | |
| *p++ = cpu_to_be32(NFS2_SATTR_NOT_SET); | |
| if (attr->ia_valid & ATTR_GID) | |
| *p++ = cpu_to_be32(from_kgid(&init_user_ns, 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 == NULL)) | |
| goto out_overflow; | |
| count = be32_to_cpup(p); | |
| if (count > NFS3_MAXNAMLEN) | |
| goto out_nametoolong; | |
| p = xdr_inline_decode(xdr, count); | |
| if (unlikely(p == NULL)) | |
| goto out_overflow; | |
| *name = (const char *)p; | |
| *length = count; | |
| return 0; | |
| out_nametoolong: | |
| dprintk("NFS: returned filename too long: %u\n", count); | |
| return -ENAMETOOLONG; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EIO; | |
| } | |
| /* | |
| * 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 == NULL)) | |
| goto out_overflow; | |
| 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; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| 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) | |
| { | |
| 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); | |
| 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) | |
| { | |
| int error; | |
| error = decode_fhandle(xdr, result->fh); | |
| if (unlikely(error)) | |
| goto out; | |
| error = decode_fattr(xdr, result->fattr); | |
| out: | |
| return error; | |
| } | |
| static int decode_diropres(struct xdr_stream *xdr, struct nfs_diropok *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_diropok(xdr, result); | |
| 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 struct nfs_fh *fh) | |
| { | |
| 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 struct nfs_sattrargs *args) | |
| { | |
| encode_fhandle(xdr, args->fh); | |
| encode_sattr(xdr, args->sattr); | |
| } | |
| static void nfs2_xdr_enc_diropargs(struct rpc_rqst *req, | |
| struct xdr_stream *xdr, | |
| const struct nfs_diropargs *args) | |
| { | |
| encode_diropargs(xdr, args->fh, args->name, args->len); | |
| } | |
| static void nfs2_xdr_enc_readlinkargs(struct rpc_rqst *req, | |
| struct xdr_stream *xdr, | |
| const struct nfs_readlinkargs *args) | |
| { | |
| encode_fhandle(xdr, args->fh); | |
| prepare_reply_buffer(req, args->pages, args->pgbase, | |
| args->pglen, NFS_readlinkres_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 struct nfs_pgio_args *args) | |
| { | |
| encode_readargs(xdr, args); | |
| prepare_reply_buffer(req, args->pages, args->pgbase, | |
| args->count, NFS_readres_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 struct nfs_pgio_args *args) | |
| { | |
| 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 struct nfs_createargs *args) | |
| { | |
| encode_diropargs(xdr, args->fh, args->name, args->len); | |
| encode_sattr(xdr, args->sattr); | |
| } | |
| static void nfs2_xdr_enc_removeargs(struct rpc_rqst *req, | |
| struct xdr_stream *xdr, | |
| const struct nfs_removeargs *args) | |
| { | |
| 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 struct nfs_renameargs *args) | |
| { | |
| 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 struct nfs_linkargs *args) | |
| { | |
| 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 struct nfs_symlinkargs *args) | |
| { | |
| encode_diropargs(xdr, args->fromfh, args->fromname, args->fromlen); | |
| encode_path(xdr, args->pages, args->pathlen); | |
| encode_sattr(xdr, args->sattr); | |
| } | |
| /* | |
| * 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 struct nfs_readdirargs *args) | |
| { | |
| encode_readdirargs(xdr, args); | |
| prepare_reply_buffer(req, args->pages, 0, | |
| args->count, NFS_readdirres_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, | |
| struct nfs_fattr *result) | |
| { | |
| return decode_attrstat(xdr, result, NULL); | |
| } | |
| static int nfs2_xdr_dec_diropres(struct rpc_rqst *req, struct xdr_stream *xdr, | |
| struct nfs_diropok *result) | |
| { | |
| return decode_diropres(xdr, result); | |
| } | |
| /* | |
| * 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, | |
| struct nfs_pgio_res *result) | |
| { | |
| 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); | |
| 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, | |
| struct nfs_pgio_res *result) | |
| { | |
| /* All NFSv2 writes are "file sync" writes */ | |
| result->verf->committed = NFS_FILE_SYNC; | |
| return decode_attrstat(xdr, result->fattr, &result->op_status); | |
| } | |
| /** | |
| * 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, | |
| int plus) | |
| { | |
| __be32 *p; | |
| int error; | |
| p = xdr_inline_decode(xdr, 4); | |
| if (unlikely(p == NULL)) | |
| goto out_overflow; | |
| if (*p++ == xdr_zero) { | |
| p = xdr_inline_decode(xdr, 4); | |
| if (unlikely(p == NULL)) | |
| goto out_overflow; | |
| if (*p++ == xdr_zero) | |
| return -EAGAIN; | |
| entry->eof = 1; | |
| return -EBADCOOKIE; | |
| } | |
| p = xdr_inline_decode(xdr, 4); | |
| if (unlikely(p == NULL)) | |
| goto out_overflow; | |
| entry->ino = be32_to_cpup(p); | |
| error = decode_filename_inline(xdr, &entry->name, &entry->len); | |
| if (unlikely(error)) | |
| return error; | |
| /* | |
| * The type (size and byte order) of nfscookie isn't defined in | |
| * RFC 1094. This implementation assumes that it's an XDR uint32. | |
| */ | |
| entry->prev_cookie = entry->cookie; | |
| p = xdr_inline_decode(xdr, 4); | |
| if (unlikely(p == NULL)) | |
| goto out_overflow; | |
| entry->cookie = be32_to_cpup(p); | |
| entry->d_type = DT_UNKNOWN; | |
| return 0; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EAGAIN; | |
| } | |
| /* | |
| * 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 == NULL)) | |
| goto out_overflow; | |
| 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; | |
| out_overflow: | |
| print_overflow_msg(__func__, xdr); | |
| return -EIO; | |
| } | |
| static int nfs2_xdr_dec_statfsres(struct rpc_rqst *req, struct xdr_stream *xdr, | |
| struct nfs2_fsstat *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 = (kxdreproc_t)nfs2_xdr_enc_##argtype, \ | |
| .p_decode = (kxdrdproc_t)nfs2_xdr_dec_##restype, \ | |
| .p_arglen = NFS_##argtype##_sz, \ | |
| .p_replen = NFS_##restype##_sz, \ | |
| .p_timer = timer, \ | |
| .p_statidx = NFSPROC_##proc, \ | |
| .p_name = #proc, \ | |
| } | |
| 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), | |
| }; | |
| const struct rpc_version nfs_version2 = { | |
| .number = 2, | |
| .nrprocs = ARRAY_SIZE(nfs_procedures), | |
| .procs = nfs_procedures | |
| }; |