[PATCH] NFS V4 - Dynamic Pseudo Root

Steve Dickson SteveD at redhat.com
Tue Jul 7 17:41:11 UTC 2009


Hey,

I would like to apply the following patch to the rawhide
kernel that will make exports for NFS v4 mount work just
list exports for v3 and v2 mounts.

In a nutshell, for NFS v4 mounts to work like v3/2 mounts
the '/ *(ro,fsid=0)' entry has to be added to the /etc/exports 
file. This patch eliminates the need for that entry.

The history, reasoning and evolution of this patch can
be found at:
   http://linux-nfs.org/pipermail/nfsv4/2009-June/010724.html

Over the weekend, I pounded on this code with 4 clients 
continuously running the cthon test suite, which tests 
every part of the protocol including mounting...    

In the end, this is the first major step toward making
NFS v4 the default protocol.
 
steved.

Date: Tue Jul 7, 2009
Author: Steve Dickson

Kernel support needed to implement dynamic pseudo root
support which will allow v3 and v2 exports accessible
to NFS v4 clients without any configuration changes. 

Signed-Off-By: Steve Dickson <steved at redhat.com>

----------------------------------------------------

diff -up linux-2.6.30.noarch/fs/nfsd/export.c.save linux-2.6.30.noarch/fs/nfsd/export.c
--- linux-2.6.30.noarch/fs/nfsd/export.c.save	2009-07-02 11:34:38.000000000 -0400
+++ linux-2.6.30.noarch/fs/nfsd/export.c	2009-07-02 11:35:44.000000000 -0400
@@ -104,6 +104,7 @@ static int expkey_parse(struct cache_det
 	if (mesg[mlen-1] != '\n')
 		return -EINVAL;
 	mesg[mlen-1] = 0;
+	dprintk("expkey_parse: '%s'\n", mesg);
 
 	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 	err = -ENOMEM;
@@ -181,6 +182,8 @@ static int expkey_parse(struct cache_det
 	if (dom)
 		auth_domain_put(dom);
 	kfree(buf);
+	if (err)
+		dprintk("expkey_parse: err %d\n", err);
 	return err;
 }
 
@@ -351,7 +354,10 @@ static void svc_export_request(struct ca
 		(*bpp)[0] = '\n';
 		return;
 	}
+
 	qword_add(bpp, blen, pth);
+	dprintk("svc_export_request: pth %s\n", pth);
+
 	(*bpp)[-1] = '\n';
 }
 
@@ -500,6 +506,7 @@ static int svc_export_parse(struct cache
 	if (mesg[mlen-1] != '\n')
 		return -EINVAL;
 	mesg[mlen-1] = 0;
+	dprintk("svc_export_parse: '%s'\n", mesg);
 
 	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
 	if (!buf)
@@ -619,6 +626,8 @@ out1:
 	auth_domain_put(dom);
 out:
 	kfree(buf);
+	if (err)
+		dprintk("svc_export_parse: err %d\n", err);
 	return err;
 }
 
