diff options
56 files changed, 1670 insertions, 1094 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb07770..aa45acd54ed6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14186,7 +14186,8 @@ L: linux-nfs@vger.kernel.org S: Supported P: Documentation/filesystems/nfs/nfsd-maintainer-entry-profile.rst B: https://bugzilla.kernel.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git nfsd-testing +T: git git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux.git nfsd-next F: Documentation/filesystems/nfs/ F: fs/lockd/ F: fs/nfs_common/ diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c index ee90e743064a..f0e1a58c9106 100644 --- a/fs/lockd/svc.c +++ b/fs/lockd/svc.c @@ -36,7 +36,6 @@ #include <net/ip.h> #include <net/addrconf.h> #include <net/ipv6.h> -#include <linux/nfs.h> #include "lockd.h" #include "netns.h" diff --git a/fs/lockd/trace.h b/fs/lockd/trace.h index a11d04e8c835..1f79955ea0f5 100644 --- a/fs/lockd/trace.h +++ b/fs/lockd/trace.h @@ -7,7 +7,6 @@ #include <linux/tracepoint.h> #include <linux/crc32.h> -#include <linux/nfs.h> #include "lockd.h" diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index a1126cca98c6..56b9796aa39d 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -10,7 +10,7 @@ #include <linux/fs.h> #include <linux/filelock.h> -#include <linux/nfs.h> +#include <linux/nfs_fh.h> #include <linux/sunrpc/xdr.h> #define SM_MAXSTRLEN 1024 diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3ef..d95249dd527c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -5211,6 +5211,8 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode, error = vfs_create(mnt_idmap(path->mnt), path->dentry, mode, NULL); if (!error) error = vfs_open(path, file); + if (!error) + file->f_mode |= FMODE_CREATED; } if (unlikely(error)) return ERR_PTR(error); diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c index 6401f6363f75..9a434f5dda8d 100644 --- a/fs/nfs/nfs4file.c +++ b/fs/nfs/nfs4file.c @@ -402,6 +402,7 @@ static void __nfs42_ssc_close(struct file *filep) } static const struct nfs4_ssc_client_ops nfs4_ssc_clnt_ops_tbl = { + .owner = THIS_MODULE, .sco_open = __nfs42_ssc_open, .sco_close = __nfs42_ssc_close, }; diff --git a/fs/nfs/super.c b/fs/nfs/super.c index cb19f1540d98..23292680adbd 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -58,7 +58,6 @@ #include <linux/rcupdate.h> #include <linux/uaccess.h> -#include <linux/nfs_ssc.h> #include <uapi/linux/tls.h> @@ -92,12 +91,6 @@ const struct super_operations nfs_sops = { }; EXPORT_SYMBOL_GPL(nfs_sops); -#ifdef CONFIG_NFS_V4_2 -static const struct nfs_ssc_client_ops nfs_ssc_clnt_ops_tbl = { - .sco_sb_deactive = nfs_sb_deactive, -}; -#endif - #if IS_ENABLED(CONFIG_NFS_V4) static int __init register_nfs4_fs(void) { @@ -119,18 +112,6 @@ static void unregister_nfs4_fs(void) } #endif -#ifdef CONFIG_NFS_V4_2 -static void nfs_ssc_register_ops(void) -{ - nfs_ssc_register(&nfs_ssc_clnt_ops_tbl); -} - -static void nfs_ssc_unregister_ops(void) -{ - nfs_ssc_unregister(&nfs_ssc_clnt_ops_tbl); -} -#endif /* CONFIG_NFS_V4_2 */ - static struct shrinker *acl_shrinker; /* @@ -163,9 +144,6 @@ int __init register_nfs_fs(void) shrinker_register(acl_shrinker); -#ifdef CONFIG_NFS_V4_2 - nfs_ssc_register_ops(); -#endif return 0; error_3: nfs_unregister_sysctl(); @@ -185,9 +163,6 @@ void __exit unregister_nfs_fs(void) shrinker_free(acl_shrinker); nfs_unregister_sysctl(); unregister_nfs4_fs(); -#ifdef CONFIG_NFS_V4_2 - nfs_ssc_unregister_ops(); -#endif unregister_filesystem(&nfs_fs_type); } diff --git a/fs/nfs_common/nfs_ssc.c b/fs/nfs_common/nfs_ssc.c index 832246b22c51..e521e3c836fe 100644 --- a/fs/nfs_common/nfs_ssc.c +++ b/fs/nfs_common/nfs_ssc.c @@ -10,82 +10,112 @@ #include <linux/module.h> #include <linux/fs.h> #include <linux/nfs_ssc.h> -#include "../nfs/nfs4_fs.h" +#include <linux/nfsd_ssc.h> +struct nfs_ssc_client_ops_tbl { + const struct nfs4_ssc_client_ops __rcu *ssc_nfs4_ops; +}; -struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl; -EXPORT_SYMBOL_GPL(nfs_ssc_client_tbl); +static struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl __read_mostly; -#ifdef CONFIG_NFS_V4_2 /** - * nfs42_ssc_register - install the NFS_V4 client ops in the nfs_ssc_client_tbl - * @ops: NFS_V4 ops to be installed + * nfsd42_ssc_open - Open a file to be used for server-to-server copy + * @ss_mnt: active mount point on which the source file resides + * @src_fh: file handle of the source file to be copied + * @stateid: stateid to use for COPY operation * - * Return values: - * None + * Caller must close the returned file using nfsd42_ssc_close(). + * + * Return: an open file, or an ERR_PTR on error */ -void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops) +struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh, + nfs4_stateid *stateid) { - nfs_ssc_client_tbl.ssc_nfs4_ops = ops; + /* + * Built under CONFIG_NFS_V4_2_SSC_HELPER, which the NFS client + * enables on its own. The dispatch below is live only when the + * server also sets CONFIG_NFSD_V4_2_INTER_SSC; without it the + * source file cannot be opened, so callers get -EIO. + */ +#if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC) + const struct nfs4_ssc_client_ops *ops; + struct file *res; + + /* + * sco_open() sleeps and must not run inside an RCU read-side + * section. Pin the provider module so the open runs with the + * module held; try_module_get() fails once unregister begins, + * and the copy then gets -EIO. + */ + rcu_read_lock(); + ops = rcu_dereference(nfs_ssc_client_tbl.ssc_nfs4_ops); + if (ops && try_module_get(ops->owner)) { + rcu_read_unlock(); + res = ops->sco_open(ss_mnt, src_fh, stateid); + module_put(ops->owner); + return res; + } + rcu_read_unlock(); +#endif + + return ERR_PTR(-EIO); } -EXPORT_SYMBOL_GPL(nfs42_ssc_register); +EXPORT_SYMBOL_GPL(nfsd42_ssc_open); /** - * nfs42_ssc_unregister - uninstall the NFS_V4 client ops from - * the nfs_ssc_client_tbl - * @ops: ops to be uninstalled + * nfsd42_ssc_close - Close a file opened with nfsd42_ssc_open() + * @filp: struct file to be closed * - * Return values: - * None + * The real cleanup happens unconditionally in nfsd4_cleanup_inter_ssc(). + * The client ops table is read under RCU; nfs42_ssc_unregister() calls + * synchronize_rcu() so unregistration cannot complete while a close is + * in flight. */ -void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops) +void nfsd42_ssc_close(struct file *filp) { - if (nfs_ssc_client_tbl.ssc_nfs4_ops != ops) - return; + /* Live only under CONFIG_NFSD_V4_2_INTER_SSC; see nfsd42_ssc_open(). */ +#if IS_ENABLED(CONFIG_NFSD_V4_2_INTER_SSC) + const struct nfs4_ssc_client_ops *ops; - nfs_ssc_client_tbl.ssc_nfs4_ops = NULL; + rcu_read_lock(); + ops = rcu_dereference(nfs_ssc_client_tbl.ssc_nfs4_ops); + if (ops) + ops->sco_close(filp); + rcu_read_unlock(); +#endif } -EXPORT_SYMBOL_GPL(nfs42_ssc_unregister); -#endif /* CONFIG_NFS_V4_2 */ +EXPORT_SYMBOL_GPL(nfsd42_ssc_close); #ifdef CONFIG_NFS_V4_2 /** - * nfs_ssc_register - install the NFS_FS client ops in the nfs_ssc_client_tbl - * @ops: NFS_FS ops to be installed + * nfs42_ssc_register - install the NFS_V4 client ops in the nfs_ssc_client_tbl + * @ops: NFS_V4 ops to be installed * * Return values: * None */ -void nfs_ssc_register(const struct nfs_ssc_client_ops *ops) +void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops) { - nfs_ssc_client_tbl.ssc_nfs_ops = ops; + rcu_assign_pointer(nfs_ssc_client_tbl.ssc_nfs4_ops, ops); } -EXPORT_SYMBOL_GPL(nfs_ssc_register); +EXPORT_SYMBOL_GPL(nfs42_ssc_register); /** - * nfs_ssc_unregister - uninstall the NFS_FS client ops from + * nfs42_ssc_unregister - uninstall the NFS_V4 client ops from * the nfs_ssc_client_tbl * @ops: ops to be uninstalled * * Return values: * None */ -void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops) +void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops) { - if (nfs_ssc_client_tbl.ssc_nfs_ops != ops) + if (rcu_dereference_protected(nfs_ssc_client_tbl.ssc_nfs4_ops, + true) != ops) return; - nfs_ssc_client_tbl.ssc_nfs_ops = NULL; -} -EXPORT_SYMBOL_GPL(nfs_ssc_unregister); -#else -void nfs_ssc_register(const struct nfs_ssc_client_ops *ops) -{ + rcu_assign_pointer(nfs_ssc_client_tbl.ssc_nfs4_ops, NULL); + synchronize_rcu(); } -EXPORT_SYMBOL_GPL(nfs_ssc_register); - -void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops) -{ -} -EXPORT_SYMBOL_GPL(nfs_ssc_unregister); +EXPORT_SYMBOL_GPL(nfs42_ssc_unregister); #endif /* CONFIG_NFS_V4_2 */ diff --git a/fs/nfsd/blocklayout.c b/fs/nfsd/blocklayout.c index 5be7721c22c2..df02cf746479 100644 --- a/fs/nfsd/blocklayout.c +++ b/fs/nfsd/blocklayout.c @@ -9,6 +9,7 @@ #include <linux/nfsd/debug.h> +#include "nfserr.h" #include "blocklayoutxdr.h" #include "pnfs.h" #include "filecache.h" diff --git a/fs/nfsd/blocklayoutxdr.c b/fs/nfsd/blocklayoutxdr.c index f80dbc41fd5f..a6589f5c878a 100644 --- a/fs/nfsd/blocklayoutxdr.c +++ b/fs/nfsd/blocklayoutxdr.c @@ -8,11 +8,22 @@ #include <linux/nfs4.h> #include "nfsd.h" +#include "nfserr.h" #include "blocklayoutxdr.h" #include "vfs.h" #define NFSDDBG_FACILITY NFSDDBG_PNFS +static __be32 +nfsd4_decode_deviceid4(struct xdr_stream *xdr, struct nfsd4_deviceid *devid) +{ + __be32 *p = xdr_inline_decode(xdr, NFS4_DEVICEID4_SIZE); + + if (unlikely(!p)) + return nfserr_bad_xdr; + svcxdr_decode_deviceid4(p, devid); + return nfs_ok; +} /** * nfsd4_block_encode_layoutget - encode block/scsi layout extent array diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index b6e0c543e028..e5a0f1ababe6 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -21,6 +21,8 @@ #include <uapi/linux/nfsd_netlink.h> #include "nfsd.h" +#include "nfserr.h" +#include "nfs4ctl.h" #include "nfsfh.h" #include "netns.h" #include "pnfs.h" @@ -1890,21 +1892,19 @@ __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp, * check_nfsd_access - check if access to export is allowed. * @exp: svc_export that is being accessed. * @rqstp: svc_rqst attempting to access @exp. - * @may_bypass_gss: reduce strictness of authorization check * * Return values: * %nfs_ok if access is granted, or * %nfserr_wrongsec if access is denied */ -__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp, - bool may_bypass_gss) +__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp) { __be32 status; status = check_xprtsec_policy(exp, rqstp); if (status != nfs_ok) return status; - return check_security_flavor(exp, rqstp, may_bypass_gss); + return check_security_flavor(exp, rqstp, false); } /* diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h index d2b09cd76145..117fb28db1e0 100644 --- a/fs/nfsd/export.h +++ b/fs/nfsd/export.h @@ -104,8 +104,7 @@ int nfsexp_flags(struct svc_cred *cred, struct svc_export *exp); __be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp); __be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp, bool may_bypass_gss); -__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp, - bool may_bypass_gss); +__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp); /* * Function declarations diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index b9548eb17c77..3539149cc75f 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -43,6 +43,7 @@ #include "vfs.h" #include "nfsd.h" +#include "nfserr.h" #include "nfsfh.h" #include "netns.h" #include "filecache.h" diff --git a/fs/nfsd/flexfilelayout.c b/fs/nfsd/flexfilelayout.c index 6d531285ab43..0deb913493a3 100644 --- a/fs/nfsd/flexfilelayout.c +++ b/fs/nfsd/flexfilelayout.c @@ -13,7 +13,9 @@ #include <linux/sunrpc/addr.h> +#include "nfserr.h" #include "flexfilelayoutxdr.h" +#include "auth.h" #include "pnfs.h" #include "vfs.h" @@ -23,10 +25,10 @@ static __be32 nfsd4_ff_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode, const struct svc_fh *fhp, struct nfsd4_layoutget *args) { + struct user_namespace *userns = nfsd_user_namespace(rqstp); struct nfsd4_layout_seg *seg = &args->lg_seg; u32 device_generation = 0; int error; - uid_t u; struct pnfs_ff_layout *fl; @@ -49,20 +51,22 @@ nfsd4_ff_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode, fl->flags = FF_FLAGS_NO_LAYOUTCOMMIT | FF_FLAGS_NO_IO_THRU_MDS | FF_FLAGS_NO_READ_IO; - /* Do not allow a IOMODE_READ segment to have write pemissions */ - if (seg->iomode == IOMODE_READ) { - u = from_kuid(&init_user_ns, inode->i_uid) + 1; - fl->uid = make_kuid(&init_user_ns, u); - } else - fl->uid = inode->i_uid; - fl->gid = inode->i_gid; + fl->uid = from_kuid_munged(userns, inode->i_uid); + fl->gid = from_kgid_munged(userns, inode->i_gid); + + /* + * Do not allow an IOMODE_READ segment to have write permissions. + * The group is left intact so group-readable files stay readable; + * nfsd_setuser() squashes an unmapped uid to the export's anon ID. + */ + if (seg->iomode == IOMODE_READ) + fl->uid++; error = nfsd4_set_deviceid(&fl->deviceid, fhp, device_generation); if (error) goto out_error; - fl->fh.size = fhp->fh_handle.fh_size; - memcpy(fl->fh.data, &fhp->fh_handle.fh_raw, fl->fh.size); + fh_copy_shallow(&fl->fh, &fhp->fh_handle); /* Give whole file layout segments */ seg->offset = 0; diff --git a/fs/nfsd/flexfilelayoutxdr.c b/fs/nfsd/flexfilelayoutxdr.c index 374e52d3064a..e297100a2ac3 100644 --- a/fs/nfsd/flexfilelayoutxdr.c +++ b/fs/nfsd/flexfilelayoutxdr.c @@ -6,6 +6,7 @@ #include <linux/nfs4.h> #include "nfsd.h" +#include "nfserr.h" #include "flexfilelayoutxdr.h" #define NFSDDBG_FACILITY NFSDDBG_PNFS @@ -30,10 +31,10 @@ nfsd4_ff_encode_layoutget(struct xdr_stream *xdr, struct ff_idmap uid; struct ff_idmap gid; - fh_len = 4 + xdr_align_size(fl->fh.size); + fh_len = 4 + xdr_align_size(fl->fh.fh_size); - uid.len = sprintf(uid.buf, "%u", from_kuid(&init_user_ns, fl->uid)); - gid.len = sprintf(gid.buf, "%u", from_kgid(&init_user_ns, fl->gid)); + uid.len = sprintf(uid.buf, "%u", fl->uid); + gid.len = sprintf(gid.buf, "%u", fl->gid); /* data server entry: deviceid + efficiency + stateid + fh list + * user + group + flags + stats_collect_hint @@ -68,7 +69,7 @@ nfsd4_ff_encode_layoutget(struct xdr_stream *xdr, sizeof(stateid_opaque_t)); *p++ = cpu_to_be32(1); /* single file handle */ - p = xdr_encode_opaque(p, fl->fh.data, fl->fh.size); + p = xdr_encode_opaque(p, fl->fh.fh_raw, fl->fh.fh_size); p = xdr_encode_opaque(p, uid.buf, uid.len); p = xdr_encode_opaque(p, gid.buf, gid.len); diff --git a/fs/nfsd/flexfilelayoutxdr.h b/fs/nfsd/flexfilelayoutxdr.h index 6d5a1066a903..f7d1dd0708ec 100644 --- a/fs/nfsd/flexfilelayoutxdr.h +++ b/fs/nfsd/flexfilelayoutxdr.h @@ -6,6 +6,7 @@ #define _NFSD_FLEXFILELAYOUTXDR_H 1 #include <linux/inet.h> +#include "nfsfh.h" #include "xdr4.h" #define FF_FLAGS_NO_LAYOUTCOMMIT 1 @@ -35,11 +36,12 @@ struct pnfs_ff_device_addr { struct pnfs_ff_layout { u32 flags; u32 stats_collect_hint; - kuid_t uid; - kgid_t gid; + /* Values to encode; nfsd4_ff_proc_layoutget() has mapped these */ + u32 uid; + u32 gid; struct nfsd4_deviceid deviceid; stateid_t stateid; - struct nfs_fh fh; + struct knfsd_fh fh; }; __be32 nfsd4_ff_encode_getdeviceinfo(struct xdr_stream *xdr, diff --git a/fs/nfsd/localio.c b/fs/nfsd/localio.c index c458c01e9478..33b56d1b3f44 100644 --- a/fs/nfsd/localio.c +++ b/fs/nfsd/localio.c @@ -11,11 +11,9 @@ #include <linux/exportfs.h> #include <linux/sunrpc/svcauth.h> #include <linux/sunrpc/clnt.h> -#include <linux/nfs.h> #include <linux/nfs_common.h> +#include <linux/nfs_fh.h> #include <linux/nfslocalio.h> -#include <linux/nfs_fs.h> -#include <linux/nfs_xdr.h> #include <linux/string.h> #include "nfsd.h" @@ -55,7 +53,7 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom, struct nfsd_file *localio; __be32 beres; - if (nfs_fh->size > NFS4_FHSIZE) + if (nfs_fh->size > NFS_MAXFHSIZE) return ERR_PTR(-EINVAL); if (!nfsd_net_try_get(net)) @@ -68,7 +66,7 @@ nfsd_open_local_fh(struct net *net, struct auth_domain *dom, return localio; /* nfs_fh -> svc_fh */ - fh_init(&fh, NFS4_FHSIZE); + fh_init(&fh, NFSD_FHSIZE_UNSPEC); fh.fh_handle.fh_size = nfs_fh->size; memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size); @@ -179,7 +177,7 @@ static bool localio_decode_uuidarg(struct svc_rqst *rqstp, struct localio_uuidarg *argp = rqstp->rq_argp; u8 uuid[UUID_SIZE]; - if (decode_opaque_fixed(xdr, uuid, UUID_SIZE)) + if (xdr_stream_decode_opaque_fixed(xdr, uuid, UUID_SIZE) < 0) return false; import_uuid(&argp->uuid, uuid); diff --git a/fs/nfsd/lockd.c b/fs/nfsd/lockd.c index 72a5b499839d..5ec0f5456063 100644 --- a/fs/nfsd/lockd.c +++ b/fs/nfsd/lockd.c @@ -10,6 +10,7 @@ #include <linux/file.h> #include <linux/lockd/bind.h> #include "nfsd.h" +#include "nfserr.h" #include "vfs.h" #define NFSDDBG_FACILITY NFSDDBG_LOCKD @@ -33,8 +34,7 @@ static int nlm_fopen(struct svc_rqst *rqstp, struct nfs_fh *f, int access; struct svc_fh fh; - /* must initialize before using! but maxsize doesn't matter */ - fh_init(&fh,0); + fh_init(&fh, NFSD_FHSIZE_UNSPEC); fh.fh_handle.fh_size = f->size; memcpy(&fh.fh_handle.fh_raw, f->data, f->size); fh.fh_export = NULL; diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 190f5a001900..33610deda3b0 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -6,6 +6,7 @@ */ #include "nfsd.h" +#include "nfserr.h" /* FIXME: nfsacl.h is a broken header */ #include <linux/nfsacl.h> #include <linux/gfp.h> @@ -16,6 +17,48 @@ #define NFSDDBG_FACILITY NFSDDBG_PROC /* + * These maps are identical to the NFSv3 maps (nfs3proc.c). This enables + * the behavior of the two versions to diverge if needed. + */ +static const struct nfsd_access_map nfsd2_regaccess[] = { + { NFS3_ACCESS_READ, NFSD_MAY_READ }, + { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC }, + { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, + { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE }, + { 0, 0 } +}; + +static const struct nfsd_access_map nfsd2_diraccess[] = { + { NFS3_ACCESS_READ, NFSD_MAY_READ }, + { NFS3_ACCESS_LOOKUP, NFSD_MAY_EXEC }, + { NFS3_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, + { NFS3_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE }, + { NFS3_ACCESS_DELETE, NFSD_MAY_REMOVE }, + { 0, 0 } +}; + +/* + * Some clients - Solaris 2.6 at least, make an access call to the NFS + * server to check for access for things like /dev/null (which really, + * NFSD doesn't care about). So NFSD provides simple access checking + * for those objects, looking mainly at mode bits, ignoring read-only + * filesystem checks. + */ +static const struct nfsd_access_map nfsd2_otheraccess[] = { + { NFS3_ACCESS_READ, NFSD_MAY_READ }, + { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC }, + { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, + { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, + { 0, 0 } +}; + +static const struct nfsd_access_maps nfsd2_access_maps = { + .regular = nfsd2_regaccess, + .directory = nfsd2_diraccess, + .other = nfsd2_otheraccess, +}; + +/* * NULL call. */ static __be32 @@ -180,7 +223,9 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp) fh_copy(&resp->fh, &argp->fh); resp->access = argp->access; - resp->status = nfsd_access(rqstp, &resp->fh, &resp->access, NULL); + + resp->status = nfsd_access(rqstp, &resp->fh, &nfsd2_access_maps, + &resp->access, NULL); if (resp->status != nfs_ok) goto out; resp->status = fh_getattr(&resp->fh, &resp->stat); diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c index 6b6b289db636..7183995182ab 100644 --- a/fs/nfsd/nfs3acl.c +++ b/fs/nfsd/nfs3acl.c @@ -6,6 +6,7 @@ */ #include "nfsd.h" +#include "nfserr.h" /* FIXME: nfsacl.h is a broken header */ #include <linux/nfsacl.h> #include <linux/gfp.h> diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c index 0904d953d10e..17bbe5d13f18 100644 --- a/fs/nfsd/nfs3proc.c +++ b/fs/nfsd/nfs3proc.c @@ -13,6 +13,7 @@ #include "cache.h" #include "xdr3.h" #include "vfs.h" +#include "nfserr.h" #include "filecache.h" #include "trace.h" @@ -48,6 +49,58 @@ static bool nfsd3_time_in_range(const struct iattr *iap) return true; } +static const struct nfsd_access_map nfsd3_regaccess[] = { + { NFS3_ACCESS_READ, NFSD_MAY_READ }, + { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC }, + { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, + { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE }, + { 0, 0 } +}; + +static const struct nfsd_access_map nfsd3_diraccess[] = { + { NFS3_ACCESS_READ, NFSD_MAY_READ }, + { NFS3_ACCESS_LOOKUP, NFSD_MAY_EXEC }, + { NFS3_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, + { NFS3_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE }, + { NFS3_ACCESS_DELETE, NFSD_MAY_REMOVE }, + { 0, 0 } +}; + +/* + * Some clients - Solaris 2.6 at least, make an access call to the NFS + * server to check for access for things like /dev/null (which really, + * NFSD doesn't care about). So NFSD provides simple access checking + * for those objects, looking mainly at mode bits, ignoring read-only + * filesystem checks. + */ +static const struct nfsd_access_map nfsd3_otheraccess[] = { + { NFS3_ACCESS_READ, NFSD_MAY_READ }, + { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC }, + { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, + { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, + { 0, 0 } +}; + +static const struct nfsd_access_maps nfsd3_access_maps = { + .regular = nfsd3_regaccess, + .directory = nfsd3_diraccess, + .other = nfsd3_otheraccess, +}; + +static int nfsd3_iocb_flags(enum nfs3_stable_how how) +{ + switch (how) { + case NFS_FILE_SYNC: + /* persist data and timestamps */ + return IOCB_DSYNC | IOCB_SYNC; + case NFS_DATA_SYNC: + /* persist data only */ + return IOCB_DSYNC; + default: + return 0; + } +} + static __be32 nfsd3_map_status(__be32 status) { switch (status) { @@ -171,7 +224,8 @@ nfsd3_proc_access(struct svc_rqst *rqstp) fh_copy(&resp->fh, &argp->fh); resp->access = argp->access; - resp->status = nfsd_access(rqstp, &resp->fh, &resp->access, NULL); + resp->status = nfsd_access(rqstp, &resp->fh, &nfsd3_access_maps, + &resp->access, NULL); resp->status = nfsd3_map_status(resp->status); return rpc_success; } @@ -260,7 +314,8 @@ nfsd3_proc_write(struct svc_rqst *rqstp) resp->committed = argp->stable; resp->status = nfsd_write(rqstp, &resp->fh, argp->offset, &argp->payload, &cnt, - resp->committed, resp->verf); + nfsd3_iocb_flags(resp->committed), + resp->verf); resp->count = cnt; resp->status = nfsd3_map_status(resp->status); return rpc_success; @@ -282,6 +337,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_attrs attrs = { .na_iattr = iap, }; + struct svc_export *exp; __u32 v_mtime, v_atime; struct inode *inode; __be32 status; @@ -320,7 +376,23 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, goto out; } - status = fh_compose(resfhp, fhp->fh_export, child, fhp); + exp = exp_get(fhp->fh_export); + if (argp->createmode == NFS3_CREATE_UNCHECKED) { + /* + * If name is already in dcache we need to check for mountpoints + */ + if (d_is_reg(child) && + unlikely(nfsd_mountpoint(child, exp))) { + status = nfsd_cross_mnt(rqstp, &child, &exp); + if (status != nfs_ok) { + exp_put(exp); + goto out; + } + } + } + + status = fh_compose(resfhp, exp, child, fhp); + exp_put(exp); if (status != nfs_ok) goto out; diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c index e481804bb120..090cea8e545d 100644 --- a/fs/nfsd/nfs3xdr.c +++ b/fs/nfsd/nfs3xdr.c @@ -13,6 +13,7 @@ #include "auth.h" #include "netns.h" #include "vfs.h" +#include "nfserr.h" /* * Force construction of an empty post-op attr @@ -556,6 +557,8 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) return false; if (xdr_stream_decode_u32(xdr, &args->stable) < 0) return false; + if (args->stable > NFS_FILE_SYNC) + return false; /* opaque data */ if (xdr_stream_decode_u32(xdr, &args->len) < 0) diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c index 2c2f2fd89e87..94f6ad381ebe 100644 --- a/fs/nfsd/nfs4acl.c +++ b/fs/nfsd/nfs4acl.c @@ -40,6 +40,7 @@ #include "nfsfh.h" #include "nfsd.h" +#include "nfserr.h" #include "acl.h" #include "vfs.h" diff --git a/fs/nfsd/nfs4callback.c b/fs/nfsd/nfs4callback.c index 19dc337502ca..a6b31d3f2bf6 100644 --- a/fs/nfsd/nfs4callback.c +++ b/fs/nfsd/nfs4callback.c @@ -37,6 +37,7 @@ #include <linux/sunrpc/svc_xprt.h> #include <linux/slab.h> #include "nfsd.h" +#include "nfserr.h" #include "state.h" #include "netns.h" #include "stats.h" @@ -1529,12 +1530,14 @@ out: /** * nfsd41_cb_destroy_referring_call_list - release referring call info - * @cb: context of a callback that has completed + * @cb: context of callback to release referring calls from * * Callers who allocate referring calls using nfsd41_cb_referring_call() must * release those resources by calling nfsd41_cb_destroy_referring_call_list. * - * Caller serializes access to @cb. + * Caller serializes access to @cb. No CB_COMPOUND for @cb may be in + * flight, because encode_cb_sequence4args() walks this list as it + * encodes. */ void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb) { @@ -1556,6 +1559,7 @@ void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb) list_del(&rcl->__list); kfree(rcl); } + cb->cb_nr_referring_call_list = 0; } static void nfsd4_cb_prepare(struct rpc_task *task, void *calldata) diff --git a/fs/nfsd/nfs4ctl.h b/fs/nfsd/nfs4ctl.h new file mode 100644 index 000000000000..bcec4c4ef1d5 --- /dev/null +++ b/fs/nfsd/nfs4ctl.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Entry points by which the knfsd core drives the optional NFSv4 + * subsystem: state lifecycle, the laundromat workqueue, the recovery + * directory, junctions, the CLD notifier, and leases-net setup. + * + * Separated from nfsd.h so that the many translation units that + * include nfsd.h but call none of these -- among them the NFSv2 and + * NFSv3 paths -- do not have to parse them. The CONFIG_NFSD_V4=n + * stubs let the version-agnostic callers invoke the routines + * unconditionally. + */ + +#ifndef LINUX_NFSD_NFS4CTL_H +#define LINUX_NFSD_NFS4CTL_H + +#include <linux/stddef.h> +#include <linux/types.h> + +struct net; +struct inode; +struct dentry; +struct svc_rqst; +struct nfsd_net; + +#ifdef CONFIG_NFSD_V4 +extern unsigned long max_delegations; +int nfsd4_init_slabs(void); +void nfsd4_free_slabs(void); +int nfs4_state_start(void); +int nfs4_state_start_net(struct net *net); +void nfs4_state_shutdown(void); +void nfs4_state_shutdown_net(struct net *net); +int nfs4_reset_recoverydir(char *recdir); +char * nfs4_recoverydir(void); +bool nfsd4_spo_must_allow(struct svc_rqst *rqstp); +int nfsd4_create_laundry_wq(void); +void nfsd4_destroy_laundry_wq(void); +bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode); + +extern int nfsd4_is_junction(struct dentry *dentry); +extern int register_cld_notifier(void); +extern void unregister_cld_notifier(void); +#ifdef CONFIG_NFSD_V4_2_INTER_SSC +extern void nfsd4_ssc_init_umount_work(struct nfsd_net *nn); +#endif + +extern void nfsd4_init_leases_net(struct nfsd_net *nn); + +#else /* CONFIG_NFSD_V4 */ +static inline int nfsd4_init_slabs(void) { return 0; } +static inline void nfsd4_free_slabs(void) { } +static inline int nfs4_state_start(void) { return 0; } +static inline int nfs4_state_start_net(struct net *net) { return 0; } +static inline void nfs4_state_shutdown(void) { } +static inline void nfs4_state_shutdown_net(struct net *net) { } +static inline int nfs4_reset_recoverydir(char *recdir) { return 0; } +static inline char * nfs4_recoverydir(void) {return NULL; } +static inline bool nfsd4_spo_must_allow(struct svc_rqst *rqstp) +{ + return false; +} +static inline int nfsd4_create_laundry_wq(void) { return 0; }; +static inline void nfsd4_destroy_laundry_wq(void) {}; +static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, + struct inode *inode) +{ + return false; +} + +static inline int nfsd4_is_junction(struct dentry *dentry) +{ + return 0; +} + +static inline void nfsd4_init_leases_net(struct nfsd_net *nn) { }; + +#define register_cld_notifier() 0 +#define unregister_cld_notifier() do { } while(0) + +#endif /* CONFIG_NFSD_V4 */ + +#endif /* LINUX_NFSD_NFS4CTL_H */ diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c index e9faf8b78f74..4e5297593963 100644 --- a/fs/nfsd/nfs4idmap.c +++ b/fs/nfsd/nfs4idmap.c @@ -41,6 +41,7 @@ #include "auth.h" #include "idmap.h" #include "nfsd.h" +#include "nfserr.h" #include "netns.h" #include "vfs.h" diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index 22bcb6d09f70..4187202f9acc 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -9,6 +9,7 @@ #include <linux/sched.h> #include <linux/sunrpc/addr.h> +#include "nfserr.h" #include "pnfs.h" #include "netns.h" #include "trace.h" diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 50c07561e31f..88385a161b4d 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -38,19 +38,22 @@ #include <linux/slab.h> #include <linux/kthread.h> #include <linux/namei.h> +#include <linux/pagemap.h> #include <linux/sunrpc/addr.h> -#include <linux/nfs_ssc.h> +#include <linux/nfsd_ssc.h> #include "attr4.h" #include "idmap.h" #include "cache.h" #include "xdr4.h" +#include "nfs4ctl.h" #include "vfs.h" #include "current_stateid.h" #include "netns.h" #include "acl.h" #include "pnfs.h" +#include "nfserr.h" #include "trace.h" static bool inter_copy_offload_enable; @@ -69,6 +72,57 @@ MODULE_PARM_DESC(nfsd4_ssc_umount_timeout, #define NFSDDBG_FACILITY NFSDDBG_PROC +static const struct nfsd_access_map nfsd4_regaccess[] = { + { NFS4_ACCESS_READ, NFSD_MAY_READ }, + { NFS4_ACCESS_EXECUTE, NFSD_MAY_EXEC }, + { NFS4_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, + { NFS4_ACCESS_EXTEND, NFSD_MAY_WRITE }, + { NFS4_ACCESS_XAREAD, NFSD_MAY_READ }, + { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE }, + { NFS4_ACCESS_XALIST, NFSD_MAY_READ }, + { 0, 0 } +}; + +static const struct nfsd_access_map nfsd4_diraccess[] = { + { NFS4_ACCESS_READ, NFSD_MAY_READ }, + { NFS4_ACCESS_LOOKUP, NFSD_MAY_EXEC }, + { NFS4_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, + { NFS4_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE }, + { NFS4_ACCESS_DELETE, NFSD_MAY_REMOVE }, + { NFS4_ACCESS_XAREAD, NFSD_MAY_READ }, + { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE }, + { NFS4_ACCESS_XALIST, NFSD_MAY_READ }, + { 0, 0 } +}; + +static const struct nfsd_access_map nfsd4_otheraccess[] = { + { NFS4_ACCESS_READ, NFSD_MAY_READ }, + { NFS4_ACCESS_EXECUTE, NFSD_MAY_EXEC }, + { NFS4_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, + { NFS4_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, + { 0, 0 } +}; + +static const struct nfsd_access_maps nfsd4_access_maps = { + .regular = nfsd4_regaccess, + .directory = nfsd4_diraccess, + .other = nfsd4_otheraccess, +}; + +static int nfsd4_iocb_flags(enum stable_how4 how) +{ + switch (how) { + case FILE_SYNC4: + /* persist data and timestamps */ + return IOCB_DSYNC | IOCB_SYNC; + case DATA_SYNC4: + /* persist data only */ + return IOCB_DSYNC; + default: + return 0; + } +} + static u32 nfsd_attrmask[] = { NFSD_WRITEABLE_ATTRS_WORD0, NFSD_WRITEABLE_ATTRS_WORD1, @@ -169,23 +223,17 @@ do_open_permission(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfs return fh_verify(rqstp, current_fh, S_IFREG, accmode); } -static __be32 nfsd_check_obj_isreg(struct svc_fh *fh, u32 minor_version) +static int nfsd_check_obj_isreg(struct dentry *child) { - umode_t mode = d_inode(fh->fh_dentry)->i_mode; + umode_t mode = d_inode(child)->i_mode; if (S_ISREG(mode)) - return nfs_ok; + return 0; if (S_ISDIR(mode)) - return nfserr_isdir; + return -EISDIR; if (S_ISLNK(mode)) - return nfserr_symlink; - - /* RFC 7530 - 16.16.6 */ - if (minor_version == 0) - return nfserr_symlink; - else - return nfserr_wrong_type; - + return -ELOOP; + return -EFTYPE; } static void nfsd4_set_open_owner_reply_cache(struct nfsd4_compound_state *cstate, struct nfsd4_open *open, struct svc_fh *resfh) @@ -202,40 +250,50 @@ static inline bool nfsd4_create_is_exclusive(int createmode) createmode == NFS4_CREATE_EXCLUSIVE4_1; } -static __be32 -nfsd4_vfs_create(struct svc_fh *fhp, struct dentry **child, - struct nfsd4_open *open) +static struct file *do_lookup_open(struct path *parent, + struct qstr *name, + unsigned int oflags, + umode_t mode) { - struct file *filp; + struct file *filp = NULL; struct path path; - int oflags; + struct dentry *child; + int want_write_err = 0; - oflags = O_CREAT | O_LARGEFILE; - if (nfsd4_create_is_exclusive(open->op_createmode)) - oflags |= O_EXCL; + want_write_err = mnt_want_write(parent->mnt); - switch (open->op_share_access & NFS4_SHARE_ACCESS_BOTH) { - case NFS4_SHARE_ACCESS_WRITE: - oflags |= O_WRONLY; - break; - case NFS4_SHARE_ACCESS_BOTH: - oflags |= O_RDWR; - break; - default: - oflags |= O_RDONLY; + child = start_creating(&nop_mnt_idmap, parent->dentry, name); + if (IS_ERR(child)) { + filp = ERR_CAST(child); + goto out; } + path.mnt = parent->mnt; + path.dentry = child; - path.mnt = fhp->fh_export->ex_path.mnt; - path.dentry = *child; - filp = dentry_create(&path, oflags, open->op_iattr.ia_mode, - current_cred()); - *child = path.dentry; - - if (IS_ERR(filp)) - return nfserrno(PTR_ERR(filp)); + if (d_really_is_positive(child)) { + /* + * open the file so that we consistently have a valid + * op_filp and consequently a valid ->f_path.dentry. + */ + int err = nfsd_check_obj_isreg(child); - open->op_filp = filp; - return nfs_ok; + if (err) + filp = ERR_PTR(err); + else + filp = dentry_open(&path, oflags, current_cred()); + } else if (!(oflags & O_CREAT)) { + filp = ERR_PTR(-ENOENT); + } else if (want_write_err) { + filp = ERR_PTR(want_write_err); + } else { + filp = dentry_create(&path, oflags, mode, current_cred()); + child = path.dentry; + } + end_creating(child); +out: + if (!want_write_err) + mnt_drop_write(parent->mnt); + return filp; } /* @@ -254,11 +312,15 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, .na_iattr = iap, .na_seclabel = &open->op_label, }; - struct dentry *parent, *child = ERR_PTR(-EINVAL); + int oflags = O_CREAT | O_LARGEFILE; + struct dentry *child = ERR_PTR(-EINVAL); + struct path parent = { + .mnt = fhp->fh_export->ex_path.mnt, + .dentry = fhp->fh_dentry, + }; __u32 v_mtime, v_atime; - struct inode *inode; - __be32 status; - int host_err; + __be32 status, create_status; + int want_write_err; if (name_is_dot_dotdot(open->op_fname, open->op_fnamelen)) return nfserr_exist; @@ -268,25 +330,67 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC); if (status != nfs_ok) return status; - parent = fhp->fh_dentry; - inode = d_inode(parent); - host_err = fh_want_write(fhp); - if (host_err) - return nfserrno(host_err); + if (open->op_createmode == NFS4_CREATE_UNCHECKED) { + /* + * If name is already in dcache we need to check for mountpoints + */ + child = try_lookup_noperm(&QSTR_LEN(open->op_fname, + open->op_fnamelen), + parent.dentry); + if (child && !IS_ERR(child) && d_is_reg(child) && + unlikely(nfsd_mountpoint(child, fhp->fh_export))) { + struct svc_export *exp = exp_get(fhp->fh_export); + + status = nfsd_cross_mnt(rqstp, &child, &exp); + if (status == nfs_ok) + status = fh_compose(resfhp, exp, + child, fhp); + fh_fill_post_noop(fhp); + open->op_truncate = + (iap->ia_valid & ATTR_SIZE) && + !iap->ia_size; + dput(child); + exp_put(exp); + return status; + } + if (!IS_ERR(child)) + dput(child); + } - if (open->op_acl) { + if (!IS_POSIXACL(d_inode(parent.dentry))) + iap->ia_mode &= ~current_umask(); + + /* + * For the EXCLUSIVE modes we do our own uniqueness tests + * so don't want O_EXCL. + */ + if (open->op_createmode == NFS4_CREATE_GUARDED) + oflags |= O_EXCL; + + switch (open->op_share_access & NFS4_SHARE_ACCESS_BOTH) { + case NFS4_SHARE_ACCESS_WRITE: + oflags |= O_WRONLY; + break; + case NFS4_SHARE_ACCESS_BOTH: + oflags |= O_RDWR; + break; + default: + oflags |= O_RDONLY; + } + + if (!is_create_with_attrs(open)) { + /* No attrs to check */ + } else if (open->op_acl) { if (open->op_dpacl || open->op_pacl) { - status = nfserr_inval; - goto out; + /* Cannot specify both NFSv4 and Posix ACLs */ + return nfserr_inval; } - if (is_create_with_attrs(open)) { - status = nfsd4_acl_to_attr(NF4REG, open->op_acl, + status = nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs); - if (status) - goto out; - } - } else if (is_create_with_attrs(open)) { + if (status) + return status; + } else { /* The dpacl and pacl will get released by nfsd_attrs_free(). */ attrs.na_dpacl = open->op_dpacl; attrs.na_pacl = open->op_pacl; @@ -294,19 +398,6 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, open->op_pacl = NULL; } - child = start_creating(&nop_mnt_idmap, parent, - &QSTR_LEN(open->op_fname, open->op_fnamelen)); - if (IS_ERR(child)) { - status = nfserrno(PTR_ERR(child)); - goto out; - } - - if (d_really_is_negative(child)) { - status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE); - if (status != nfs_ok) - goto out; - } - v_mtime = 0; v_atime = 0; if (nfsd4_create_is_exclusive(open->op_createmode)) { @@ -322,24 +413,53 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, */ v_mtime = verifier[0] & 0x7fffffff; v_atime = verifier[1] & 0x7fffffff; + + iap->ia_valid |= ATTR_MTIME | ATTR_ATIME | + ATTR_MTIME_SET|ATTR_ATIME_SET; + iap->ia_mtime.tv_sec = v_mtime; + iap->ia_atime.tv_sec = v_atime; + iap->ia_mtime.tv_nsec = 0; + iap->ia_atime.tv_nsec = 0; } - if (d_really_is_positive(child)) { - /* NFSv4 protocol requires change attributes even though - * no change happened. - */ - status = fh_fill_both_attrs(fhp); - if (status != nfs_ok) - goto out; + create_status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE); + if (create_status) + /* Might still succeed if no create is needed */ + oflags &= ~O_CREAT; + + open->op_filp = do_lookup_open(&parent, + &QSTR_LEN(open->op_fname, + open->op_fnamelen), + oflags, + open->op_iattr.ia_mode); + if (IS_ERR(open->op_filp)) { + status = nfserrno(PTR_ERR(open->op_filp)); + open->op_filp = NULL; + if (status == nfserr_noent && create_status) + status = create_status; + goto out; + } - status = fh_compose(resfhp, fhp->fh_export, child, fhp); - if (status != nfs_ok) - goto out; + child = open->op_filp->f_path.dentry; + open->op_created = open->op_filp->f_mode & FMODE_CREATED; - switch (open->op_createmode) { - case NFS4_CREATE_UNCHECKED: - if (!d_is_reg(child)) - break; + status = fh_compose(resfhp, fhp->fh_export, child, fhp); + if (status != nfs_ok) + goto out; + + if (!open->op_created && + nfsd4_create_is_exclusive(open->op_createmode) && + inode_get_mtime_sec(d_inode(child)) == v_mtime && + inode_get_atime_sec(d_inode(child)) == v_atime && + d_inode(child)->i_size == 0) + open->op_created = true; + + if (!open->op_created) { + if (open->op_createmode == NFS4_CREATE_UNCHECKED) { + /* NFSv4 protocol requires change attributes + * even though no change happened. + */ + fh_fill_post_noop(fhp); /* * In NFSv4, we don't want to truncate the file @@ -347,63 +467,30 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, * some other reason. Furthermore, if the size is * nonzero, we should ignore it according to spec! */ - open->op_truncate = (iap->ia_valid & ATTR_SIZE) && - !iap->ia_size; - break; - case NFS4_CREATE_GUARDED: + open->op_truncate = (d_is_reg(child) && + (iap->ia_valid & ATTR_SIZE) && + !iap->ia_size); + } else status = nfserr_exist; - break; - case NFS4_CREATE_EXCLUSIVE: - if (inode_get_mtime_sec(d_inode(child)) == v_mtime && - inode_get_atime_sec(d_inode(child)) == v_atime && - d_inode(child)->i_size == 0) { - open->op_created = true; - break; /* subtle */ - } - status = nfserr_exist; - break; - case NFS4_CREATE_EXCLUSIVE4_1: - if (inode_get_mtime_sec(d_inode(child)) == v_mtime && - inode_get_atime_sec(d_inode(child)) == v_atime && - d_inode(child)->i_size == 0) { - open->op_created = true; - goto set_attr; /* subtle */ - } - status = nfserr_exist; - } goto out; } - - if (!IS_POSIXACL(inode)) - iap->ia_mode &= ~current_umask(); - - status = fh_fill_pre_attrs(fhp); - if (status != nfs_ok) - goto out; - status = nfsd4_vfs_create(fhp, &child, open); - if (status != nfs_ok) - goto out; - open->op_created = true; + /* file was created */ fh_fill_post_attrs(fhp); - status = fh_compose(resfhp, fhp->fh_export, child, fhp); - if (status != nfs_ok) - goto out; - /* A newly created file already has a file size of zero. */ if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0)) iap->ia_valid &= ~ATTR_SIZE; - if (nfsd4_create_is_exclusive(open->op_createmode)) { - iap->ia_valid = ATTR_MTIME | ATTR_ATIME | - ATTR_MTIME_SET|ATTR_ATIME_SET; - iap->ia_mtime.tv_sec = v_mtime; - iap->ia_atime.tv_sec = v_atime; - iap->ia_mtime.tv_nsec = 0; - iap->ia_atime.tv_nsec = 0; - } -set_attr: - status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs); + /* We will need write access to set the attrs */ + want_write_err = fh_want_write(fhp); + if (!want_write_err) { + status = nfsd_create_setattr(rqstp, fhp, + resfhp, &attrs); + fh_drop_write(fhp); + } else if (nfsd_attrs_valid(&attrs)) { + /* Needed write access */ + status = nfserrno(want_write_err); + } if (attrs.na_labelerr) open->op_bmval[2] &= ~FATTR4_WORD2_SECURITY_LABEL; @@ -414,9 +501,7 @@ set_attr: if (attrs.na_paclerr) open->op_bmval[2] &= ~FATTR4_WORD2_POSIX_ACCESS_ACL; out: - end_creating(child); nfsd_attrs_free(&attrs); - fh_drop_write(fhp); return status; } @@ -465,6 +550,9 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru fh_init(*resfh, NFS4_FHSIZE); open->op_truncate = false; + status = fh_fill_pre_attrs_unlocked(current_fh); + if (status) + goto out; if (open->op_create) { /* FIXME: check session persistence and pnfs flags. * The nfsv4.1 spec requires the following semantics: @@ -496,15 +584,15 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru } else { status = nfsd_lookup(rqstp, current_fh, open->op_fname, open->op_fnamelen, *resfh); - if (status == nfs_ok) - /* NFSv4 protocol requires change attributes even though - * no change happened. - */ - status = fh_fill_both_attrs(current_fh); + /* + * NFSv4 protocol requires change attributes even though + * no change happened. + */ + fh_fill_post_noop(current_fh); } if (status) goto out; - status = nfsd_check_obj_isreg(*resfh, cstate->minorversion); + status = nfserrno(nfsd_check_obj_isreg((*resfh)->fh_dentry)); if (status) goto out; @@ -516,6 +604,10 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru status = do_open_permission(rqstp, *resfh, open, accmode); set_change_info(&open->op_cinfo, current_fh); out: + if (status == nfserr_wrong_type && cstate->minorversion == 0) + /* RFC 7530 - 16.16.6 */ + return nfserr_symlink; + return status; } @@ -788,17 +880,18 @@ nfsd4_access(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_access *access = &u->access; u32 access_full; - access_full = NFS3_ACCESS_FULL; + access_full = NFS4_ACCESS_READ | NFS4_ACCESS_LOOKUP | + NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND | + NFS4_ACCESS_DELETE | NFS4_ACCESS_EXECUTE; if (cstate->minorversion >= 2) access_full |= NFS4_ACCESS_XALIST | NFS4_ACCESS_XAREAD | NFS4_ACCESS_XAWRITE; if (access->ac_req_access & ~access_full) return nfserr_inval; - access->ac_resp_access = access->ac_req_access; - return nfsd_access(rqstp, &cstate->current_fh, &access->ac_resp_access, - &access->ac_supported); + return nfsd_access(rqstp, &cstate->current_fh, &nfsd4_access_maps, + &access->ac_resp_access, &access->ac_supported); } static __be32 @@ -1377,7 +1470,7 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, write->wr_how_written = write->wr_stable_how; status = nfsd_vfs_write(rqstp, &cstate->current_fh, nf, write->wr_offset, &write->wr_payload, - &cnt, write->wr_how_written, + &cnt, nfsd4_iocb_flags(write->wr_how_written), (__be32 *)write->wr_verifier.data); nfsd_file_put(nf); @@ -1431,16 +1524,37 @@ nfsd4_clone(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, { struct nfsd4_clone *clone = &u->clone; struct nfsd_file *src, *dst; + bool sync_failed = false; + errseq_t since; __be32 status; + int host_err; status = nfsd4_verify_copy(rqstp, cstate, &clone->cl_src_stateid, &src, &clone->cl_dst_stateid, &dst); if (status) goto out; - status = nfsd4_clone_file_range(rqstp, src, clone->cl_src_pos, - dst, clone->cl_dst_pos, clone->cl_count, - EX_ISSYNC(cstate->current_fh.fh_export)); + host_err = nfsd_clone_file_range(src->nf_file, clone->cl_src_pos, + dst->nf_file, clone->cl_dst_pos, + clone->cl_count, &since); + if (!host_err && EX_ISSYNC(cstate->current_fh.fh_export)) { + host_err = nfsd_clone_sync_range(src->nf_file, dst->nf_file, + clone->cl_dst_pos, + clone->cl_count, since); + sync_failed = host_err < 0; + } + if (host_err < 0) { + trace_nfsd_clone_file_range_err(rqstp, &cstate->save_fh, + clone->cl_src_pos, &cstate->current_fh, + clone->cl_dst_pos, clone->cl_count, host_err); + if (sync_failed) { + struct nfsd_net *nn = net_generic(dst->nf_net, + nfsd_net_id); + + nfsd_maybe_reset_write_verifier(nn, rqstp, host_err); + } + } + status = nfserrno(host_err); if (!status && (READ_ONCE(dst->nf_file->f_mode) & FMODE_NOCMTIME) != 0) nfsd_update_cmtime_attr(dst->nf_file, 0); @@ -1653,13 +1767,6 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb) #ifdef CONFIG_NFSD_V4_2_INTER_SSC -extern struct file *nfs42_ssc_open(struct vfsmount *ss_mnt, - struct nfs_fh *src_fh, - nfs4_stateid *stateid); -extern void nfs42_ssc_close(struct file *filep); - -extern void nfs_sb_deactive(struct super_block *sb); - #define NFSD42_INTERSSC_MOUNTOPS "vers=4.2,addr=%s,sec=sys" /* @@ -1882,7 +1989,7 @@ nfsd4_cleanup_inter_ssc(struct nfsd4_ssc_umount_item *nsui, struct file *filp, struct nfsd_net *nn = net_generic(dst->nf_net, nfsd_net_id); long timeout = msecs_to_jiffies(nfsd4_ssc_umount_timeout); - nfs42_ssc_close(filp); + nfsd42_ssc_close(filp); fput(filp); spin_lock(&nn->nfsd_ssc_lock); @@ -1914,12 +2021,6 @@ nfsd4_cleanup_inter_ssc(struct nfsd4_ssc_umount_item *nsui, struct file *filp, { } -static struct file *nfs42_ssc_open(struct vfsmount *ss_mnt, - struct nfs_fh *src_fh, - nfs4_stateid *stateid) -{ - return NULL; -} #endif /* CONFIG_NFSD_V4_2_INTER_SSC */ static __be32 @@ -1974,7 +2075,7 @@ static void nfsd4_init_copy_res(struct nfsd4_copy *copy, bool sync) { copy->cp_res.wr_stable_how = test_bit(NFSD4_COPY_F_COMMITTED, ©->cp_flags) ? - NFS_FILE_SYNC : NFS_UNSTABLE; + FILE_SYNC4 : UNSTABLE4; nfsd4_copy_set_sync(copy, sync); } @@ -2142,8 +2243,8 @@ static int nfsd4_do_async_copy(void *data) if (nfsd4_ssc_is_inter(copy)) { struct file *filp; - filp = nfs42_ssc_open(copy->ss_nsui->nsui_vfsmount, - ©->c_fh, ©->stateid); + filp = nfsd42_ssc_open(copy->ss_nsui->nsui_vfsmount, + ©->c_fh, ©->stateid); if (IS_ERR(filp)) { switch (PTR_ERR(filp)) { case -EBADF: @@ -3328,7 +3429,7 @@ nfsd4_proc_compound(struct svc_rqst *rqstp) if (current_fh->fh_export && need_wrongsec_check(rqstp)) - op->status = check_nfsd_access(current_fh->fh_export, rqstp, false); + op->status = check_nfsd_access(current_fh->fh_export, rqstp); } encode_op: if (op->status == nfserr_replay_me) { diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index d513971fb119..aee3a0b22d1c 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -47,6 +47,7 @@ #include <linux/nfsd/cld.h> #include "nfsd.h" +#include "nfs4ctl.h" #include "state.h" #include "vfs.h" #include "netns.h" diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 9c4adf3110ae..06e4192bc693 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -45,13 +45,15 @@ #include <linux/string_helpers.h> #include <linux/fsnotify.h> #include <linux/rhashtable.h> -#include <linux/nfs_ssc.h> +#include <linux/nfsd_ssc.h> #include "xdr4.h" +#include "nfs4ctl.h" #include "xdr4cb.h" #include "vfs.h" #include "current_stateid.h" #include "stats.h" +#include "nfserr.h" #include "netns.h" #include "pnfs.h" @@ -92,6 +94,8 @@ static void nfsd4_end_grace(struct nfsd_net *nn); static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps); static void nfsd4_file_hash_remove(struct nfs4_file *fi); static void deleg_reaper(struct nfsd_net *nn); +static void nfsd4_drop_revoked_stid(struct nfs4_stid *s) + __releases(&s->sc_client->cl_lock); static const struct lease_manager_operations nfsd_lease_mng_ops; @@ -1163,6 +1167,8 @@ static void nfs4_free_deleg(struct nfs4_stid *stid) WARN_ON_ONCE(!list_empty(&dp->dl_perfile)); WARN_ON_ONCE(!list_empty(&dp->dl_perclnt)); WARN_ON_ONCE(!list_empty(&dp->dl_recall_lru)); + /* The list outlives one recall, so ->release() cannot free it. */ + nfsd41_cb_destroy_referring_call_list(&dp->dl_recall); kmem_cache_free(deleg_slab, stid); atomic_long_dec(&num_delegations); } @@ -1279,6 +1285,9 @@ __alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp, dp->dl_type = dl_type; dp->dl_retries = 1; dp->dl_recalled = false; + dp->dl_recall_rejected = false; + dp->dl_recall_grant.valid = false; + dp->dl_recall_grant.retired_at_send = false; get_nfs4_file(fp); dp->dl_stid.sc_file = fp; nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client, @@ -1314,6 +1323,7 @@ static void nfs4_free_dir_deleg(struct nfs4_stid *stid) for (i = 0; i < ncn->ncn_evt_cnt; ++i) nfsd_notify_event_put(ncn->ncn_evt[i]); kfree(ncn->ncn_nf); + kfree(ncn->ncn_masks); for (i = 0; i < NOTIFY4_PAGE_ARRAY_SIZE; i++) { if (!ncn->ncn_pages[i]) break; @@ -1346,6 +1356,11 @@ alloc_init_dir_deleg(struct nfs4_client *clp, struct nfs4_file *fp) nfs4_put_stid(&dp->dl_stid); return NULL; } + ncn->ncn_masks = kcalloc(NOTIFY4_EVENT_QUEUE_SIZE, sizeof(*ncn->ncn_masks), GFP_KERNEL); + if (!ncn->ncn_masks) { + nfs4_put_stid(&dp->dl_stid); + return NULL; + } spin_lock_init(&ncn->ncn_lock); nfsd4_init_cb(&ncn->ncn_cb, dp->dl_stid.sc_client, &nfsd4_cb_notify_ops, NFSPROC4_CLNT_CB_NOTIFY); @@ -1563,27 +1578,22 @@ static void destroy_delegation(struct nfs4_delegation *dp) } /** - * revoke_delegation - perform nfs4 delegation structure cleanup - * @dp: pointer to the delegation + * revoke_delegation - dispose of a delegation the server has revoked + * @dp: delegation to dispose of + * + * The caller holds a reference on @dp, which this function consumes. + * On NFSv4.1 and newer, @dp's sc_status must already carry + * SC_STATUS_REVOKED or SC_STATUS_ADMIN_REVOKED. * - * This function assumes that it's called either from the administrative - * interface (nfsd4_revoke_states()) that's revoking a specific delegation - * stateid or it's called from a laundromat thread (nfsd4_landromat()) that - * determined that this specific state has expired and needs to be revoked - * (both mark state with the appropriate stid sc_status mode). It is also - * assumed that a reference was taken on the @dp state. This function - * consumes that reference. + * @dp is parked on the client's cl_revoked list to await a FREE_STATEID. + * Where none can arrive, @dp is destroyed here instead: FREE_STATEID has + * already freed it, or the client rejected the recall with + * NFS4ERR_BADHANDLE or NFS4ERR_BAD_STATEID and holds no record of the + * delegation. NFS4ERR_ADMIN_REVOKED still prompts one, so an + * administrative revoke waits on cl_revoked. * - * If this function finds that the @dp state is SC_STATUS_FREED it means - * that a FREE_STATEID operation for this stateid has been processed and - * we can proceed to removing it from recalled list. However, if @dp state - * isn't marked SC_STATUS_FREED, it means we need place it on the cl_revoked - * list and wait for the FREE_STATEID to arrive from the client. At the same - * time, we need to mark it as SC_STATUS_FREEABLE to indicate to the - * nfsd4_free_stateid() function that this stateid has already been added - * to the cl_revoked list and that nfsd4_free_stateid() is now responsible - * for removing it from the list. Inspection of where the delegation state - * in the revocation process is protected by the clp->cl_lock. + * Context: Takes and releases the client's cl_lock; may sleep after + * dropping it. */ static void revoke_delegation(struct nfs4_delegation *dp) { @@ -1601,6 +1611,19 @@ static void revoke_delegation(struct nfs4_delegation *dp) list_del_init(&dp->dl_recall_lru); goto out; } + if (dp->dl_recall_rejected && + !(dp->dl_stid.sc_status & SC_STATUS_ADMIN_REVOKED)) { + /* + * SC_STATUS_CLOSED, set under cl_lock, makes a racing + * FREE_STATEID bail out rather than drop this reference + * too. The put releases what cl_revoked would have held. + */ + dp->dl_stid.sc_status |= SC_STATUS_CLOSED; + spin_unlock(&clp->cl_lock); + nfs4_put_stid(&dp->dl_stid); + destroy_unhashed_deleg(dp); + return; + } list_add(&dp->dl_recall_lru, &clp->cl_revoked); dp->dl_stid.sc_status |= SC_STATUS_FREEABLE; out: @@ -2789,10 +2812,16 @@ void nfsd4_put_client(struct nfs4_client *clp) static void free_client(struct nfs4_client *clp) { - while (!list_empty(&clp->cl_sessions)) { + LIST_HEAD(reaplist); + + /* client_info_show() walks cl_sessions under cl_lock */ + spin_lock(&clp->cl_lock); + list_splice_init(&clp->cl_sessions, &reaplist); + spin_unlock(&clp->cl_lock); + while (!list_empty(&reaplist)) { struct nfsd4_session *ses; - ses = list_entry(clp->cl_sessions.next, struct nfsd4_session, - se_perclnt); + ses = list_entry(reaplist.next, struct nfsd4_session, + se_perclnt); list_del(&ses->se_perclnt); WARN_ON_ONCE(atomic_read(&ses->se_ref)); free_session(ses); @@ -2889,11 +2918,18 @@ __destroy_client(struct nfs4_client *clp) list_del_init(&dp->dl_recall_lru); destroy_unhashed_deleg(dp); } + /* + * A CB_RECALL reply can release revoked delegations concurrently: + * nfsd4_shutdown_callback() has not run yet. + */ + spin_lock(&clp->cl_lock); while (!list_empty(&clp->cl_revoked)) { dp = list_entry(clp->cl_revoked.next, struct nfs4_delegation, dl_recall_lru); - list_del_init(&dp->dl_recall_lru); - nfs4_put_stid(&dp->dl_stid); + /* this function drops ->cl_lock */ + nfsd4_drop_revoked_stid(&dp->dl_stid); + spin_lock(&clp->cl_lock); } + spin_unlock(&clp->cl_lock); while (!list_empty(&clp->cl_openowners)) { oo = list_entry(clp->cl_openowners.next, struct nfs4_openowner, oo_perclient); nfs4_get_stateowner(&oo->oo_owner); @@ -3737,14 +3773,9 @@ nfsd4_cb_notify_prepare(struct nfsd4_callback *cb) struct nfsd_notify_event *nne = events[i]; if (!error) { - u32 *maskp = (u32 *)xdr_reserve_space(&stream, sizeof(*maskp)); + u32 *maskp = &ncn->ncn_masks[i]; u8 *p; - if (!maskp) { - error = true; - goto put_event; - } - p = nfsd4_encode_notify_event(&stream, nne, dp, nf, maskp); if (!p) { pr_notice("Could not generate CB_NOTIFY from fsnotify mask 0x%x\n", @@ -3762,13 +3793,10 @@ put_event: nfsd_notify_event_put(nne); } if (!error && (dp->dl_notify_mask & BIT(NOTIFY4_CHANGE_DIR_ATTRS))) { - u32 *maskp = (u32 *)xdr_reserve_space(&stream, sizeof(*maskp)); + u32 *maskp = &ncn->ncn_masks[count]; u8 *p; - if (maskp) - p = nfsd4_encode_dir_attr_change(&stream, dp, nf); - else - p = ERR_PTR(-ENOBUFS); + p = nfsd4_encode_dir_attr_change(&stream, dp, nf); if (IS_ERR(p)) { /* @@ -6063,6 +6091,80 @@ bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode) return timeo > 0; } +/* + * gen_sessionid() composes a sessionid from the client's clientid and a + * sequence counter, so the sequence alone identifies the granting session. + */ +static void nfsd4_recall_grant_sessionid(const struct nfs4_delegation *dp, + struct nfsd4_sessionid *sid) +{ + sid->clientid = dp->dl_stid.sc_client->cl_clientid; + sid->sequence = dp->dl_recall_grant.sessionid_seq; + sid->reserved = 0; +} + +static bool nfsd4_recall_grant_slot_retired(struct nfs4_delegation *dp) +{ + struct nfs4_client *clp = dp->dl_stid.sc_client; + struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + struct nfsd4_session *ses; + struct nfsd4_sessionid sid; + bool retired = false; + void *entry; + + if (!dp->dl_recall_grant.valid) + return false; + + nfsd4_recall_grant_sessionid(dp, &sid); + + /* + * A missing session does not prove the client saw the grant: a + * DESTROY_SESSION unhashes its own session before the reply to + * that compound is encoded. + */ + spin_lock(&nn->client_lock); + ses = __find_in_sessionid_hashtbl((struct nfs4_sessionid *)&sid, + clp->net); + entry = ses ? xa_load(&ses->se_slots, dp->dl_recall_grant.slotid) : NULL; + if (xa_is_value(entry)) { + /* + * A slot is freed only once the client has acknowledged + * the smaller slot table, which it cannot do while a + * request on that slot is outstanding. + */ + retired = true; + } else if (entry) { + struct nfsd4_slot *slot = entry; + + /* + * A reactivated slot was freed and rebuilt, so the same + * acknowledgment applies. The seqid test errs toward + * revoking: a rebuilt slot restarting at seqid 1 matches + * an old grant. + */ + retired = (slot->sl_flags & NFSD4_SLOT_REUSED) || + ((slot->sl_flags & NFSD4_SLOT_INITIALIZED) && + slot->sl_seqid != dp->dl_recall_grant.seqid); + } + spin_unlock(&nn->client_lock); + return retired; +} + +/* + * ->prepare does not run on every send: nfsd4_run_cb_work() skips it + * on a requeue, and a retry via rpc_restart_call_prepare() re-enters + * the RPC layer beneath it. The granting request does not change, so + * a send inherits a correct list. Retirement is the one transition + * the list has to follow. + */ +static void nfsd4_refresh_recall_grant(struct nfs4_delegation *dp) +{ + dp->dl_recall_grant.retired_at_send = + nfsd4_recall_grant_slot_retired(dp); + if (dp->dl_recall_grant.retired_at_send) + nfsd41_cb_destroy_referring_call_list(&dp->dl_recall); +} + static bool nfsd4_cb_recall_prepare(struct nfsd4_callback *cb) { struct nfs4_delegation *dp = cb_to_delegation(cb); @@ -6084,9 +6186,46 @@ static bool nfsd4_cb_recall_prepare(struct nfsd4_callback *cb) list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru); } spin_unlock(&nn->deleg_lock); + + nfsd4_refresh_recall_grant(dp); + + if (dp->dl_recall_grant.valid && !dp->dl_recall_grant.retired_at_send) { + struct nfsd4_sessionid sid; + + nfsd4_recall_grant_sessionid(dp, &sid); + nfsd41_cb_referring_call(&dp->dl_recall, + (struct nfs4_sessionid *)&sid, + dp->dl_recall_grant.slotid, + dp->dl_recall_grant.seqid); + } return true; } +/* + * cl_lock orders this against a laundromat reaping @dp: either + * revoke_delegation() observes dl_recall_rejected and destroys @dp, or + * it reached cl_revoked first and @dp is released here instead. + */ +static void nfsd4_deleg_recall_rejected(struct nfs4_delegation *dp) +{ + struct nfs4_client *clp = dp->dl_stid.sc_client; + + spin_lock(&clp->cl_lock); + if (dp->dl_stid.sc_status & (SC_STATUS_CLOSED | SC_STATUS_FREED | + SC_STATUS_ADMIN_REVOKED)) { + spin_unlock(&clp->cl_lock); + return; + } + if (dp->dl_stid.sc_status & SC_STATUS_FREEABLE) { + dp->dl_stid.sc_status |= SC_STATUS_CLOSED; + /* this function drops ->cl_lock */ + nfsd4_drop_revoked_stid(&dp->dl_stid); + return; + } + dp->dl_recall_rejected = true; + spin_unlock(&clp->cl_lock); +} + static int nfsd4_cb_recall_done(struct nfsd4_callback *cb, struct rpc_task *task) { @@ -6094,27 +6233,32 @@ static int nfsd4_cb_recall_done(struct nfsd4_callback *cb, trace_nfsd_cb_recall_done(&dp->dl_stid.sc_stateid, task); - if (dp->dl_stid.sc_status) - /* CLOSED or REVOKED */ - return 1; - switch (task->tk_status) { case 0: return 1; case -NFS4ERR_DELAY: + if (dp->dl_stid.sc_status) + /* CLOSED or REVOKED */ + return 1; rpc_delay(task, 2 * HZ); return 0; case -EBADHANDLE: case -NFS4ERR_BAD_STATEID: /* - * Race: client probably got cb_recall before open reply - * granting delegation. + * Retirement of the granting slot proves the client saw + * the grant. Trust the rejection only if the slot had + * retired when this recall was sent. */ - if (dp->dl_retries--) { + if (dp->dl_recall_grant.retired_at_send) { + nfsd4_deleg_recall_rejected(dp); + return 1; + } + if (!dp->dl_stid.sc_status && dp->dl_retries--) { + nfsd4_refresh_recall_grant(dp); rpc_delay(task, 2 * HZ); return 0; } - fallthrough; + return 1; default: return 1; } @@ -6708,9 +6852,25 @@ static bool nfsd4_want_deleg_timestamps(const struct nfsd4_open *open) return open->op_deleg_want & OPEN4_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS; } +static void +nfs4_delegation_record_grant_slot(struct nfs4_delegation *dp, + const struct nfsd4_compound_state *cstate) +{ + const struct nfsd4_sessionid *sid; + + if (!cstate->session) + return; + sid = (struct nfsd4_sessionid *)cstate->session->se_sessionid.data; + dp->dl_recall_grant.sessionid_seq = sid->sequence; + dp->dl_recall_grant.slotid = cstate->slot->sl_index; + dp->dl_recall_grant.seqid = cstate->slot->sl_seqid; + dp->dl_recall_grant.valid = true; +} + static struct nfs4_delegation * -nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp, - struct svc_fh *parent) +nfs4_set_delegation(struct nfsd4_open *open, + const struct nfsd4_compound_state *cstate, + struct nfs4_ol_stateid *stp, struct svc_fh *parent) { bool deleg_ts = nfsd4_want_deleg_timestamps(open); struct nfs4_client *clp = stp->st_stid.sc_client; @@ -6800,6 +6960,14 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp, dp = alloc_init_deleg(clp, fp, odstate, dl_type); if (!dp) goto out_delegees; + + /* + * Record the granting slot before kernel_setlease() makes @dp + * visible to lease breakers. A conflicting open can drive + * CB_RECALL to completion from that point on. + */ + nfs4_delegation_record_grant_slot(dp, cstate); + if (stp->st_stid.sc_export) dp->dl_stid.sc_export = exp_get(stp->st_stid.sc_export); @@ -6964,6 +7132,7 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open, struct nfs4_ol_stateid *stp, struct svc_fh *currentfh, struct svc_fh *fh) { + struct nfsd4_compoundres *resp = rqstp->rq_resp; struct nfs4_openowner *oo = openowner(stp->st_stateowner); bool deleg_ts = nfsd4_want_deleg_timestamps(open); struct nfs4_client *clp = stp->st_stid.sc_client; @@ -7000,7 +7169,7 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open, default: goto out_no_deleg; } - dp = nfs4_set_delegation(open, stp, parent); + dp = nfs4_set_delegation(open, &resp->cstate, stp, parent); if (IS_ERR(dp)) goto out_no_deleg; @@ -10294,6 +10463,7 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate, dp = alloc_init_dir_deleg(clp, fp); if (!dp) goto out_delegees; + nfs4_delegation_record_grant_slot(dp, cstate); if (cstate->current_fh.fh_export) dp->dl_stid.sc_export = exp_get(cstate->current_fh.fh_export); diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 606ddcb085c0..7d1b2d6f57f2 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -54,6 +54,7 @@ #include "xdr4.h" #include "vfs.h" #include "state.h" +#include "nfserr.h" #include "cache.h" #include "netns.h" #include "pnfs.h" @@ -1606,7 +1607,7 @@ nfsd4_decode_write(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u) return nfserr_bad_xdr; if (xdr_stream_decode_u32(argp->xdr, &write->wr_stable_how) < 0) return nfserr_bad_xdr; - if (write->wr_stable_how > NFS_FILE_SYNC) + if (write->wr_stable_how > FILE_SYNC4) return nfserr_bad_xdr; if (xdr_stream_decode_u32(argp->xdr, &write->wr_buflen) < 0) return nfserr_bad_xdr; @@ -1920,6 +1921,17 @@ nfsd4_decode_get_dir_delegation(struct nfsd4_compoundargs *argp, #ifdef CONFIG_NFSD_PNFS static __be32 +nfsd4_decode_deviceid4(struct xdr_stream *xdr, struct nfsd4_deviceid *devid) +{ + __be32 *p = xdr_inline_decode(xdr, NFS4_DEVICEID4_SIZE); + + if (unlikely(!p)) + return nfserr_bad_xdr; + svcxdr_decode_deviceid4(p, devid); + return nfs_ok; +} + +static __be32 nfsd4_decode_getdeviceinfo(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u) { @@ -2733,6 +2745,87 @@ nfsd4_decode_compound(struct nfsd4_compoundargs *argp) return true; } +static __always_inline __be32 +nfsd4_encode_bool(struct xdr_stream *xdr, bool val) +{ + __be32 *p = xdr_reserve_space(xdr, XDR_UNIT); + + if (unlikely(p == NULL)) + return nfserr_resource; + *p = val ? xdr_one : xdr_zero; + return nfs_ok; +} + +static __always_inline __be32 +nfsd4_encode_uint32_t(struct xdr_stream *xdr, u32 val) +{ + __be32 *p = xdr_reserve_space(xdr, XDR_UNIT); + + if (unlikely(p == NULL)) + return nfserr_resource; + *p = cpu_to_be32(val); + return nfs_ok; +} + +#define nfsd4_encode_aceflag4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_acemask4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_acetype4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_count4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_mode4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_nfs_lease4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_qop4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_sequenceid4(x, v) nfsd4_encode_uint32_t(x, v) +#define nfsd4_encode_slotid4(x, v) nfsd4_encode_uint32_t(x, v) + +static __always_inline __be32 +nfsd4_encode_uint64_t(struct xdr_stream *xdr, u64 val) +{ + __be32 *p = xdr_reserve_space(xdr, XDR_UNIT * 2); + + if (unlikely(p == NULL)) + return nfserr_resource; + put_unaligned_be64(val, p); + return nfs_ok; +} + +#define nfsd4_encode_changeid4(x, v) nfsd4_encode_uint64_t(x, v) +#define nfsd4_encode_nfs_cookie4(x, v) nfsd4_encode_uint64_t(x, v) +#define nfsd4_encode_length4(x, v) nfsd4_encode_uint64_t(x, v) +#define nfsd4_encode_offset4(x, v) nfsd4_encode_uint64_t(x, v) + +static __always_inline __be32 +nfsd4_encode_opaque_fixed(struct xdr_stream *xdr, const void *data, + size_t size) +{ + __be32 *p = xdr_reserve_space(xdr, xdr_align_size(size)); + size_t pad = xdr_pad_size(size); + + if (unlikely(p == NULL)) + return nfserr_resource; + memcpy(p, data, size); + if (pad) + memset((char *)p + size, 0, pad); + return nfs_ok; +} + +static __always_inline __be32 +nfsd4_encode_opaque(struct xdr_stream *xdr, const void *data, size_t size) +{ + size_t pad = xdr_pad_size(size); + __be32 *p; + + p = xdr_reserve_space(xdr, XDR_UNIT + xdr_align_size(size)); + if (unlikely(p == NULL)) + return nfserr_resource; + *p++ = cpu_to_be32(size); + memcpy(p, data, size); + if (pad) + memset((char *)p + size, 0, pad); + return nfs_ok; +} + +#define nfsd4_encode_component4(x, d, s) nfsd4_encode_opaque(x, d, s) + static __be32 nfsd4_encode_nfs_fh4(struct xdr_stream *xdr, const struct knfsd_fh *fh_handle) { @@ -4285,21 +4378,16 @@ out: static bool nfsd4_setup_notify_entry4(struct notify_entry4 *ne, struct xdr_stream *xdr, struct dentry *dentry, struct nfs4_delegation *dp, - struct nfsd_file *nf, char *name, u32 namelen) + struct nfsd_file *nf, char *name, u32 namelen, + u32 *attrmask) { struct path path = nf->nf_file->f_path; struct nfsd4_fattr_args args = { }; const u32 *reqmask; - uint32_t *attrmask; __be32 status; bool parent; int ret; - /* Reserve space for attrmask */ - attrmask = xdr_reserve_space(xdr, 3 * sizeof(uint32_t)); - if (!attrmask) - return false; - ne->ne_file.data = name; ne->ne_file.len = namelen; ne->ne_attrs.attrmask.element = attrmask; @@ -4383,6 +4471,7 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event * struct nfs4_delegation *dp, struct nfsd_file *nf, u32 *notify_mask) { + u32 attrmask[3][3] = { }; u8 *p = NULL; *notify_mask = 0; @@ -4391,7 +4480,8 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event * struct notify_remove4 nr = { }; if (!nfsd4_setup_notify_entry4(&nr.nrm_old_entry, xdr, nne->ne_dentry, dp, - nf, nne->ne_name, nne->ne_namelen)) + nf, nne->ne_name, nne->ne_namelen, + attrmask[0])) goto out_err; p = (u8 *)xdr->p; if (!xdrgen_encode_notify_remove4(xdr, &nr)) @@ -4402,14 +4492,16 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event * struct notify_remove4 old = { }; if (!nfsd4_setup_notify_entry4(&na.nad_new_entry, xdr, nne->ne_dentry, dp, - nf, nne->ne_name, nne->ne_namelen)) + nf, nne->ne_name, nne->ne_namelen, + attrmask[0])) goto out_err; /* If a file was overwritten, report it in nad_old_entry */ if (nne->ne_target) { if (!nfsd4_setup_notify_entry4(&old.nrm_old_entry, xdr, NULL, dp, nf, - nne->ne_name, nne->ne_namelen)) + nne->ne_name, nne->ne_namelen, + attrmask[1])) goto out_err; na.nad_old_entry.count = 1; na.nad_old_entry.element = &old; @@ -4428,19 +4520,19 @@ u8 *nfsd4_encode_notify_event(struct xdr_stream *xdr, struct nfsd_notify_event * /* Don't send any attributes in the old_entry since they're the same in new */ if (!nfsd4_setup_notify_entry4(&nr.nrn_old_entry.nrm_old_entry, xdr, NULL, dp, nf, nne->ne_name, - nne->ne_namelen)) + nne->ne_namelen, attrmask[0])) goto out_err; if (!nfsd4_setup_notify_entry4(&nr.nrn_new_entry.nad_new_entry, xdr, nne->ne_dentry, dp, nf, newname, - nne->ne_newnamelen)) + nne->ne_newnamelen, attrmask[1])) goto out_err; /* If a file was overwritten, report it in nad_old_entry */ if (nne->ne_target) { if (!nfsd4_setup_notify_entry4(&old.nrm_old_entry, xdr, NULL, dp, nf, newname, - nne->ne_newnamelen)) + nne->ne_newnamelen, attrmask[2])) goto out_err; nr.nrn_new_entry.nad_old_entry.count = 1; nr.nrn_new_entry.nad_old_entry.element = &old; @@ -4476,11 +4568,12 @@ u8 *nfsd4_encode_dir_attr_change(struct xdr_stream *xdr, struct nfs4_delegation { struct dentry *dentry = nf->nf_file->f_path.dentry; struct notify_attr4 na = { }; + u32 attrmask[3] = { }; u8 *p; /* RFC 8881 s10.4.3: ne_file must be a zero-length string for dir attrs */ if (!nfsd4_setup_notify_entry4(&na.na_changed_entry, xdr, - dentry, dp, nf, "", 0)) + dentry, dp, nf, "", 0, attrmask)) return ERR_PTR(-ENOBUFS); /* No requested attributes to report; omit the event */ @@ -4574,8 +4667,6 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name, * directly from the mountpoint dentry. */ if (nfsd_mountpoint(dentry, exp)) { - int err; - if (!(exp->ex_flags & NFSEXP_V4ROOT) && !attributes_need_mount(cd->rd_bmval)) { ignore_crossmnt = 1; @@ -4586,12 +4677,7 @@ nfsd4_encode_entry4_fattr(struct nfsd4_readdir *cd, const char *name, * Different "."/".." handling? Something else? * At least, add a comment here to explain.... */ - err = nfsd_cross_mnt(cd->rd_rqstp, &dentry, &exp); - if (err) { - nfserr = nfserrno(err); - goto out_put; - } - nfserr = check_nfsd_access(exp, cd->rd_rqstp, false); + nfserr = nfsd_cross_mnt(cd->rd_rqstp, &dentry, &exp); if (nfserr) goto out_put; crossed = true; diff --git a/fs/nfsd/nfscache.c b/fs/nfsd/nfscache.c index c7db532c8523..80364b91331a 100644 --- a/fs/nfsd/nfscache.c +++ b/fs/nfsd/nfscache.c @@ -19,6 +19,7 @@ #include <net/checksum.h> #include "nfsd.h" +#include "nfserr.h" #include "netns.h" #include "stats.h" #include "cache.h" diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 5abb2d4274c9..c0f10517470b 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -23,6 +23,8 @@ #include "idmap.h" #include "nfsd.h" +#include "nfserr.h" +#include "nfs4ctl.h" #include "netns.h" #include "stats.h" #include "cache.h" diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 76a69d9a4e73..64315890eef5 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -15,7 +15,6 @@ #include <linux/nfs.h> #include <linux/nfs2.h> #include <linux/nfs3.h> -#include <linux/nfs4.h> #include <linux/sunrpc/svc.h> #include <linux/sunrpc/svc_xprt.h> @@ -147,49 +146,7 @@ extern u64 nfsd_io_cache_write __read_mostly; extern int nfsd_max_blksize; -static inline int nfsd_v4client(struct svc_rqst *rq) -{ - return rq && rq->rq_prog == NFS_PROGRAM && rq->rq_vers == 4; -} - -/* - * NFSv4 State - */ -#ifdef CONFIG_NFSD_V4 -extern unsigned long max_delegations; -int nfsd4_init_slabs(void); -void nfsd4_free_slabs(void); -int nfs4_state_start(void); -int nfs4_state_start_net(struct net *net); -void nfs4_state_shutdown(void); -void nfs4_state_shutdown_net(struct net *net); -int nfs4_reset_recoverydir(char *recdir); -char * nfs4_recoverydir(void); -bool nfsd4_spo_must_allow(struct svc_rqst *rqstp); -int nfsd4_create_laundry_wq(void); -void nfsd4_destroy_laundry_wq(void); -bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode); -#else -static inline int nfsd4_init_slabs(void) { return 0; } -static inline void nfsd4_free_slabs(void) { } -static inline int nfs4_state_start(void) { return 0; } -static inline int nfs4_state_start_net(struct net *net) { return 0; } -static inline void nfs4_state_shutdown(void) { } -static inline void nfs4_state_shutdown_net(struct net *net) { } -static inline int nfs4_reset_recoverydir(char *recdir) { return 0; } -static inline char * nfs4_recoverydir(void) {return NULL; } -static inline bool nfsd4_spo_must_allow(struct svc_rqst *rqstp) -{ - return false; -} -static inline int nfsd4_create_laundry_wq(void) { return 0; }; -static inline void nfsd4_destroy_laundry_wq(void) {}; -static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, - struct inode *inode) -{ - return false; -} -#endif +bool nfsd_v4client(struct svc_rqst *rqstp); /* * lockd binding @@ -198,195 +155,4 @@ void nfsd_lockd_init(void); void nfsd_lockd_shutdown(void); -/* - * These macros provide pre-xdr'ed values for faster operation. - */ -#define nfs_ok cpu_to_be32(NFS_OK) -#define nfserr_perm cpu_to_be32(NFSERR_PERM) -#define nfserr_noent cpu_to_be32(NFSERR_NOENT) -#define nfserr_io cpu_to_be32(NFSERR_IO) -#define nfserr_nxio cpu_to_be32(NFSERR_NXIO) -#define nfserr_acces cpu_to_be32(NFSERR_ACCES) -#define nfserr_exist cpu_to_be32(NFSERR_EXIST) -#define nfserr_xdev cpu_to_be32(NFSERR_XDEV) -#define nfserr_nodev cpu_to_be32(NFSERR_NODEV) -#define nfserr_notdir cpu_to_be32(NFSERR_NOTDIR) -#define nfserr_isdir cpu_to_be32(NFSERR_ISDIR) -#define nfserr_inval cpu_to_be32(NFSERR_INVAL) -#define nfserr_fbig cpu_to_be32(NFSERR_FBIG) -#define nfserr_nospc cpu_to_be32(NFSERR_NOSPC) -#define nfserr_rofs cpu_to_be32(NFSERR_ROFS) -#define nfserr_mlink cpu_to_be32(NFSERR_MLINK) -#define nfserr_nametoolong cpu_to_be32(NFSERR_NAMETOOLONG) -#define nfserr_notempty cpu_to_be32(NFSERR_NOTEMPTY) -#define nfserr_dquot cpu_to_be32(NFSERR_DQUOT) -#define nfserr_stale cpu_to_be32(NFSERR_STALE) -#define nfserr_remote cpu_to_be32(NFSERR_REMOTE) -#define nfserr_wflush cpu_to_be32(NFSERR_WFLUSH) -#define nfserr_badhandle cpu_to_be32(NFSERR_BADHANDLE) -#define nfserr_notsync cpu_to_be32(NFSERR_NOT_SYNC) -#define nfserr_badcookie cpu_to_be32(NFSERR_BAD_COOKIE) -#define nfserr_notsupp cpu_to_be32(NFSERR_NOTSUPP) -#define nfserr_toosmall cpu_to_be32(NFSERR_TOOSMALL) -#define nfserr_serverfault cpu_to_be32(NFSERR_SERVERFAULT) -#define nfserr_badtype cpu_to_be32(NFSERR_BADTYPE) -#define nfserr_jukebox cpu_to_be32(NFSERR_JUKEBOX) -#define nfserr_denied cpu_to_be32(NFSERR_DENIED) -#define nfserr_deadlock cpu_to_be32(NFSERR_DEADLOCK) -#define nfserr_expired cpu_to_be32(NFSERR_EXPIRED) -#define nfserr_bad_cookie cpu_to_be32(NFSERR_BAD_COOKIE) -#define nfserr_same cpu_to_be32(NFSERR_SAME) -#define nfserr_clid_inuse cpu_to_be32(NFSERR_CLID_INUSE) -#define nfserr_stale_clientid cpu_to_be32(NFSERR_STALE_CLIENTID) -#define nfserr_resource cpu_to_be32(NFSERR_RESOURCE) -#define nfserr_moved cpu_to_be32(NFSERR_MOVED) -#define nfserr_nofilehandle cpu_to_be32(NFSERR_NOFILEHANDLE) -#define nfserr_minor_vers_mismatch cpu_to_be32(NFSERR_MINOR_VERS_MISMATCH) -#define nfserr_share_denied cpu_to_be32(NFSERR_SHARE_DENIED) -#define nfserr_stale_stateid cpu_to_be32(NFSERR_STALE_STATEID) -#define nfserr_old_stateid cpu_to_be32(NFSERR_OLD_STATEID) -#define nfserr_bad_stateid cpu_to_be32(NFSERR_BAD_STATEID) -#define nfserr_bad_seqid cpu_to_be32(NFSERR_BAD_SEQID) -#define nfserr_symlink cpu_to_be32(NFSERR_SYMLINK) -#define nfserr_not_same cpu_to_be32(NFSERR_NOT_SAME) -#define nfserr_lock_range cpu_to_be32(NFSERR_LOCK_RANGE) -#define nfserr_restorefh cpu_to_be32(NFSERR_RESTOREFH) -#define nfserr_attrnotsupp cpu_to_be32(NFSERR_ATTRNOTSUPP) -#define nfserr_bad_xdr cpu_to_be32(NFSERR_BAD_XDR) -#define nfserr_openmode cpu_to_be32(NFSERR_OPENMODE) -#define nfserr_badowner cpu_to_be32(NFSERR_BADOWNER) -#define nfserr_locks_held cpu_to_be32(NFSERR_LOCKS_HELD) -#define nfserr_op_illegal cpu_to_be32(NFSERR_OP_ILLEGAL) -#define nfserr_grace cpu_to_be32(NFSERR_GRACE) -#define nfserr_no_grace cpu_to_be32(NFSERR_NO_GRACE) -#define nfserr_reclaim_bad cpu_to_be32(NFSERR_RECLAIM_BAD) -#define nfserr_badname cpu_to_be32(NFSERR_BADNAME) -#define nfserr_admin_revoked cpu_to_be32(NFS4ERR_ADMIN_REVOKED) -#define nfserr_cb_path_down cpu_to_be32(NFSERR_CB_PATH_DOWN) -#define nfserr_locked cpu_to_be32(NFSERR_LOCKED) -#define nfserr_wrongsec cpu_to_be32(NFSERR_WRONGSEC) -#define nfserr_delay cpu_to_be32(NFS4ERR_DELAY) -#define nfserr_badiomode cpu_to_be32(NFS4ERR_BADIOMODE) -#define nfserr_badlayout cpu_to_be32(NFS4ERR_BADLAYOUT) -#define nfserr_bad_session_digest cpu_to_be32(NFS4ERR_BAD_SESSION_DIGEST) -#define nfserr_badsession cpu_to_be32(NFS4ERR_BADSESSION) -#define nfserr_badslot cpu_to_be32(NFS4ERR_BADSLOT) -#define nfserr_complete_already cpu_to_be32(NFS4ERR_COMPLETE_ALREADY) -#define nfserr_conn_not_bound_to_session cpu_to_be32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION) -#define nfserr_deleg_already_wanted cpu_to_be32(NFS4ERR_DELEG_ALREADY_WANTED) -#define nfserr_back_chan_busy cpu_to_be32(NFS4ERR_BACK_CHAN_BUSY) -#define nfserr_layouttrylater cpu_to_be32(NFS4ERR_LAYOUTTRYLATER) -#define nfserr_layoutunavailable cpu_to_be32(NFS4ERR_LAYOUTUNAVAILABLE) -#define nfserr_nomatching_layout cpu_to_be32(NFS4ERR_NOMATCHING_LAYOUT) -#define nfserr_recallconflict cpu_to_be32(NFS4ERR_RECALLCONFLICT) -#define nfserr_unknown_layouttype cpu_to_be32(NFS4ERR_UNKNOWN_LAYOUTTYPE) -#define nfserr_seq_misordered cpu_to_be32(NFS4ERR_SEQ_MISORDERED) -#define nfserr_sequence_pos cpu_to_be32(NFS4ERR_SEQUENCE_POS) -#define nfserr_req_too_big cpu_to_be32(NFS4ERR_REQ_TOO_BIG) -#define nfserr_rep_too_big cpu_to_be32(NFS4ERR_REP_TOO_BIG) -#define nfserr_rep_too_big_to_cache cpu_to_be32(NFS4ERR_REP_TOO_BIG_TO_CACHE) -#define nfserr_retry_uncached_rep cpu_to_be32(NFS4ERR_RETRY_UNCACHED_REP) -#define nfserr_unsafe_compound cpu_to_be32(NFS4ERR_UNSAFE_COMPOUND) -#define nfserr_too_many_ops cpu_to_be32(NFS4ERR_TOO_MANY_OPS) -#define nfserr_op_not_in_session cpu_to_be32(NFS4ERR_OP_NOT_IN_SESSION) -#define nfserr_hash_alg_unsupp cpu_to_be32(NFS4ERR_HASH_ALG_UNSUPP) -#define nfserr_clientid_busy cpu_to_be32(NFS4ERR_CLIENTID_BUSY) -#define nfserr_pnfs_io_hole cpu_to_be32(NFS4ERR_PNFS_IO_HOLE) -#define nfserr_seq_false_retry cpu_to_be32(NFS4ERR_SEQ_FALSE_RETRY) -#define nfserr_bad_high_slot cpu_to_be32(NFS4ERR_BAD_HIGH_SLOT) -#define nfserr_deadsession cpu_to_be32(NFS4ERR_DEADSESSION) -#define nfserr_encr_alg_unsupp cpu_to_be32(NFS4ERR_ENCR_ALG_UNSUPP) -#define nfserr_pnfs_no_layout cpu_to_be32(NFS4ERR_PNFS_NO_LAYOUT) -#define nfserr_not_only_op cpu_to_be32(NFS4ERR_NOT_ONLY_OP) -#define nfserr_wrong_cred cpu_to_be32(NFS4ERR_WRONG_CRED) -#define nfserr_wrong_type cpu_to_be32(NFS4ERR_WRONG_TYPE) -#define nfserr_dirdeleg_unavail cpu_to_be32(NFS4ERR_DIRDELEG_UNAVAIL) -#define nfserr_reject_deleg cpu_to_be32(NFS4ERR_REJECT_DELEG) -#define nfserr_returnconflict cpu_to_be32(NFS4ERR_RETURNCONFLICT) -#define nfserr_deleg_revoked cpu_to_be32(NFS4ERR_DELEG_REVOKED) -#define nfserr_partner_notsupp cpu_to_be32(NFS4ERR_PARTNER_NOTSUPP) -#define nfserr_partner_no_auth cpu_to_be32(NFS4ERR_PARTNER_NO_AUTH) -#define nfserr_union_notsupp cpu_to_be32(NFS4ERR_UNION_NOTSUPP) -#define nfserr_offload_denied cpu_to_be32(NFS4ERR_OFFLOAD_DENIED) -#define nfserr_wrong_lfs cpu_to_be32(NFS4ERR_WRONG_LFS) -#define nfserr_badlabel cpu_to_be32(NFS4ERR_BADLABEL) -#define nfserr_file_open cpu_to_be32(NFS4ERR_FILE_OPEN) -#define nfserr_xattr2big cpu_to_be32(NFS4ERR_XATTR2BIG) -#define nfserr_noxattr cpu_to_be32(NFS4ERR_NOXATTR) - -/* - * Error codes for internal use. These are based at an impossible - * nfsstat4 value so that, once converted to be32, they cannot conflict - * with any value defined by the protocol (compare the nlm__int__* codes - * in fs/lockd/lockd.h). - */ -enum { -/* end-of-file indicator in readdir */ - NFSERR_EOF = 30000, -#define nfserr_eof cpu_to_be32(NFSERR_EOF) - -/* replay detected */ - NFSERR_REPLAY_ME, -#define nfserr_replay_me cpu_to_be32(NFSERR_REPLAY_ME) - -/* nfs41 replay detected */ - NFSERR_REPLAY_CACHE, -#define nfserr_replay_cache cpu_to_be32(NFSERR_REPLAY_CACHE) - -/* symlink found where dir expected - handled differently to - * other symlink found errors by NFSv3. - */ - NFSERR_SYMLINK_NOT_DIR, -#define nfserr_symlink_not_dir cpu_to_be32(NFSERR_SYMLINK_NOT_DIR) -}; - -#ifdef CONFIG_NFSD_V4 - -/* before processing a COMPOUND operation, we have to check that there - * is enough space in the buffer for XDR encode to succeed. otherwise, - * we might process an operation with side effects, and be unable to - * tell the client that the operation succeeded. - * - * COMPOUND_SLACK_SPACE - this is the minimum bytes of buffer space - * needed to encode an "ordinary" _successful_ operation. (GETATTR, - * READ, READDIR, and READLINK have their own buffer checks.) if we - * fall below this level, we fail the next operation with NFS4ERR_RESOURCE. - * - * COMPOUND_ERR_SLACK_SPACE - this is the minimum bytes of buffer space - * needed to encode an operation which has failed with NFS4ERR_RESOURCE. - * care is taken to ensure that we never fall below this level for any - * reason. - */ -#define COMPOUND_SLACK_SPACE 140 /* OP_GETFH */ -#define COMPOUND_ERR_SLACK_SPACE 16 /* OP_SETATTR */ - -#define NFSD_LAUNDROMAT_MINTIMEOUT 1 /* seconds */ -#define NFSD_COURTESY_CLIENT_TIMEOUT (24 * 60 * 60) /* seconds */ -#define NFSD_CLIENT_MAX_TRIM_PER_RUN 128 -#define NFS4_CLIENTS_PER_GB 1024 -#define NFSD_DELEGRETURN_TIMEOUT (HZ / 34) /* 30ms */ -#define NFSD_CB_GETATTR_TIMEOUT NFSD_DELEGRETURN_TIMEOUT - -extern int nfsd4_is_junction(struct dentry *dentry); -extern int register_cld_notifier(void); -extern void unregister_cld_notifier(void); -#ifdef CONFIG_NFSD_V4_2_INTER_SSC -extern void nfsd4_ssc_init_umount_work(struct nfsd_net *nn); -#endif - -extern void nfsd4_init_leases_net(struct nfsd_net *nn); - -#else /* CONFIG_NFSD_V4 */ -static inline int nfsd4_is_junction(struct dentry *dentry) -{ - return 0; -} - -static inline void nfsd4_init_leases_net(struct nfsd_net *nn) { }; - -#define register_cld_notifier() 0 -#define unregister_cld_notifier() do { } while(0) - -#endif /* CONFIG_NFSD_V4 */ - #endif /* LINUX_NFSD_NFSD_H */ diff --git a/fs/nfsd/nfserr.h b/fs/nfsd/nfserr.h new file mode 100644 index 000000000000..9b9df7aab220 --- /dev/null +++ b/fs/nfsd/nfserr.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Pre-xdr'ed nfsd error values and nfsd-internal error codes. + * + * Separated from nfsd.h so that nfsd.h itself does not have to pull + * in <linux/nfs4.h>: the NFS4ERR_* values used below are the only + * reason that include was needed. + */ + +#ifndef LINUX_NFSD_NFSERR_H +#define LINUX_NFSD_NFSERR_H + +#include <linux/nfs.h> +#include <linux/nfs4.h> + +/* + * These macros provide pre-xdr'ed values for faster operation. + */ +#define nfs_ok cpu_to_be32(NFS_OK) +#define nfserr_perm cpu_to_be32(NFSERR_PERM) +#define nfserr_noent cpu_to_be32(NFSERR_NOENT) +#define nfserr_io cpu_to_be32(NFSERR_IO) +#define nfserr_nxio cpu_to_be32(NFSERR_NXIO) +#define nfserr_acces cpu_to_be32(NFSERR_ACCES) +#define nfserr_exist cpu_to_be32(NFSERR_EXIST) +#define nfserr_xdev cpu_to_be32(NFSERR_XDEV) +#define nfserr_nodev cpu_to_be32(NFSERR_NODEV) +#define nfserr_notdir cpu_to_be32(NFSERR_NOTDIR) +#define nfserr_isdir cpu_to_be32(NFSERR_ISDIR) +#define nfserr_inval cpu_to_be32(NFSERR_INVAL) +#define nfserr_fbig cpu_to_be32(NFSERR_FBIG) +#define nfserr_nospc cpu_to_be32(NFSERR_NOSPC) +#define nfserr_rofs cpu_to_be32(NFSERR_ROFS) +#define nfserr_mlink cpu_to_be32(NFSERR_MLINK) +#define nfserr_nametoolong cpu_to_be32(NFSERR_NAMETOOLONG) +#define nfserr_notempty cpu_to_be32(NFSERR_NOTEMPTY) +#define nfserr_dquot cpu_to_be32(NFSERR_DQUOT) +#define nfserr_stale cpu_to_be32(NFSERR_STALE) +#define nfserr_remote cpu_to_be32(NFSERR_REMOTE) +#define nfserr_wflush cpu_to_be32(NFSERR_WFLUSH) +#define nfserr_badhandle cpu_to_be32(NFSERR_BADHANDLE) +#define nfserr_notsync cpu_to_be32(NFSERR_NOT_SYNC) +#define nfserr_badcookie cpu_to_be32(NFSERR_BAD_COOKIE) +#define nfserr_notsupp cpu_to_be32(NFSERR_NOTSUPP) +#define nfserr_toosmall cpu_to_be32(NFSERR_TOOSMALL) +#define nfserr_serverfault cpu_to_be32(NFSERR_SERVERFAULT) +#define nfserr_badtype cpu_to_be32(NFSERR_BADTYPE) +#define nfserr_jukebox cpu_to_be32(NFSERR_JUKEBOX) +#define nfserr_denied cpu_to_be32(NFSERR_DENIED) +#define nfserr_deadlock cpu_to_be32(NFSERR_DEADLOCK) +#define nfserr_expired cpu_to_be32(NFSERR_EXPIRED) +#define nfserr_bad_cookie cpu_to_be32(NFSERR_BAD_COOKIE) +#define nfserr_same cpu_to_be32(NFSERR_SAME) +#define nfserr_clid_inuse cpu_to_be32(NFSERR_CLID_INUSE) +#define nfserr_stale_clientid cpu_to_be32(NFSERR_STALE_CLIENTID) +#define nfserr_resource cpu_to_be32(NFSERR_RESOURCE) +#define nfserr_moved cpu_to_be32(NFSERR_MOVED) +#define nfserr_nofilehandle cpu_to_be32(NFSERR_NOFILEHANDLE) +#define nfserr_minor_vers_mismatch cpu_to_be32(NFSERR_MINOR_VERS_MISMATCH) +#define nfserr_share_denied cpu_to_be32(NFSERR_SHARE_DENIED) +#define nfserr_stale_stateid cpu_to_be32(NFSERR_STALE_STATEID) +#define nfserr_old_stateid cpu_to_be32(NFSERR_OLD_STATEID) +#define nfserr_bad_stateid cpu_to_be32(NFSERR_BAD_STATEID) +#define nfserr_bad_seqid cpu_to_be32(NFSERR_BAD_SEQID) +#define nfserr_symlink cpu_to_be32(NFSERR_SYMLINK) +#define nfserr_not_same cpu_to_be32(NFSERR_NOT_SAME) +#define nfserr_lock_range cpu_to_be32(NFSERR_LOCK_RANGE) +#define nfserr_restorefh cpu_to_be32(NFSERR_RESTOREFH) +#define nfserr_attrnotsupp cpu_to_be32(NFSERR_ATTRNOTSUPP) +#define nfserr_bad_xdr cpu_to_be32(NFSERR_BAD_XDR) +#define nfserr_openmode cpu_to_be32(NFSERR_OPENMODE) +#define nfserr_badowner cpu_to_be32(NFSERR_BADOWNER) +#define nfserr_locks_held cpu_to_be32(NFSERR_LOCKS_HELD) +#define nfserr_op_illegal cpu_to_be32(NFSERR_OP_ILLEGAL) +#define nfserr_grace cpu_to_be32(NFSERR_GRACE) +#define nfserr_no_grace cpu_to_be32(NFSERR_NO_GRACE) +#define nfserr_reclaim_bad cpu_to_be32(NFSERR_RECLAIM_BAD) +#define nfserr_badname cpu_to_be32(NFSERR_BADNAME) +#define nfserr_admin_revoked cpu_to_be32(NFS4ERR_ADMIN_REVOKED) +#define nfserr_cb_path_down cpu_to_be32(NFSERR_CB_PATH_DOWN) +#define nfserr_locked cpu_to_be32(NFSERR_LOCKED) +#define nfserr_wrongsec cpu_to_be32(NFSERR_WRONGSEC) +#define nfserr_delay cpu_to_be32(NFS4ERR_DELAY) +#define nfserr_badiomode cpu_to_be32(NFS4ERR_BADIOMODE) +#define nfserr_badlayout cpu_to_be32(NFS4ERR_BADLAYOUT) +#define nfserr_bad_session_digest cpu_to_be32(NFS4ERR_BAD_SESSION_DIGEST) +#define nfserr_badsession cpu_to_be32(NFS4ERR_BADSESSION) +#define nfserr_badslot cpu_to_be32(NFS4ERR_BADSLOT) +#define nfserr_complete_already cpu_to_be32(NFS4ERR_COMPLETE_ALREADY) +#define nfserr_conn_not_bound_to_session cpu_to_be32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION) +#define nfserr_deleg_already_wanted cpu_to_be32(NFS4ERR_DELEG_ALREADY_WANTED) +#define nfserr_back_chan_busy cpu_to_be32(NFS4ERR_BACK_CHAN_BUSY) +#define nfserr_layouttrylater cpu_to_be32(NFS4ERR_LAYOUTTRYLATER) +#define nfserr_layoutunavailable cpu_to_be32(NFS4ERR_LAYOUTUNAVAILABLE) +#define nfserr_nomatching_layout cpu_to_be32(NFS4ERR_NOMATCHING_LAYOUT) +#define nfserr_recallconflict cpu_to_be32(NFS4ERR_RECALLCONFLICT) +#define nfserr_unknown_layouttype cpu_to_be32(NFS4ERR_UNKNOWN_LAYOUTTYPE) +#define nfserr_seq_misordered cpu_to_be32(NFS4ERR_SEQ_MISORDERED) +#define nfserr_sequence_pos cpu_to_be32(NFS4ERR_SEQUENCE_POS) +#define nfserr_req_too_big cpu_to_be32(NFS4ERR_REQ_TOO_BIG) +#define nfserr_rep_too_big cpu_to_be32(NFS4ERR_REP_TOO_BIG) +#define nfserr_rep_too_big_to_cache cpu_to_be32(NFS4ERR_REP_TOO_BIG_TO_CACHE) +#define nfserr_retry_uncached_rep cpu_to_be32(NFS4ERR_RETRY_UNCACHED_REP) +#define nfserr_unsafe_compound cpu_to_be32(NFS4ERR_UNSAFE_COMPOUND) +#define nfserr_too_many_ops cpu_to_be32(NFS4ERR_TOO_MANY_OPS) +#define nfserr_op_not_in_session cpu_to_be32(NFS4ERR_OP_NOT_IN_SESSION) +#define nfserr_hash_alg_unsupp cpu_to_be32(NFS4ERR_HASH_ALG_UNSUPP) +#define nfserr_clientid_busy cpu_to_be32(NFS4ERR_CLIENTID_BUSY) +#define nfserr_pnfs_io_hole cpu_to_be32(NFS4ERR_PNFS_IO_HOLE) +#define nfserr_seq_false_retry cpu_to_be32(NFS4ERR_SEQ_FALSE_RETRY) +#define nfserr_bad_high_slot cpu_to_be32(NFS4ERR_BAD_HIGH_SLOT) +#define nfserr_deadsession cpu_to_be32(NFS4ERR_DEADSESSION) +#define nfserr_encr_alg_unsupp cpu_to_be32(NFS4ERR_ENCR_ALG_UNSUPP) +#define nfserr_pnfs_no_layout cpu_to_be32(NFS4ERR_PNFS_NO_LAYOUT) +#define nfserr_not_only_op cpu_to_be32(NFS4ERR_NOT_ONLY_OP) +#define nfserr_wrong_cred cpu_to_be32(NFS4ERR_WRONG_CRED) +#define nfserr_wrong_type cpu_to_be32(NFS4ERR_WRONG_TYPE) +#define nfserr_dirdeleg_unavail cpu_to_be32(NFS4ERR_DIRDELEG_UNAVAIL) +#define nfserr_reject_deleg cpu_to_be32(NFS4ERR_REJECT_DELEG) +#define nfserr_returnconflict cpu_to_be32(NFS4ERR_RETURNCONFLICT) +#define nfserr_deleg_revoked cpu_to_be32(NFS4ERR_DELEG_REVOKED) +#define nfserr_partner_notsupp cpu_to_be32(NFS4ERR_PARTNER_NOTSUPP) +#define nfserr_partner_no_auth cpu_to_be32(NFS4ERR_PARTNER_NO_AUTH) +#define nfserr_union_notsupp cpu_to_be32(NFS4ERR_UNION_NOTSUPP) +#define nfserr_offload_denied cpu_to_be32(NFS4ERR_OFFLOAD_DENIED) +#define nfserr_wrong_lfs cpu_to_be32(NFS4ERR_WRONG_LFS) +#define nfserr_badlabel cpu_to_be32(NFS4ERR_BADLABEL) +#define nfserr_file_open cpu_to_be32(NFS4ERR_FILE_OPEN) +#define nfserr_xattr2big cpu_to_be32(NFS4ERR_XATTR2BIG) +#define nfserr_noxattr cpu_to_be32(NFS4ERR_NOXATTR) + +/* + * Error codes for internal use. These are based at an impossible + * nfsstat4 value so that, once converted to be32, they cannot conflict + * with any value defined by the protocol (compare the nlm__int__* codes + * in fs/lockd/lockd.h). + */ +enum { +/* end-of-file indicator in readdir */ + NFSERR_EOF = 30000, +#define nfserr_eof cpu_to_be32(NFSERR_EOF) + +/* replay detected */ + NFSERR_REPLAY_ME, +#define nfserr_replay_me cpu_to_be32(NFSERR_REPLAY_ME) + +/* nfs41 replay detected */ + NFSERR_REPLAY_CACHE, +#define nfserr_replay_cache cpu_to_be32(NFSERR_REPLAY_CACHE) + +/* symlink found where dir expected - handled differently to + * other symlink found errors by NFSv3. + */ + NFSERR_SYMLINK_NOT_DIR, +#define nfserr_symlink_not_dir cpu_to_be32(NFSERR_SYMLINK_NOT_DIR) +}; + +#endif /* LINUX_NFSD_NFSERR_H */ diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c index c7c60c35bdfc..b1f3c22af525 100644 --- a/fs/nfsd/nfsfh.c +++ b/fs/nfsd/nfsfh.c @@ -13,6 +13,7 @@ #include <linux/sunrpc/svcauth_gss.h> #include <crypto/utils.h> #include "nfsd.h" +#include "nfserr.h" #include "netns.h" #include "stats.h" #include "vfs.h" @@ -334,6 +335,8 @@ static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net, } switch (fhp->fh_maxsize) { + case NFSD_FHSIZE_UNSPEC: + break; case NFS4_FHSIZE: if (dentry->d_sb->s_export_op->flags & EXPORT_OP_NOATOMIC_ATTR) fhp->fh_no_atomic_attr = true; @@ -782,35 +785,54 @@ __be32 fh_getattr(const struct svc_fh *fhp, struct kstat *stat) AT_STATX_SYNC_AS_STAT)); } -/** - * fh_fill_pre_attrs - Fill in pre-op attributes - * @fhp: file handle to be updated - * - */ -__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp) +static __be32 __must_check __fh_fill_pre_attrs(struct svc_fh *fhp) { bool v4 = (fhp->fh_maxsize == NFS4_FHSIZE); - struct kstat stat; __be32 err; if (fhp->fh_no_wcc || fhp->fh_pre_saved) return nfs_ok; - err = fh_getattr(fhp, &stat); + err = fh_getattr(fhp, &fhp->fh_post_attr); if (err) return err; if (v4) - fhp->fh_pre_change = nfsd4_change_attribute(&stat); + fhp->fh_pre_change = fhp->fh_post_change = + nfsd4_change_attribute(&fhp->fh_post_attr); - fhp->fh_pre_mtime = stat.mtime; - fhp->fh_pre_ctime = stat.ctime; - fhp->fh_pre_size = stat.size; + fhp->fh_pre_mtime = fhp->fh_post_attr.mtime; + fhp->fh_pre_ctime = fhp->fh_post_attr.ctime; + fhp->fh_pre_size = fhp->fh_post_attr.size; fhp->fh_pre_saved = true; return nfs_ok; } /** + * fh_fill_pre_attrs - Fill in pre-op attributes + * @fhp: file handle to be updated + * + * Post-op attrs are filled and pre-op attrs are copied + * from there. The post-op attrs can later be replaced by + * fh_fill_post_attrs() or activated by fh_fill_post_noop(). + * + * The inode must be locked. + * + * Returns: error from vfs_getattr() which must be checked. + */ +__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp) +{ + lockdep_assert_held_write(&fhp->fh_dentry->d_inode->i_rwsem); + return __fh_fill_pre_attrs(fhp); +} + +__be32 __must_check fh_fill_pre_attrs_unlocked(struct svc_fh *fhp) +{ + fhp->fh_no_atomic_attr = true; + return __fh_fill_pre_attrs(fhp); +} + +/** * fh_fill_post_attrs - Fill in post-op attributes * @fhp: file handle to be updated * @@ -826,6 +848,9 @@ __be32 fh_fill_post_attrs(struct svc_fh *fhp) if (fhp->fh_post_saved) printk("nfsd: inode locked twice during operation.\n"); + if (!fhp->fh_no_atomic_attr) + lockdep_assert_held_write(&fhp->fh_dentry->d_inode->i_rwsem); + err = fh_getattr(fhp, &fhp->fh_post_attr); if (err) return err; @@ -837,29 +862,6 @@ __be32 fh_fill_post_attrs(struct svc_fh *fhp) return nfs_ok; } -/** - * fh_fill_both_attrs - Fill pre-op and post-op attributes - * @fhp: file handle to be updated - * - * This is used when the directory wasn't changed, but wcc attributes - * are needed anyway. - */ -__be32 __must_check fh_fill_both_attrs(struct svc_fh *fhp) -{ - __be32 err; - - err = fh_fill_post_attrs(fhp); - if (err) - return err; - - fhp->fh_pre_change = fhp->fh_post_change; - fhp->fh_pre_mtime = fhp->fh_post_attr.mtime; - fhp->fh_pre_ctime = fhp->fh_post_attr.ctime; - fhp->fh_pre_size = fhp->fh_post_attr.size; - fhp->fh_pre_saved = true; - return nfs_ok; -} - /* * Release a file handle. */ diff --git a/fs/nfsd/nfsfh.h b/fs/nfsd/nfsfh.h index cdeb5eea65a8..7d8e3f015307 100644 --- a/fs/nfsd/nfsfh.h +++ b/fs/nfsd/nfsfh.h @@ -246,6 +246,20 @@ fh_copy_shallow(struct knfsd_fh *dst, const struct knfsd_fh *src) memcpy(&dst->fh_raw, &src->fh_raw, src->fh_size); } +#define NFSD_FHSIZE_UNSPEC 0 + +/** + * fh_init - Prepare a file handle for fh_compose() or fh_verify() + * @fhp: File handle to initialize + * @maxsize: Largest file handle, in bytes, to build in @fhp + * + * @maxsize bounds the handle fh_compose() may build: NFS_FHSIZE, + * NFS3_FHSIZE, and NFS4_FHSIZE additionally select version-specific + * handling in fh_verify(). Callers that only verify an incoming + * handle pass NFSD_FHSIZE_UNSPEC, which cannot be composed. + * + * Return: @fhp + */ static __inline__ struct svc_fh * fh_init(struct svc_fh *fhp, int maxsize) { @@ -337,6 +351,18 @@ static inline void fh_clear_pre_post_attrs(struct svc_fh *fhp) u64 nfsd4_change_attribute(const struct kstat *stat); __be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp); +__be32 __must_check fh_fill_pre_attrs_unlocked(struct svc_fh *fhp); __be32 fh_fill_post_attrs(struct svc_fh *fhp); -__be32 __must_check fh_fill_both_attrs(struct svc_fh *fhp); + +/** + * fh_fill_post_noop - Copy pre attrs to post attrs + * @fhp: file handle to be updated + * + * This is used when the directory wasn't changed, but wcc attributes + * are needed anyway. + */ +static inline void fh_fill_post_noop(struct svc_fh *fhp) +{ + fhp->fh_post_saved = true; +} #endif /* _LINUX_NFSD_NFSFH_H */ diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index e2b5f8a241be..48541ef7644f 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -10,6 +10,7 @@ #include "cache.h" #include "xdr.h" #include "vfs.h" +#include "nfserr.h" #include "trace.h" #define NFSDDBG_FACILITY NFSDDBG_PROC @@ -265,7 +266,7 @@ nfsd_proc_write(struct svc_rqst *rqstp) fh_copy(&resp->fh, &argp->fh); resp->status = nfsd_write(rqstp, &resp->fh, argp->offset, - &argp->payload, &cnt, NFS_DATA_SYNC, NULL); + &argp->payload, &cnt, IOCB_DSYNC, NULL); if (resp->status == nfs_ok) resp->status = fh_getattr(&resp->fh, &resp->stat); else if (resp->status == nfserr_jukebox) @@ -291,6 +292,7 @@ nfsd_proc_create(struct svc_rqst *rqstp) struct nfsd_attrs attrs = { .na_iattr = attr, }; + struct svc_export *exp; struct inode *inode; struct dentry *dchild; int type, mode; @@ -319,8 +321,22 @@ nfsd_proc_create(struct svc_rqst *rqstp) resp->status = nfserrno(PTR_ERR(dchild)); goto out_write; } + /* + * If name exists we need to check for mountpoints + */ + exp = exp_get(dirfhp->fh_export); + if (d_is_reg(dchild) && + unlikely(nfsd_mountpoint(dchild, exp))) { + resp->status = nfsd_cross_mnt(rqstp, &dchild, &exp); + if (resp->status != nfs_ok) { + exp_put(exp); + goto out_unlock; + } + } + fh_init(newfhp, NFS_FHSIZE); - resp->status = fh_compose(newfhp, dirfhp->fh_export, dchild, dirfhp); + resp->status = fh_compose(newfhp, exp, dchild, dirfhp); + exp_put(exp); if (!resp->status && d_really_is_negative(dchild)) resp->status = nfserr_noent; if (resp->status) { diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 2edf716ea022..c04ef9d180ce 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -25,7 +25,10 @@ #include <net/addrconf.h> #include <net/ipv6.h> #include <net/net_namespace.h> + #include "nfsd.h" +#include "nfserr.h" +#include "nfs4ctl.h" #include "cache.h" #include "vfs.h" #include "netns.h" @@ -204,6 +207,11 @@ int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change return 0; } +bool nfsd_v4client(struct svc_rqst *rqstp) +{ + return rqstp && rqstp->rq_prog == NFS_PROGRAM && rqstp->rq_vers == 4; +} + bool nfsd_net_try_get(struct net *net) __must_hold(rcu) { struct nfsd_net *nn = net_generic(net, nfsd_net_id); diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c index 019f0cc971a7..0961c13d6ab1 100644 --- a/fs/nfsd/nfsxdr.c +++ b/fs/nfsd/nfsxdr.c @@ -8,6 +8,7 @@ #include <linux/filelock.h> #include "vfs.h" +#include "nfserr.h" #include "xdr.h" #include "auth.h" diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index 2d00a411c663..c65b604e29f1 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -45,6 +45,25 @@ #include "nfsfh.h" #include "nfsd.h" +/* + * Before processing a COMPOUND operation, we have to check that there + * is enough space in the buffer for XDR encode to succeed. otherwise, + * we might process an operation with side effects, and be unable to + * tell the client that the operation succeeded. + * + * COMPOUND_ERR_SLACK_SPACE - this is the minimum bytes of buffer space + * needed to encode an operation which has failed with NFS4ERR_RESOURCE. + * care is taken to ensure that we never fall below this level for any + * reason. + */ +#define COMPOUND_ERR_SLACK_SPACE 16 /* OP_SETATTR */ + +#define NFSD_LAUNDROMAT_MINTIMEOUT 1 /* seconds */ +#define NFSD_CLIENT_MAX_TRIM_PER_RUN 128 +#define NFS4_CLIENTS_PER_GB 1024 +#define NFSD_DELEGRETURN_TIMEOUT (HZ / 34) /* 30ms */ +#define NFSD_CB_GETATTR_TIMEOUT NFSD_DELEGRETURN_TIMEOUT + typedef struct { u32 cl_boot; u32 cl_id; @@ -126,10 +145,13 @@ struct nfs4_stid { #define SC_TYPE_COPY BIT(4) unsigned short sc_type; -/* nn->deleg_lock protects sc_status for delegation stateids. - * ->cl_lock protects sc_status for open and lock stateids. - * ->st_mutex also protect sc_status for open stateids. - * ->ls_lock protects sc_status for layout stateids. +/* + * nn->deleg_lock protects sc_status for hashed delegation stateids. + * ->cl_lock protects the bits set as one is disposed of + * (SC_STATUS_CLOSED, SC_STATUS_FREEABLE, SC_STATUS_FREED) and + * sc_status for open and lock stateids. ->st_mutex also protects + * sc_status for open stateids. ->ls_lock protects sc_status for + * layout stateids. */ /* * For an open stateid kept around *only* to process close replays. @@ -249,6 +271,7 @@ struct nfsd4_cb_notify { struct nfsd_notify_event *ncn_evt[NOTIFY4_EVENT_QUEUE_SIZE]; // list of events struct page *ncn_pages[NOTIFY4_PAGE_ARRAY_SIZE]; // for encoding struct notify4 *ncn_nf; // array of notify4's to be sent + u32 *ncn_masks; // host-order notify_mask backing for ncn_nf[] bool ncn_encode_err; // did encoding fail? struct nfsd4_callback ncn_cb; // notify4 callback }; @@ -270,7 +293,8 @@ struct nfsd4_cb_notify { * If the server attempts to recall a delegation and the client doesn't do so * before a timeout, the server may also revoke the delegation. In that case, * the object will either be destroyed (v4.0) or moved to a per-client list of - * revoked delegations (v4.1+). + * revoked delegations (v4.1+). A v4.1+ client that rejects the recall holds + * no record of the delegation, so the object is destroyed rather than listed. * * This object is a superset of the nfs4_stid. */ @@ -286,9 +310,19 @@ struct nfs4_delegation { int dl_retries; struct nfsd4_callback dl_recall; bool dl_recalled; + bool dl_recall_rejected; bool dl_written; bool dl_setattr; + /* Forward-channel slot that carried the granting request */ + struct { + u32 sessionid_seq; + u32 slotid; + u32 seqid; + bool valid; + bool retired_at_send; + } dl_recall_grant; + union { /* for CB_GETATTR */ struct nfs4_cb_fattr dl_cb_fattr; diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 8923a9910a08..f9131827d391 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -34,15 +34,14 @@ #include <linux/sunrpc/xdr.h> #include <linux/fileattr.h> -#include "xdr3.h" - #ifdef CONFIG_NFSD_V4 #include "acl.h" #include "idmap.h" -#include "xdr4.h" #endif /* CONFIG_NFSD_V4 */ #include "nfsd.h" +#include "nfserr.h" +#include "nfs4ctl.h" #include "netns.h" #include "stats.h" #include "vfs.h" @@ -63,7 +62,7 @@ u64 nfsd_io_cache_write __read_mostly = NFSD_IO_BUFFERED; * it's an error we don't expect, log it once and return nfserr_io. */ __be32 -nfserrno (int errno) +nfserrno(int errno) { static struct { __be32 nfserr; @@ -107,6 +106,8 @@ nfserrno (int errno) { nfserr_perm, -ENOKEY }, { nfserr_no_grace, -ENOGRACE}, { nfserr_io, -EBADMSG }, + { nfserr_symlink, -ELOOP }, + { nfserr_wrong_type, -EFTYPE }, }; int i; @@ -118,15 +119,15 @@ nfserrno (int errno) return nfserr_io; } -/* - * Called from nfsd_lookup and encode_dirent. Check if we have crossed +/* + * Called from nfsd_lookup and encode_dirent. Check if we have crossed * a mount point. - * Returns -EAGAIN or -ETIMEDOUT leaving *dpp and *expp unchanged, + * Returns an nfs error leaving *dpp and *expp unchanged, * or nfs_ok having possibly changed *dpp and *expp */ -int -nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, - struct svc_export **expp) +__be32 +nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, + struct svc_export **expp) { struct svc_export *exp = *expp, *exp2 = NULL; struct dentry *dentry = *dpp; @@ -134,6 +135,7 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, .dentry = dget(dentry)}; unsigned int follow_flags = 0; int err = 0; + __be32 nfserr = nfs_ok; if (exp->ex_flags & NFSEXP_CROSSMOUNT) follow_flags = LOOKUP_AUTOMOUNT; @@ -163,23 +165,28 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, err = 0; } else if (nfsd_v4client(rqstp) || (exp->ex_flags & NFSEXP_CROSSMOUNT) || EX_NOHIDE(exp2)) { - /* successfully crossed mount point */ - /* - * This is subtle: path.dentry is *not* on path.mnt - * at this point. The only reason we are safe is that - * original mnt is pinned down by exp, so we should - * put path *before* putting exp - */ - *dpp = path.dentry; - path.dentry = dentry; - *expp = exp2; - exp2 = exp; + nfserr = check_nfsd_access(exp, rqstp); + if (nfserr == nfs_ok) { + /* successfully crossed mount point */ + /* + * This is subtle: path.dentry is *not* on path.mnt + * at this point. The only reason we are safe is that + * original mnt is pinned down by exp, so we should + * put path *before* putting exp + */ + *dpp = path.dentry; + path.dentry = dentry; + *expp = exp2; + exp2 = exp; + } } out: path_put(&path); if (exp2) exp_put(exp2); - return err; + if (nfserr) + return nfserr; + return nfserrno(err); } static void follow_to_parent(struct path *path) @@ -277,10 +284,12 @@ nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp, if (IS_ERR(dentry)) goto out_nfserr; if (nfsd_mountpoint(dentry, exp)) { - host_err = nfsd_cross_mnt(rqstp, &dentry, &exp); - if (host_err) { + __be32 nfserr = nfsd_cross_mnt(rqstp, &dentry, &exp); + + if (nfserr) { dput(dentry); - goto out_nfserr; + exp_put(exp); + return nfserr; } } } @@ -327,9 +336,6 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name, err = nfsd_lookup_dentry(rqstp, fhp, name, len, &exp, &dentry); if (err) return err; - err = check_nfsd_access(exp, rqstp, false); - if (err) - goto out; /* * Note: we compose the file handle now, but as the * dentry may be negative, it may need to be updated. @@ -337,15 +343,26 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name, err = fh_compose(resfh, exp, dentry, fhp); if (!err && d_really_is_negative(dentry)) err = nfserr_noent; -out: + dput(dentry); exp_put(exp); return err; } -static void -commit_reset_write_verifier(struct nfsd_net *nn, struct svc_rqst *rqstp, - int err) +/** + * nfsd_maybe_reset_write_verifier - Reset the write verifier after an I/O error + * @nn: nfsd namespace holding the write verifier + * @rqstp: RPC transaction context + * @err: errno reported by the failed operation + * + * A write verifier reset tells clients that unstable data the server has + * already acknowledged might have been lost. Client response is to resend + * in-flight dirty data. + * + * Context: Process context. + */ +void nfsd_maybe_reset_write_verifier(struct nfsd_net *nn, + struct svc_rqst *rqstp, int err) { switch (err) { case -EAGAIN: @@ -682,56 +699,67 @@ int nfsd4_is_junction(struct dentry *dentry) return 1; } -static struct nfsd4_compound_state *nfsd4_get_cstate(struct svc_rqst *rqstp) +/** + * nfsd_clone_file_range - Clone a range of one file into another + * @src: file the range is cloned from + * @src_pos: offset in @src where the source range begins + * @dst: file the range is cloned into + * @dst_pos: offset in @dst where the destination range begins + * @count: length of the range, or zero to clone through end-of-file + * @since: receives @dst's writeback error state, sampled before the clone + * + * A caller that has to place the cloned data on durable storage passes + * @since to nfsd_clone_sync_range() once this call succeeds. Sampling + * happens here because a writeback error raised by the clone's own + * dirty pages has to fall inside the sampled interval. + * + * Context: Process context. + * Return: zero on success, or a negative errno + */ +int nfsd_clone_file_range(struct file *src, u64 src_pos, struct file *dst, + u64 dst_pos, u64 count, errseq_t *since) { - return &((struct nfsd4_compoundres *)rqstp->rq_resp)->cstate; + loff_t cloned; + + *since = READ_ONCE(dst->f_wb_err); + cloned = vfs_clone_file_range(src, src_pos, dst, dst_pos, count, 0); + if (cloned < 0) + return cloned; + if (count && cloned != count) + return -EINVAL; + return 0; } -__be32 nfsd4_clone_file_range(struct svc_rqst *rqstp, - struct nfsd_file *nf_src, u64 src_pos, - struct nfsd_file *nf_dst, u64 dst_pos, - u64 count, bool sync) +/** + * nfsd_clone_sync_range - Commit a cloned range to durable storage + * @src: file the range was cloned from, whose metadata is committed too + * @dst: file the range was cloned into + * @dst_pos: offset in @dst where the cloned range begins + * @count: length of the range, or zero if the clone ran to end-of-file + * @since: @dst's writeback error state as sampled by + * nfsd_clone_file_range() + * + * Context: Process context. + * Return: zero on success, or a negative errno + */ +int nfsd_clone_sync_range(struct file *src, struct file *dst, u64 dst_pos, + u64 count, errseq_t since) { - struct file *src = nf_src->nf_file; - struct file *dst = nf_dst->nf_file; - errseq_t since; - loff_t cloned; - __be32 ret = 0; + loff_t dst_end = count ? dst_pos + count - 1 : LLONG_MAX; + int status; - since = READ_ONCE(dst->f_wb_err); - cloned = vfs_clone_file_range(src, src_pos, dst, dst_pos, count, 0); - if (cloned < 0) { - ret = nfserrno(cloned); - goto out_err; - } - if (count && cloned != count) { - ret = nfserrno(-EINVAL); - goto out_err; - } - if (sync) { - loff_t dst_end = count ? dst_pos + count - 1 : LLONG_MAX; - int status = vfs_fsync_range(dst, dst_pos, dst_end, 0); - - if (!status) - status = filemap_check_wb_err(dst->f_mapping, since); - if (!status) - status = commit_inode_metadata(file_inode(src)); - if (status < 0) { - struct nfsd_net *nn = net_generic(nf_dst->nf_net, - nfsd_net_id); - - trace_nfsd_clone_file_range_err(rqstp, - &nfsd4_get_cstate(rqstp)->save_fh, - src_pos, - &nfsd4_get_cstate(rqstp)->current_fh, - dst_pos, - count, status); - commit_reset_write_verifier(nn, rqstp, status); - ret = nfserrno(status); - } + status = vfs_fsync_range(dst, dst_pos, dst_end, 0); + if (!status) + status = filemap_check_wb_err(dst->f_mapping, since); + if (!status) { + /* + * A reflink marks extents shared in the source inode too, + * so the source's metadata has to reach durable storage + * even though its data is untouched. + */ + status = commit_inode_metadata(file_inode(src)); } -out_err: - return ret; + return status; } ssize_t nfsd_copy_file_range(struct file *src, u64 src_pos, struct file *dst, @@ -773,64 +801,21 @@ __be32 nfsd4_vfs_fallocate(struct svc_rqst *rqstp, struct svc_fh *fhp, } #endif /* defined(CONFIG_NFSD_V4) */ -/* - * Check server access rights to a file system object +/** + * nfsd_access - Check caller's access rights to a file system object + * @rqstp: RPC transaction context + * @fhp: target NFS filehandle + * @maps: tables mapping on-the-wire access bits to NFSD_MAY flags + * @access: requested access bits on entry, permitted bits on return + * @supported: optional output of the access bits the server supports + * + * Return: nfs_ok on success, otherwise an nfserr status code */ -struct accessmap { - u32 access; - int how; -}; -static struct accessmap nfs3_regaccess[] = { - { NFS3_ACCESS_READ, NFSD_MAY_READ }, - { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC }, - { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_TRUNC }, - { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE }, - -#ifdef CONFIG_NFSD_V4 - { NFS4_ACCESS_XAREAD, NFSD_MAY_READ }, - { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE }, - { NFS4_ACCESS_XALIST, NFSD_MAY_READ }, -#endif - - { 0, 0 } -}; - -static struct accessmap nfs3_diraccess[] = { - { NFS3_ACCESS_READ, NFSD_MAY_READ }, - { NFS3_ACCESS_LOOKUP, NFSD_MAY_EXEC }, - { NFS3_ACCESS_MODIFY, NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC}, - { NFS3_ACCESS_EXTEND, NFSD_MAY_EXEC|NFSD_MAY_WRITE }, - { NFS3_ACCESS_DELETE, NFSD_MAY_REMOVE }, - -#ifdef CONFIG_NFSD_V4 - { NFS4_ACCESS_XAREAD, NFSD_MAY_READ }, - { NFS4_ACCESS_XAWRITE, NFSD_MAY_WRITE }, - { NFS4_ACCESS_XALIST, NFSD_MAY_READ }, -#endif - - { 0, 0 } -}; - -static struct accessmap nfs3_anyaccess[] = { - /* Some clients - Solaris 2.6 at least, make an access call - * to the server to check for access for things like /dev/null - * (which really, the server doesn't care about). So - * We provide simple access checking for them, looking - * mainly at mode bits, and we make sure to ignore read-only - * filesystem checks - */ - { NFS3_ACCESS_READ, NFSD_MAY_READ }, - { NFS3_ACCESS_EXECUTE, NFSD_MAY_EXEC }, - { NFS3_ACCESS_MODIFY, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, - { NFS3_ACCESS_EXTEND, NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS }, - - { 0, 0 } -}; - -__be32 -nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *supported) +__be32 nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, + const struct nfsd_access_maps *maps, + u32 *access, u32 *supported) { - struct accessmap *map; + const struct nfsd_access_map *map; struct svc_export *export; struct dentry *dentry; u32 query, result = 0, sresult = 0; @@ -844,12 +829,11 @@ nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *suppor dentry = fhp->fh_dentry; if (d_is_reg(dentry)) - map = nfs3_regaccess; + map = maps->regular; else if (d_is_dir(dentry)) - map = nfs3_diraccess; + map = maps->directory; else - map = nfs3_anyaccess; - + map = maps->other; query = *access; for (; map->access; map++) { @@ -859,7 +843,7 @@ nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *access, u32 *suppor sresult |= map->access; err2 = nfsd_permission(&rqstp->rq_cred, export, - dentry, map->how); + dentry, map->may); switch (err2) { case nfs_ok: result |= map->access; @@ -1424,7 +1408,7 @@ nfsd_direct_write(struct svc_rqst *rqstp, struct svc_fh *fhp, * @offset: Byte offset of start * @payload: xdr_buf containing the write payload * @cnt: IN: number of bytes to write, OUT: number of bytes actually written - * @stable: An NFS stable_how value + * @iocb_flags: VFS IOCB_* flags expressing the requested write stability * @verf: NFS WRITE verifier * * Upon return, caller must invoke fh_put on @fhp. @@ -1436,7 +1420,7 @@ __be32 nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_file *nf, loff_t offset, const struct xdr_buf *payload, unsigned long *cnt, - int stable, __be32 *verf) + int iocb_flags, __be32 *verf) { struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); struct file *file = nf->nf_file; @@ -1473,21 +1457,11 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, exp = fhp->fh_export; if (!EX_ISSYNC(exp)) - stable = NFS_UNSTABLE; + iocb_flags = 0; init_sync_kiocb(&kiocb, file); kiocb.ki_pos = offset; - if (likely(!fhp->fh_use_wgather)) { - switch (stable) { - case NFS_FILE_SYNC: - /* persist data and timestamps */ - kiocb.ki_flags |= IOCB_DSYNC | IOCB_SYNC; - break; - case NFS_DATA_SYNC: - /* persist data only */ - kiocb.ki_flags |= IOCB_DSYNC; - break; - } - } + if (likely(!fhp->fh_use_wgather)) + kiocb.ki_flags |= iocb_flags; nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload); if (nvecs < 0) { @@ -1517,21 +1491,21 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, break; } if (host_err < 0) { - commit_reset_write_verifier(nn, rqstp, host_err); + nfsd_maybe_reset_write_verifier(nn, rqstp, host_err); goto out_nfserr; } nfsd_stats_io_write_add(nn, exp, *cnt); fsnotify_modify(file); host_err = filemap_check_wb_err(file->f_mapping, since); if (host_err < 0) { - commit_reset_write_verifier(nn, rqstp, host_err); + nfsd_maybe_reset_write_verifier(nn, rqstp, host_err); goto out_nfserr; } - if (stable && fhp->fh_use_wgather) { + if (iocb_flags && fhp->fh_use_wgather) { host_err = wait_for_concurrent_writes(file); if (host_err < 0) - commit_reset_write_verifier(nn, rqstp, host_err); + nfsd_maybe_reset_write_verifier(nn, rqstp, host_err); } out_nfserr: @@ -1619,7 +1593,7 @@ __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, * @offset: Byte offset of start * @payload: xdr_buf containing the write payload * @cnt: IN: number of bytes to write, OUT: number of bytes actually written - * @stable: An NFS stable_how value + * @iocb_flags: VFS IOCB_* flags expressing the requested write stability * @verf: NFS WRITE verifier * * Upon return, caller must invoke fh_put on @fhp. @@ -1629,8 +1603,8 @@ __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, */ __be32 nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, - const struct xdr_buf *payload, unsigned long *cnt, int stable, - __be32 *verf) + const struct xdr_buf *payload, unsigned long *cnt, + int iocb_flags, __be32 *verf) { struct nfsd_file *nf; __be32 err; @@ -1642,7 +1616,7 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, goto out; err = nfsd_vfs_write(rqstp, fhp, nf, offset, payload, cnt, - stable, verf); + iocb_flags, verf); nfsd_file_put(nf); out: trace_nfsd_write_done(rqstp, fhp, offset, *cnt); @@ -1707,14 +1681,14 @@ nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_file *nf, err2 = filemap_check_wb_err(nf->nf_file->f_mapping, since); if (err2 < 0) - commit_reset_write_verifier(nn, rqstp, err2); + nfsd_maybe_reset_write_verifier(nn, rqstp, err2); err = nfserrno(err2); break; case -EINVAL: err = nfserr_notsupp; break; default: - commit_reset_write_verifier(nn, rqstp, err2); + nfsd_maybe_reset_write_verifier(nn, rqstp, err2); err = nfserrno(err2); } } else diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h index 4af2ff9e9dfe..f0cb184643f2 100644 --- a/fs/nfsd/vfs.h +++ b/fs/nfsd/vfs.h @@ -37,6 +37,17 @@ #define NFSD_MAY_CREATE (NFSD_MAY_EXEC|NFSD_MAY_WRITE) #define NFSD_MAY_REMOVE (NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC) +struct nfsd_access_map { + u32 access; + int may; +}; + +struct nfsd_access_maps { + const struct nfsd_access_map *regular; + const struct nfsd_access_map *directory; + const struct nfsd_access_map *other; +}; + struct nfsd_file; /* @@ -75,9 +86,14 @@ static inline bool nfsd_attrs_valid(struct nfsd_attrs *attrs) attrs->na_pacl || attrs->na_dpacl); } +struct nfsd_net; + __be32 nfserrno (int errno); -int nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, - struct svc_export **expp); +void nfsd_maybe_reset_write_verifier(struct nfsd_net *nn, + struct svc_rqst *rqstp, + int err); +__be32 nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp, + struct svc_export **expp); __be32 nfsd_lookup(struct svc_rqst *, struct svc_fh *, const char *, unsigned int, struct svc_fh *); __be32 nfsd_lookup_dentry(struct svc_rqst *, struct svc_fh *, @@ -89,10 +105,11 @@ int nfsd_mountpoint(struct dentry *, struct svc_export *); #ifdef CONFIG_NFSD_V4 __be32 nfsd4_vfs_fallocate(struct svc_rqst *, struct svc_fh *, struct file *, loff_t, loff_t, int); -__be32 nfsd4_clone_file_range(struct svc_rqst *rqstp, - struct nfsd_file *nf_src, u64 src_pos, - struct nfsd_file *nf_dst, u64 dst_pos, - u64 count, bool sync); +int nfsd_clone_file_range(struct file *src, u64 src_pos, + struct file *dst, u64 dst_pos, + u64 count, errseq_t *since); +int nfsd_clone_sync_range(struct file *src, struct file *dst, + u64 dst_pos, u64 count, errseq_t since); #endif /* CONFIG_NFSD_V4 */ __be32 nfsd_create_locked(struct svc_rqst *, struct svc_fh *, struct nfsd_attrs *attrs, int type, dev_t rdev, @@ -100,7 +117,9 @@ __be32 nfsd_create_locked(struct svc_rqst *, struct svc_fh *, __be32 nfsd_create(struct svc_rqst *, struct svc_fh *, char *name, int len, struct nfsd_attrs *attrs, int type, dev_t rdev, struct svc_fh *res); -__be32 nfsd_access(struct svc_rqst *, struct svc_fh *, u32 *, u32 *); +__be32 nfsd_access(struct svc_rqst *rqstp, struct svc_fh *fhp, + const struct nfsd_access_maps *maps, + u32 *access, u32 *supported); __be32 nfsd_create_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct svc_fh *resfhp, struct nfsd_attrs *iap); __be32 nfsd_commit(struct svc_rqst *rqst, struct svc_fh *fhp, @@ -135,11 +154,13 @@ __be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, u32 *eof); __be32 nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, const struct xdr_buf *payload, - unsigned long *cnt, int stable, __be32 *verf); + unsigned long *cnt, int iocb_flags, + __be32 *verf); __be32 nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_file *nf, loff_t offset, const struct xdr_buf *payload, - unsigned long *cnt, int stable, __be32 *verf); + unsigned long *cnt, int iocb_flags, + __be32 *verf); __be32 nfsd_readlink(struct svc_rqst *, struct svc_fh *, char *, int *); __be32 nfsd_symlink(struct svc_rqst *, struct svc_fh *, diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h index 344203874b4c..cad875d14231 100644 --- a/fs/nfsd/xdr3.h +++ b/fs/nfsd/xdr3.h @@ -39,7 +39,7 @@ struct nfsd3_writeargs { svc_fh fh; __u64 offset; __u32 count; - int stable; + __u32 stable; __u32 len; struct xdr_buf payload; }; diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h index c7eda5bc833b..7bbb375874ef 100644 --- a/fs/nfsd/xdr4.h +++ b/fs/nfsd/xdr4.h @@ -50,134 +50,6 @@ #define HAS_CSTATE_FLAG(c, f) ((c)->sid_flags & (f)) #define CLEAR_CSTATE_FLAG(c, f) ((c)->sid_flags &= ~(f)) -/** - * nfsd4_encode_bool - Encode an XDR bool type result - * @xdr: target XDR stream - * @val: boolean value to encode - * - * Return values: - * %nfs_ok: @val encoded; @xdr advanced to next position - * %nfserr_resource: stream buffer space exhausted - */ -static __always_inline __be32 -nfsd4_encode_bool(struct xdr_stream *xdr, bool val) -{ - __be32 *p = xdr_reserve_space(xdr, XDR_UNIT); - - if (unlikely(p == NULL)) - return nfserr_resource; - *p = val ? xdr_one : xdr_zero; - return nfs_ok; -} - -/** - * nfsd4_encode_uint32_t - Encode an XDR uint32_t type result - * @xdr: target XDR stream - * @val: integer value to encode - * - * Return values: - * %nfs_ok: @val encoded; @xdr advanced to next position - * %nfserr_resource: stream buffer space exhausted - */ -static __always_inline __be32 -nfsd4_encode_uint32_t(struct xdr_stream *xdr, u32 val) -{ - __be32 *p = xdr_reserve_space(xdr, XDR_UNIT); - - if (unlikely(p == NULL)) - return nfserr_resource; - *p = cpu_to_be32(val); - return nfs_ok; -} - -#define nfsd4_encode_aceflag4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_acemask4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_acetype4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_count4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_mode4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_nfs_lease4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_qop4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_sequenceid4(x, v) nfsd4_encode_uint32_t(x, v) -#define nfsd4_encode_slotid4(x, v) nfsd4_encode_uint32_t(x, v) - -/** - * nfsd4_encode_uint64_t - Encode an XDR uint64_t type result - * @xdr: target XDR stream - * @val: integer value to encode - * - * Return values: - * %nfs_ok: @val encoded; @xdr advanced to next position - * %nfserr_resource: stream buffer space exhausted - */ -static __always_inline __be32 -nfsd4_encode_uint64_t(struct xdr_stream *xdr, u64 val) -{ - __be32 *p = xdr_reserve_space(xdr, XDR_UNIT * 2); - - if (unlikely(p == NULL)) - return nfserr_resource; - put_unaligned_be64(val, p); - return nfs_ok; -} - -#define nfsd4_encode_changeid4(x, v) nfsd4_encode_uint64_t(x, v) -#define nfsd4_encode_nfs_cookie4(x, v) nfsd4_encode_uint64_t(x, v) -#define nfsd4_encode_length4(x, v) nfsd4_encode_uint64_t(x, v) -#define nfsd4_encode_offset4(x, v) nfsd4_encode_uint64_t(x, v) - -/** - * nfsd4_encode_opaque_fixed - Encode a fixed-length XDR opaque type result - * @xdr: target XDR stream - * @data: pointer to data - * @size: length of data in bytes - * - * Return values: - * %nfs_ok: @data encoded; @xdr advanced to next position - * %nfserr_resource: stream buffer space exhausted - */ -static __always_inline __be32 -nfsd4_encode_opaque_fixed(struct xdr_stream *xdr, const void *data, - size_t size) -{ - __be32 *p = xdr_reserve_space(xdr, xdr_align_size(size)); - size_t pad = xdr_pad_size(size); - - if (unlikely(p == NULL)) - return nfserr_resource; - memcpy(p, data, size); - if (pad) - memset((char *)p + size, 0, pad); - return nfs_ok; -} - -/** - * nfsd4_encode_opaque - Encode a variable-length XDR opaque type result - * @xdr: target XDR stream - * @data: pointer to data - * @size: length of data in bytes - * - * Return values: - * %nfs_ok: @data encoded; @xdr advanced to next position - * %nfserr_resource: stream buffer space exhausted - */ -static __always_inline __be32 -nfsd4_encode_opaque(struct xdr_stream *xdr, const void *data, size_t size) -{ - size_t pad = xdr_pad_size(size); - __be32 *p; - - p = xdr_reserve_space(xdr, XDR_UNIT + xdr_align_size(size)); - if (unlikely(p == NULL)) - return nfserr_resource; - *p++ = cpu_to_be32(size); - memcpy(p, data, size); - if (pad) - memset((char *)p + size, 0, pad); - return nfs_ok; -} - -#define nfsd4_encode_component4(x, d, s) nfsd4_encode_opaque(x, d, s) - struct nfsd4_compound_state { struct svc_fh current_fh; struct svc_fh save_fh; @@ -642,17 +514,6 @@ svcxdr_decode_deviceid4(__be32 *p, struct nfsd4_deviceid *devid) return p; } -static inline __be32 -nfsd4_decode_deviceid4(struct xdr_stream *xdr, struct nfsd4_deviceid *devid) -{ - __be32 *p = xdr_inline_decode(xdr, NFS4_DEVICEID4_SIZE); - - if (unlikely(!p)) - return nfserr_bad_xdr; - svcxdr_decode_deviceid4(p, devid); - return nfs_ok; -} - struct nfsd4_layout_seg { u32 iomode; u64 offset; @@ -736,6 +597,19 @@ struct nfsd4_cb_offload { u32 co_referring_seqno; }; +struct nfsd4_ssc_umount_item { + struct list_head nsui_list; + bool nsui_busy; + /* + * nsui_refcnt inited to 2, 1 on list and 1 for consumer. Entry + * is removed when refcnt drops to 1 and nsui_expire expires. + */ + refcount_t nsui_refcnt; + unsigned long nsui_expire; + struct vfsmount *nsui_vfsmount; + char nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1]; +}; + struct nfsd4_copy { /* request */ stateid_t cp_src_stateid; diff --git a/fs/nfsd/xdr4cb.h b/fs/nfsd/xdr4cb.h index b06d0170d7c4..838f8629821f 100644 --- a/fs/nfsd/xdr4cb.h +++ b/fs/nfsd/xdr4cb.h @@ -6,29 +6,30 @@ #define cb_compound_enc_hdr_sz 4 #define cb_compound_dec_hdr_sz (3 + (NFS4_MAXTAGLEN >> 2)) #define sessionid_sz (NFS4_MAX_SESSIONID_LEN >> 2) +#define op_enc_sz 1 #define enc_referring_call4_sz (1 + 1) #define enc_referring_call_list4_sz (sessionid_sz + 1 + \ enc_referring_call4_sz) -#define cb_sequence_enc_sz (sessionid_sz + 4 + \ - enc_referring_call_list4_sz) +#define cb_sequence_enc_sz (op_enc_sz + sessionid_sz + 4 + \ + 1 + enc_referring_call_list4_sz) #define cb_sequence_dec_sz (op_dec_sz + sessionid_sz + 4) -#define op_enc_sz 1 #define op_dec_sz 2 #define enc_nfs4_fh_sz (1 + (NFS4_FHSIZE >> 2)) #define enc_stateid_sz (NFS4_STATEID_SIZE >> 2) #define NFS4_enc_cb_recall_sz (cb_compound_enc_hdr_sz + \ cb_sequence_enc_sz + \ - 1 + enc_stateid_sz + \ - enc_nfs4_fh_sz) + op_enc_sz + enc_stateid_sz + \ + 1 + enc_nfs4_fh_sz) #define NFS4_dec_cb_recall_sz (cb_compound_dec_hdr_sz + \ cb_sequence_dec_sz + \ op_dec_sz) #define NFS4_enc_cb_layout_sz (cb_compound_enc_hdr_sz + \ cb_sequence_enc_sz + \ - 1 + 3 + \ - enc_nfs4_fh_sz + 4) + op_enc_sz + 3 + 1 + \ + enc_nfs4_fh_sz + 4 + \ + enc_stateid_sz) #define NFS4_dec_cb_layout_sz (cb_compound_dec_hdr_sz + \ cb_sequence_dec_sz + \ op_dec_sz) @@ -47,7 +48,7 @@ #define NFS4_enc_cb_notify_lock_sz (cb_compound_enc_hdr_sz + \ cb_sequence_enc_sz + \ - 2 + 1 + \ + op_enc_sz + 2 + 1 + \ XDR_QUADLEN(NFS4_OPAQUE_LIMIT) + \ enc_nfs4_fh_sz) #define NFS4_dec_cb_notify_lock_sz (cb_compound_dec_hdr_sz + \ @@ -57,6 +58,7 @@ XDR_QUADLEN(NFS4_VERIFIER_SIZE)) #define NFS4_enc_cb_offload_sz (cb_compound_enc_hdr_sz + \ cb_sequence_enc_sz + \ + op_enc_sz + \ enc_nfs4_fh_sz + \ enc_stateid_sz + \ enc_cb_offload_info_sz) @@ -65,7 +67,7 @@ op_dec_sz) #define NFS4_enc_cb_recall_any_sz (cb_compound_enc_hdr_sz + \ cb_sequence_enc_sz + \ - 1 + 1 + 1) + op_enc_sz + 1 + 1 + 1) #define NFS4_dec_cb_recall_any_sz (cb_compound_dec_hdr_sz + \ cb_sequence_dec_sz + \ op_dec_sz) diff --git a/include/linux/nfs.h b/include/linux/nfs.h index 0906a0b40c6a..8c2818db43c5 100644 --- a/include/linux/nfs.h +++ b/include/linux/nfs.h @@ -11,59 +11,8 @@ #include <linux/cred.h> #include <linux/sunrpc/auth.h> #include <linux/sunrpc/msg_prot.h> -#include <linux/string.h> -#include <linux/crc32.h> -#include <uapi/linux/nfs.h> - -/* The LOCALIO program is entirely private to Linux and is - * NOT part of the uapi. - */ -#define NFS_LOCALIO_PROGRAM 400122 -#define LOCALIOPROC_NULL 0 -#define LOCALIOPROC_UUID_IS_LOCAL 1 - -/* - * This is the kernel NFS client file handle representation - */ -#define NFS_MAXFHSIZE 128 -struct nfs_fh { - unsigned short size; - unsigned char data[NFS_MAXFHSIZE]; -}; - -/* - * Returns a zero iff the size and data fields match. - * Checks only "size" bytes in the data field. - */ -static inline int nfs_compare_fh(const struct nfs_fh *a, const struct nfs_fh *b) -{ - return a->size != b->size || memcmp(a->data, b->data, a->size) != 0; -} - -static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *source) -{ - target->size = source->size; - memcpy(target->data, source->data, source->size); -} - -enum nfs3_stable_how { - NFS_UNSTABLE = 0, - NFS_DATA_SYNC = 1, - NFS_FILE_SYNC = 2, +#include <linux/nfs_fh.h> - /* used by direct.c to mark verf as invalid */ - NFS_INVALID_STABLE_HOW = -1 -}; +#include <uapi/linux/nfs.h> -/** - * nfs_fhandle_hash - calculate the crc32 hash for the filehandle - * @fh - pointer to filehandle - * - * returns a crc32 hash for the filehandle that is compatible with - * the one displayed by "wireshark". - */ -static inline u32 nfs_fhandle_hash(const struct nfs_fh *fh) -{ - return ~crc32_le(0xFFFFFFFF, &fh->data[0], fh->size); -} #endif /* _LINUX_NFS_H */ diff --git a/include/linux/nfs3.h b/include/linux/nfs3.h index 404b8f724fc9..1d18da0860d5 100644 --- a/include/linux/nfs3.h +++ b/include/linux/nfs3.h @@ -7,6 +7,14 @@ #include <uapi/linux/nfs3.h> +enum nfs3_stable_how { + NFS_UNSTABLE = 0, + NFS_DATA_SYNC = 1, + NFS_FILE_SYNC = 2, + + /* used to mark verf as invalid */ + NFS_INVALID_STABLE_HOW = -1 +}; /* Number of 32bit words in post_op_attr */ #define NFS3_POST_OP_ATTR_WORDS 22 diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h index 1a3981c26b23..41b7cdcc674f 100644 --- a/include/linux/nfs4.h +++ b/include/linux/nfs4.h @@ -263,6 +263,12 @@ enum why_no_delegation4 { /* new to v4.1 */ WND4_IS_DIR = 8, }; +enum stable_how4 { + UNSTABLE4 = 0, + DATA_SYNC4 = 1, + FILE_SYNC4 = 2, +}; + enum lock_type4 { NFS4_UNLOCK_LT = 0, NFS4_READ_LT = 1, diff --git a/include/linux/nfs_fh.h b/include/linux/nfs_fh.h new file mode 100644 index 000000000000..49dfc5ec60fe --- /dev/null +++ b/include/linux/nfs_fh.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * struct nfs_fh is an NFS version-agnostic data structure that + * stores an NFS file handle. It is also commonly used in NFS + * related APIs. + */ +#ifndef _LINUX_NFS_FH_H +#define _LINUX_NFS_FH_H + +#include <linux/types.h> +#include <linux/string.h> +#include <linux/crc32.h> + +/* + * The largest file handle size today is an NFSv4 file handle, + * which can be up to 128 octets long. + */ +#define NFS_MAXFHSIZE 128 +struct nfs_fh { + unsigned short size; + unsigned char data[NFS_MAXFHSIZE]; +}; + +/** + * nfs_compare_fh - Compare two NFS file handles + * @a: An NFS file handle to be compared + * @b: An NFS file handle to be compared + * + * Checks only "size" bytes in each data field. + * + * Return: %false if the two file handles are equal, otherwise %true + */ +static inline bool nfs_compare_fh(const struct nfs_fh *a, const struct nfs_fh *b) +{ + return a->size != b->size || memcmp(a->data, b->data, a->size) != 0; +} + +/** + * nfs_copy_fh - Copy an NFS file handle + * @target: Destination file handle + * @source: Source file handle + * + * Copies source->size bytes of file handle data into target. + */ +static inline void nfs_copy_fh(struct nfs_fh *target, const struct nfs_fh *source) +{ + target->size = source->size; + memcpy(target->data, source->data, source->size); +} + +/** + * nfs_fhandle_hash - Calculate the crc32 hash for the filehandle + * @fh: An NFS file handle to hash + * + * Return: a crc32 hash for the filehandle that is compatible with + * the one displayed by "wireshark" + */ +static inline u32 nfs_fhandle_hash(const struct nfs_fh *fh) +{ + return ~crc32_le(0xFFFFFFFF, &fh->data[0], fh->size); +} + +#endif /* _LINUX_NFS_FH_H */ diff --git a/include/linux/nfs_ssc.h b/include/linux/nfs_ssc.h index 22265b1ff080..c199ea23e7eb 100644 --- a/include/linux/nfs_ssc.h +++ b/include/linux/nfs_ssc.h @@ -2,80 +2,33 @@ /* * include/linux/nfs_ssc.h * + * NFSv4.2 server-to-server copy, NFS client side APIs + * * Author: Dai Ngo <dai.ngo@oracle.com> * * Copyright (c) 2020, Oracle and/or its affiliates. */ -#include <linux/nfs_fs.h> -#include <linux/sunrpc/svc.h> +#ifndef _LINUX_NFS_SSC_H +#define _LINUX_NFS_SSC_H -extern struct nfs_ssc_client_ops_tbl nfs_ssc_client_tbl; +#include <linux/nfs_fh.h> +#include <linux/nfs4.h> + +struct file; +struct vfsmount; -/* - * NFS_V4 - */ struct nfs4_ssc_client_ops { + struct module *owner; struct file *(*sco_open)(struct vfsmount *ss_mnt, struct nfs_fh *src_fh, nfs4_stateid *stateid); void (*sco_close)(struct file *filep); }; -/* - * NFS_FS - */ -struct nfs_ssc_client_ops { - void (*sco_sb_deactive)(struct super_block *sb); -}; - -struct nfs_ssc_client_ops_tbl { - const struct nfs4_ssc_client_ops *ssc_nfs4_ops; - const struct nfs_ssc_client_ops *ssc_nfs_ops; -}; - extern void nfs42_ssc_register_ops(void); extern void nfs42_ssc_unregister_ops(void); extern void nfs42_ssc_register(const struct nfs4_ssc_client_ops *ops); extern void nfs42_ssc_unregister(const struct nfs4_ssc_client_ops *ops); -#ifdef CONFIG_NFSD_V4_2_INTER_SSC -static inline struct file *nfs42_ssc_open(struct vfsmount *ss_mnt, - struct nfs_fh *src_fh, nfs4_stateid *stateid) -{ - if (nfs_ssc_client_tbl.ssc_nfs4_ops) - return (*nfs_ssc_client_tbl.ssc_nfs4_ops->sco_open)(ss_mnt, src_fh, stateid); - return ERR_PTR(-EIO); -} - -static inline void nfs42_ssc_close(struct file *filep) -{ - if (nfs_ssc_client_tbl.ssc_nfs4_ops) - (*nfs_ssc_client_tbl.ssc_nfs4_ops->sco_close)(filep); -} -#endif - -struct nfsd4_ssc_umount_item { - struct list_head nsui_list; - bool nsui_busy; - /* - * nsui_refcnt inited to 2, 1 on list and 1 for consumer. Entry - * is removed when refcnt drops to 1 and nsui_expire expires. - */ - refcount_t nsui_refcnt; - unsigned long nsui_expire; - struct vfsmount *nsui_vfsmount; - char nsui_ipaddr[RPC_MAX_ADDRBUFLEN + 1]; -}; - -/* - * NFS_FS - */ -extern void nfs_ssc_register(const struct nfs_ssc_client_ops *ops); -extern void nfs_ssc_unregister(const struct nfs_ssc_client_ops *ops); - -static inline void nfs_do_sb_deactive(struct super_block *sb) -{ - if (nfs_ssc_client_tbl.ssc_nfs_ops) - (*nfs_ssc_client_tbl.ssc_nfs_ops->sco_sb_deactive)(sb); -} +#endif /* _LINUX_NFS_SSC_H */ diff --git a/include/linux/nfsd_ssc.h b/include/linux/nfsd_ssc.h new file mode 100644 index 000000000000..7001410f01c2 --- /dev/null +++ b/include/linux/nfsd_ssc.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * include/linux/nfsd_ssc.h + * + * NFSv4.2 server-to-server copy, NFS server side APIs + * + * Author: Dai Ngo <dai.ngo@oracle.com> + * + * Copyright (c) 2020, Oracle and/or its affiliates. + */ + +#ifndef _LINUX_NFSD_SSC_H +#define _LINUX_NFSD_SSC_H + +#include <linux/nfs_fh.h> +#include <linux/nfs4.h> + +struct file; +struct vfsmount; + +#if IS_ENABLED(CONFIG_NFS_V4_2_SSC_HELPER) +struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, struct nfs_fh *src_fh, + nfs4_stateid *stateid); +void nfsd42_ssc_close(struct file *filp); +#else +static inline struct file *nfsd42_ssc_open(struct vfsmount *ss_mnt, + struct nfs_fh *src_fh, + nfs4_stateid *stateid) +{ + return ERR_PTR(-EIO); +} + +static inline void nfsd42_ssc_close(struct file *filp) +{ +} +#endif + +#endif /* _LINUX_NFSD_SSC_H */ diff --git a/include/linux/nfslocalio.h b/include/linux/nfslocalio.h index 3d91043254e6..8ce4d978a636 100644 --- a/include/linux/nfslocalio.h +++ b/include/linux/nfslocalio.h @@ -13,9 +13,18 @@ #include <linux/uuid.h> #include <linux/sunrpc/clnt.h> #include <linux/sunrpc/svcauth.h> -#include <linux/nfs.h> +#include <linux/nfs_fh.h> + #include <net/net_namespace.h> +/* + * The LOCALIO program is entirely private to Linux and is NOT part of + * the uapi. + */ +#define NFS_LOCALIO_PROGRAM 400122 +#define LOCALIOPROC_NULL 0 +#define LOCALIOPROC_UUID_IS_LOCAL 1 + struct nfs_client; struct nfs_file_localio; diff --git a/include/trace/misc/nfs.h b/include/trace/misc/nfs.h index a394b4d38e18..b5fb77d7954b 100644 --- a/include/trace/misc/nfs.h +++ b/include/trace/misc/nfs.h @@ -8,6 +8,7 @@ */ #include <linux/nfs.h> +#include <linux/nfs3.h> #include <linux/nfs4.h> #include <uapi/linux/nfs.h> diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c index 50e5e7f5b762..7a423e9ee74d 100644 --- a/net/sunrpc/svcsock.c +++ b/net/sunrpc/svcsock.c @@ -238,6 +238,39 @@ static int svc_one_sock_name(struct svc_sock *svsk, char *buf, int remaining) return len; } +/* + * kTLS delivers a record only up to the caller's buffer and keeps + * the remainder on its receive list, where no further data_ready + * announces it. Consume the whole record. + */ +static void +svc_tcp_sock_drain_record(struct socket *sock) +{ + union { + struct cmsghdr cmsg; + u8 buf[CMSG_SPACE(sizeof(u8))]; + } u; + u8 discard[64]; + struct kvec discard_kvec = { + .iov_base = discard, + .iov_len = sizeof(discard), + }; + + for (;;) { + struct msghdr msg = { + .msg_control = &u, + .msg_controllen = sizeof(u), + }; + + iov_iter_kvec(&msg.msg_iter, ITER_DEST, &discard_kvec, 1, + discard_kvec.iov_len); + if (sock_recvmsg(sock, &msg, MSG_DONTWAIT) <= 0) + break; + if (msg.msg_flags & MSG_EOR) + break; + } +} + static int svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg, struct cmsghdr *cmsg, int ret) @@ -257,8 +290,17 @@ svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg, break; case TLS_RECORD_TYPE_ALERT: tls_alert_recv(sock->sk, msg, &level, &description); - ret = (level == TLS_ALERT_LEVEL_FATAL) ? - -ENOTCONN : -EAGAIN; + /* RFC 8446 Section 6: every alert but a closure alert is + * an error alert. + */ + switch (description) { + case TLS_ALERT_DESC_CLOSE_NOTIFY: + case TLS_ALERT_DESC_USER_CANCELED: + ret = -EAGAIN; + break; + default: + ret = -ENOTCONN; + } break; default: /* discard this record type */ @@ -289,8 +331,31 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags) iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1, alert_kvec.iov_len); ret = sock_recvmsg(sock, &msg, MSG_DONTWAIT); - if (ret > 0 && - tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT) { + /* put_cmsg() shrinks msg_controllen, so a short one means + * kTLS filled in u.cmsg. + */ + if (ret >= 0 && msg.msg_controllen < sizeof(u)) { + u8 content_type = tls_get_record_type(sock->sk, &u.cmsg); + + /* Returning the count would credit the RPC stream with + * octets that never reached the caller's buffer. + */ + if (content_type != TLS_RECORD_TYPE_ALERT) { + /* An application data record carries RPC payload. + * Draining one breaks RPC fragment framing. + */ + if (content_type != TLS_RECORD_TYPE_DATA && + !(msg.msg_flags & MSG_EOR)) + svc_tcp_sock_drain_record(sock); + return -EAGAIN; + } + /* An Alert record carries exactly one two-octet message + * (RFC 8446 Section 5.1). alert_kvec caps the receive at two, + * so a longer record produces the same count. MSG_EOR appears + * only once kTLS has drained the whole record. + */ + if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR)) + return -EBADMSG; iov_iter_revert(&msg.msg_iter, ret); ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN); } @@ -306,8 +371,16 @@ svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg) ret = sock_recvmsg(sock, msg, MSG_DONTWAIT); if (msg->msg_flags & MSG_CTRUNC) { msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR); - if (ret == 0 || ret == -EIO) + if (ret == 0 || ret == -EIO) { ret = svc_tcp_sock_recv_cmsg(sock, &msg->msg_flags); + /* A control record delivers nothing to the caller, + * and kTLS announces no data_ready for records it + * already holds. Mark the transport ready so that + * the records behind this one can be received. + */ + if (ret == -EAGAIN) + set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags); + } } return ret; } diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c index 7f60723fa64d..ca22ca8f2fea 100644 --- a/net/sunrpc/xprtsock.c +++ b/net/sunrpc/xprtsock.c @@ -357,35 +357,6 @@ xs_alloc_sparse_pages(struct xdr_buf *buf, size_t want, gfp_t gfp) } static int -xs_sock_process_cmsg(struct socket *sock, struct msghdr *msg, - unsigned int *msg_flags, struct cmsghdr *cmsg, int ret) -{ - u8 content_type = tls_get_record_type(sock->sk, cmsg); - u8 level, description; - - switch (content_type) { - case 0: - break; - case TLS_RECORD_TYPE_DATA: - /* TLS sets EOR at the end of each application data - * record, even though there might be more frames - * waiting to be decrypted. - */ - *msg_flags &= ~MSG_EOR; - break; - case TLS_RECORD_TYPE_ALERT: - tls_alert_recv(sock->sk, msg, &level, &description); - ret = (level == TLS_ALERT_LEVEL_FATAL) ? - -EACCES : -EAGAIN; - break; - default: - /* discard this record type */ - ret = -EAGAIN; - } - return ret; -} - -static int xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags) { union { @@ -402,16 +373,39 @@ xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags) .msg_control = &u, .msg_controllen = sizeof(u), }; + u8 level, description; int ret; iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1, alert_kvec.iov_len); ret = sock_recvmsg(sock, &msg, flags); - if (ret > 0) { - if (tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT) - iov_iter_revert(&msg.msg_iter, ret); - ret = xs_sock_process_cmsg(sock, &msg, msg_flags, &u.cmsg, - -EAGAIN); + /* put_cmsg() shrinks msg_controllen, so a short one means + * kTLS filled in u.cmsg. + */ + if (ret >= 0 && msg.msg_controllen < sizeof(u)) { + if (tls_get_record_type(sock->sk, &u.cmsg) != + TLS_RECORD_TYPE_ALERT) + return -EAGAIN; + /* An Alert record carries exactly one two-octet message + * (RFC 8446 Section 5.1). alert_kvec caps the receive at + * two, so the count alone cannot reliably detect an + * oversized record. + */ + if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR)) + return -EACCES; + iov_iter_revert(&msg.msg_iter, ret); + tls_alert_recv(sock->sk, &msg, &level, &description); + /* RFC 8446 Section 6: every alert but a closure alert is + * an error alert. + */ + switch (description) { + case TLS_ALERT_DESC_CLOSE_NOTIFY: + case TLS_ALERT_DESC_USER_CANCELED: + ret = -EAGAIN; + break; + default: + ret = -EACCES; + } } return ret; } @@ -425,6 +419,10 @@ xs_sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags, size_t seek) ret = sock_recvmsg(sock, msg, flags); /* Handle TLS inband control message lazily */ if (msg->msg_flags & MSG_CTRUNC) { + /* TLS sets EOR at the end of each application data + * record, even though there might be more frames + * waiting to be decrypted. + */ msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR); if (ret == 0 || ret == -EIO) ret = xs_sock_recv_cmsg(sock, &msg->msg_flags, flags); |