@@ -1413,6 +1422,7 @@ static struct flags {
 	{ NFSEXP_CROSSMOUNT, {"crossmnt", ""}},
 	{ NFSEXP_NOSUBTREECHECK, {"no_subtree_check", ""}},
 	{ NFSEXP_NOAUTHNLM, {"insecure_locks", ""}},
+	{ NFSEXP_V4ROOT, {"v4root", ""}},
 #ifdef MSNFS
 	{ NFSEXP_MSNFS, {"msnfs", ""}},
 #endif
@@ -1493,7 +1503,7 @@ static int e_show(struct seq_file *m, vo
 	struct svc_export *exp = container_of(cp, struct svc_export, h);
 
 	if (p == SEQ_START_TOKEN) {
-		seq_puts(m, "# Version 1.1\n");
+		seq_puts(m, "# Version 1.2\n");
 		seq_puts(m, "# Path Client(Flags) # IPs\n");
 		return 0;
 	}
diff -up linux-2.6.30.noarch/fs/nfsd/nfs4xdr.c.save linux-2.6.30.noarch/fs/nfsd/nfs4xdr.c
--- linux-2.6.30.noarch/fs/nfsd/nfs4xdr.c.save	2009-07-02 11:34:38.000000000 -0400
+++ linux-2.6.30.noarch/fs/nfsd/nfs4xdr.c	2009-07-02 11:35:31.000000000 -0400
@@ -2176,28 +2176,62 @@ static inline int attributes_need_mount(
 	return 0;
 }
 
-static __be32
-nfsd4_encode_dirent_fattr(struct nfsd4_readdir *cd,
-		const char *name, int namlen, __be32 *p, int *buflen)
+struct dentry *
+nfsd_check_export(struct nfsd4_readdir *cd, const char *name, int namlen)
 {
 	struct svc_export *exp = cd->rd_fhp->fh_export;
 	struct dentry *dentry;
-	__be32 nfserr;
-	int ignore_crossmnt = 0;
+	int err;
 
 	dentry = lookup_one_len(name, cd->rd_fhp->fh_dentry, namlen);
 	if (IS_ERR(dentry))
-		return nfserrno(PTR_ERR(dentry));
+		return dentry;
 	if (!dentry->d_inode) {
-		/*
-		 * nfsd_buffered_readdir drops the i_mutex between
-		 * readdir and calling this callback, leaving a window
-		 * where this directory entry could have gone away.
-		 */
 		dput(dentry);
-		return nfserr_noent;
+		return ERR_PTR(-ENOENT);
+	}
+	
+	/*
+	 * Check to see if this dentry is part 
+	 * of the psuedo root
+	 */
+	if ((exp->ex_flags & NFSEXP_V4ROOT) == 0)
+		return dentry;
+
+	/*
+	 * Only exported directories are visable
+	 * on psuedo exports
+	 */
+	if (!S_ISDIR(dentry->d_inode->i_mode)) {
+		dput(dentry);
+		return ERR_PTR(-ENOENT);
 	}
 
+	/*
+	 * Make the upcall to see if this directory
+	 * is exported.
+	 */
+	exp_get(exp);
+	err = nfsd_export_lookup(cd->rd_rqstp, dentry, exp);
+	if (err) {
+		exp_put(exp);
+		dput(dentry);
+		return ERR_PTR(err);
+	}
+	exp_put(exp);
+
+	return dentry;
+}
+
+static __be32
+nfsd4_encode_dirent_fattr(struct nfsd4_readdir *cd,
+		struct dentry *dentry, __be32 *p, int *buflen)
+{
+	struct svc_export *exp = cd->rd_fhp->fh_export;
+	__be32 nfserr;
+	int ignore_crossmnt = 0;
+	int err, v4root = (exp->ex_flags & NFSEXP_V4ROOT);
+
 	exp_get(exp);
 	/*
 	 * In the case of a mountpoint, the client may be asking for
@@ -2208,18 +2242,29 @@ nfsd4_encode_dirent_fattr(struct nfsd4_r
 	 */
 	if (d_mountpoint(dentry) && !attributes_need_mount(cd->rd_bmval))
 		ignore_crossmnt = 1;
-	else if (d_mountpoint(dentry)) {
-		int err;
-
+	else if (d_mountpoint(dentry) || v4root) {
+		/*
+		 * Make sure the dentry is viewable on the psuedo export
+		 */
+		v4root = (dentry->d_inode && v4root);
+		if (v4root) {
+			err = nfsd_export_lookup(cd->rd_rqstp, dentry, exp);
+			if (err) {
+				nfserr = nfserrno(err);
+				goto out_put;
+			}
+		}
 		/*
 		 * Why the heck aren't we just using nfsd_lookup??
 		 * 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;
+		if (d_mountpoint(dentry) || v4root) {
+			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);
 		if (nfserr)
@@ -2258,6 +2303,7 @@ nfsd4_encode_dirent(void *ccdv, const ch
 	struct readdir_cd *ccd = ccdv;
 	struct nfsd4_readdir *cd = container_of(ccd, struct nfsd4_readdir, common);
 	int buflen;
+	struct dentry *dentry;
 	__be32 *p = cd->buffer;
 	__be32 *cookiep;
 	__be32 nfserr = nfserr_toosmall;
@@ -2268,19 +2314,40 @@ nfsd4_encode_dirent(void *ccdv, const ch
 		return 0;
 	}
 
+	/*
+	 * Do the lookup and make sure the dentry is 
+	 * visible on the exported directory
+	 */
+	dentry = nfsd_check_export(cd, name, namlen);
+	if (IS_ERR(dentry)) {
+		if (PTR_ERR(dentry) == -ENOENT) {
+			cd->common.err = nfs_ok;
+			return 0;
+		}
+		cd->common.err = nfserrno(PTR_ERR(dentry));
+		return -EINVAL;
+	}
+ 
 	if (cd->offset)
 		xdr_encode_hyper(cd->offset, (u64) offset);
 
 	buflen = cd->buflen - 4 - XDR_QUADLEN(namlen);
-	if (buflen < 0)
+	if (buflen < 0) {
+		dput(dentry);
 		goto fail;
+	}
 
 	*p++ = xdr_one;                             /* mark entry present */
 	cookiep = p;
 	p = xdr_encode_hyper(p, NFS_OFFSET_MAX);    /* offset of next entry */
 	p = xdr_encode_array(p, name, namlen);      /* name length & name */
 
-	nfserr = nfsd4_encode_dirent_fattr(cd, name, namlen, p, &buflen);
+	/*
+	 * Note: the dput() on the dentry is done in 
+	 * nfsd4_encode_dirent_fattr() since the dentry can
+	 * change when crossing a mount point.
+	 */
+	nfserr = nfsd4_encode_dirent_fattr(cd, dentry, p, &buflen);
 	switch (nfserr) {
 	case nfs_ok:
 		p += buflen;
diff -up linux-2.6.30.noarch/fs/nfsd/nfsfh.c.save linux-2.6.30.noarch/fs/nfsd/nfsfh.c
--- linux-2.6.30.noarch/fs/nfsd/nfsfh.c.save	2009-07-02 11:34:38.000000000 -0400
+++ linux-2.6.30.noarch/fs/nfsd/nfsfh.c	2009-07-02 11:35:48.000000000 -0400
@@ -109,6 +109,34 @@ static __be32 nfsd_setuser_and_check_por
 	return nfserrno(nfsd_setuser(rqstp, exp));
 }
 
+static inline __be32 check_pseudo_root(struct svc_rqst *rqstp,
+	struct dentry *dentry, struct svc_export *exp)
+{
+	int error;
+
+	/*
+	 * Only interested in pseudo roots
+	 */
+	if (!(exp->ex_flags & NFSEXP_V4ROOT))
+		return nfs_ok;
+
+	/*
+	 * Only directories should be on the pseudo root
+	 */
+	if (unlikely(!S_ISDIR(dentry->d_inode->i_mode)))
+		return nfserr_stale;
+	/*
+	 * Check non-root directories to make sure
+	 * they are truly exported
+	 */
+	if (unlikely(dentry->d_name.len > 1)) {
+		error = nfsd_export_lookup(rqstp, dentry, exp);
+		return nfserrno(error);
+	}
+
+	return nfs_ok;
+}
+
 /*
  * Use the given filehandle to look up the corresponding export and
  * dentry.  On success, the results are used to set fh_export and
@@ -315,6 +343,14 @@ fh_verify(struct svc_rqst *rqstp, struct
 		error = nfsd_setuser_and_check_port(rqstp, exp);
 		if (error)
 			goto out;
+
+		/*
+		 * Do some spoof checking if we are on the pseudo root
+		 */
+		error = check_pseudo_root(rqstp, dentry, exp);
+		if (error)
+			goto out;
+
 	}
 
 	error = nfsd_mode_check(rqstp, dentry->d_inode->i_mode, type);
diff -up linux-2.6.30.noarch/fs/nfsd/vfs.c.save linux-2.6.30.noarch/fs/nfsd/vfs.c
--- linux-2.6.30.noarch/fs/nfsd/vfs.c.save	2009-07-02 11:34:38.000000000 -0400
+++ linux-2.6.30.noarch/fs/nfsd/vfs.c	2009-07-02 11:35:39.000000000 -0400
@@ -89,6 +89,12 @@ struct raparm_hbucket {
 #define RAPARM_HASH_MASK	(RAPARM_HASH_SIZE-1)
 static struct raparm_hbucket	raparm_hash[RAPARM_HASH_SIZE];
 
+static inline int
+nfsd_v4client(struct svc_rqst *rq)
+{
+    return((rq->rq_prog == NFS_PROGRAM) && (rq->rq_vers == 4));
+}
+
 /* 
  * Called from nfsd_lookup and encode_dirent. Check if we have crossed 
  * a mount point.
@@ -115,7 +121,8 @@ nfsd_cross_mnt(struct svc_rqst *rqstp, s
 		path_put(&path);
 		goto out;
 	}
-	if ((exp->ex_flags & NFSEXP_CROSSMOUNT) || EX_NOHIDE(exp2)) {
+	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
@@ -134,6 +141,55 @@ out:
 	return err;
 }
 
+/*
+ * Lookup the export the dentry is on. To be
+ * viewable on an pseudo export, the dentry
+ * has to be an exported directory. 
+ */
+int
+nfsd_export_lookup(struct svc_rqst *rqstp, struct dentry *dentry,
+	struct svc_export *exp)
+{
+	struct svc_export *exp2 = NULL;
+	struct path path;
+	int err = 0;
+
+	if ((exp->ex_flags & NFSEXP_V4ROOT) == 0)
+		return 0;
+
+	/*
+	 * Make sure the export is the parent of the dentry
+	 */
+	if (dentry->d_parent != exp->ex_path.dentry)
+		return 0;
+
+	/*
+	 * Only directories are seen on psuedo exports
+	 */
+	if (!S_ISDIR(dentry->d_inode->i_mode))
+		return -ENOENT;
+
+	/*
+	 * Make the upcall 
+	 */
+	path.mnt = mntget(exp->ex_path.mnt);
+	path.dentry = dget(dentry);
+	while (d_mountpoint(path.dentry) && follow_down(&path));
+
+	exp2 = rqst_exp_get_by_name(rqstp, &path);
+	if (IS_ERR(exp2))
+		err = PTR_ERR(exp2);
+	else  {
+		/*
+		 * The export exist so allow the access
+		 */
+		exp_put(exp2);
+	}
+
+	dput(path.dentry);
+	mntput(path.mnt);
+	return err;
+}
 __be32
 nfsd_lookup_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		   const char *name, unsigned int len,
@@ -143,7 +199,7 @@ nfsd_lookup_dentry(struct svc_rqst *rqst
 	struct dentry		*dparent;
 	struct dentry		*dentry;
 	__be32			err;
-	int			host_err;
+	int			host_err, v4root;
 
 	dprintk("nfsd: nfsd_lookup(fh %s, %.*s)\n", SVCFH_fmt(fhp), len,name);
 
@@ -155,6 +211,7 @@ nfsd_lookup_dentry(struct svc_rqst *rqst
 	dparent = fhp->fh_dentry;
 	exp  = fhp->fh_export;
 	exp_get(exp);
+	v4root = (exp->ex_flags & NFSEXP_V4ROOT);
 
 	/* Lookup the name, but don't follow links */
 	if (isdotent(name, len)) {
@@ -199,9 +256,21 @@ nfsd_lookup_dentry(struct svc_rqst *rqst
 		if (IS_ERR(dentry))
 			goto out_nfserr;
 		/*
+		 * The export is a pseudo one, make sure the
+		 * dentry is accessible 
+		 */
+		v4root = (dentry->d_inode && v4root);
+		if (v4root) {
+			host_err = nfsd_export_lookup(rqstp, dentry, exp);
+			if (host_err) {
+				dput(dentry);
+				goto out_nfserr;
+			}
+		}
+		/*
 		 * check if we have crossed a mount point ...
 		 */
-		if (d_mountpoint(dentry)) {
+		if (d_mountpoint(dentry) || v4root) {
 			if ((host_err = nfsd_cross_mnt(rqstp, &dentry, &exp))) {
 				dput(dentry);
 				goto out_nfserr;
diff -up linux-2.6.30.noarch/include/linux/nfsd/export.h.save linux-2.6.30.noarch/include/linux/nfsd/export.h
--- linux-2.6.30.noarch/include/linux/nfsd/export.h.save	2009-07-02 11:34:38.000000000 -0400
+++ linux-2.6.30.noarch/include/linux/nfsd/export.h	2009-07-02 11:35:22.000000000 -0400
@@ -39,7 +39,8 @@
 #define NFSEXP_FSID		0x2000
 #define	NFSEXP_CROSSMOUNT	0x4000
 #define	NFSEXP_NOACL		0x8000	/* reserved for possible ACL related use */
-#define NFSEXP_ALLFLAGS		0xFE3F
+#define	NFSEXP_V4ROOT		0x10000
+#define NFSEXP_ALLFLAGS		0x1FE3F
 
 /* The flags that may vary depending on security flavor: */
 #define NFSEXP_SECINFO_FLAGS	(NFSEXP_READONLY | NFSEXP_ROOTSQUASH \
diff -up linux-2.6.30.noarch/include/linux/nfsd/nfsd.h.save linux-2.6.30.noarch/include/linux/nfsd/nfsd.h
--- linux-2.6.30.noarch/include/linux/nfsd/nfsd.h.save	2009-07-02 11:34:38.000000000 -0400
+++ linux-2.6.30.noarch/include/linux/nfsd/nfsd.h	2009-07-02 11:35:27.000000000 -0400
@@ -76,6 +76,8 @@ int		nfsd_racache_init(int);
 void		nfsd_racache_shutdown(void);
 int		nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
 		                struct svc_export **expp);
+int		nfsd_export_lookup(struct svc_rqst *rqstp, struct dentry *dpp,
+		                struct svc_export *exp);
 __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 *,




More information about the Fedora-kernel-list mailing list