[PATCH] knfsd: add some new fsid types

Add support for using a filesystem UUID to identify and export point in the
filehandle.

For NFSv2, this UUID is xor-ed down to 4 or 8 bytes so that it doesn't take up
too much room.  For NFSv3+, we use the full 16 bytes, and possibly also a
64bit inode number for exports beneath the root of a filesystem.

When generating an fsid to return in 'stat' information, use the UUID (hashed
down to size) if it is available and a small 'fsid' was not specifically
provided.

Signed-off-by: Neil Brown <neilb@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 49c310b..bb3c314 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -190,18 +190,17 @@
 		       struct cache_head *h)
 {
 	struct svc_expkey *ek ;
+	int i;
 
 	if (h ==NULL) {
 		seq_puts(m, "#domain fsidtype fsid [path]\n");
 		return 0;
 	}
 	ek = container_of(h, struct svc_expkey, h);
-	seq_printf(m, "%s %d 0x%08x", ek->ek_client->name,
-		   ek->ek_fsidtype, ek->ek_fsid[0]);
-	if (ek->ek_fsidtype != 1)
-		seq_printf(m, "%08x", ek->ek_fsid[1]);
-	if (ek->ek_fsidtype == 2)
-		seq_printf(m, "%08x", ek->ek_fsid[2]);
+	seq_printf(m, "%s %d 0x", ek->ek_client->name,
+		   ek->ek_fsidtype);
+	for (i=0; i < key_len(ek->ek_fsidtype)/4; i++)
+		seq_printf(m, "%08x", ek->ek_fsid[i]);
 	if (test_bit(CACHE_VALID, &h->flags) && 
 	    !test_bit(CACHE_NEGATIVE, &h->flags)) {
 		seq_printf(m, " ");
@@ -232,9 +231,8 @@
 	kref_get(&item->ek_client->ref);
 	new->ek_client = item->ek_client;
 	new->ek_fsidtype = item->ek_fsidtype;
-	new->ek_fsid[0] = item->ek_fsid[0];
-	new->ek_fsid[1] = item->ek_fsid[1];
-	new->ek_fsid[2] = item->ek_fsid[2];
+
+	memcpy(new->ek_fsid, item->ek_fsid, sizeof(new->ek_fsid));
 }
 
 static inline void expkey_update(struct cache_head *cnew,
@@ -363,7 +361,7 @@
 					    struct svc_export *old);
 static struct svc_export *svc_export_lookup(struct svc_export *);
 
-static int check_export(struct inode *inode, int flags)
+static int check_export(struct inode *inode, int flags, unsigned char *uuid)
 {
 
 	/* We currently export only dirs and regular files.
@@ -376,12 +374,13 @@
 	/* There are two requirements on a filesystem to be exportable.
 	 * 1:  We must be able to identify the filesystem from a number.
 	 *       either a device number (so FS_REQUIRES_DEV needed)
-	 *       or an FSID number (so NFSEXP_FSID needed).
+	 *       or an FSID number (so NFSEXP_FSID or ->uuid is needed).
 	 * 2:  We must be able to find an inode from a filehandle.
 	 *       This means that s_export_op must be set.
 	 */
 	if (!(inode->i_sb->s_type->fs_flags & FS_REQUIRES_DEV) &&
-	    !(flags & NFSEXP_FSID)) {
+	    !(flags & NFSEXP_FSID) &&
+	    uuid == NULL) {
 		dprintk("exp_export: export of non-dev fs without fsid\n");
 		return -EINVAL;
 	}
@@ -406,10 +405,6 @@
 	int len;
 	int migrated, i, err;
 
-	len = qword_get(mesg, buf, PAGE_SIZE);
-	if (len != 5 || memcmp(buf, "fsloc", 5))
-		return 0;
-
 	/* listsize */
 	err = get_int(mesg, &fsloc->locations_count);
 	if (err)
@@ -520,6 +515,8 @@
 	exp.ex_fslocs.locations_count = 0;
 	exp.ex_fslocs.migrated = 0;
 
+	exp.ex_uuid = NULL;
+
 	/* flags */
 	err = get_int(&mesg, &an_int);
 	if (err == -ENOENT)
@@ -543,12 +540,33 @@
 		if (err) goto out;
 		exp.ex_fsid = an_int;
 
-		err = check_export(nd.dentry->d_inode, exp.ex_flags);
-		if (err) goto out;
+		while ((len = qword_get(&mesg, buf, PAGE_SIZE)) > 0) {
+			if (strcmp(buf, "fsloc") == 0)
+				err = fsloc_parse(&mesg, buf, &exp.ex_fslocs);
+			else if (strcmp(buf, "uuid") == 0) {
+				/* expect a 16 byte uuid encoded as \xXXXX... */
+				len = qword_get(&mesg, buf, PAGE_SIZE);
+				if (len != 16)
+					err  = -EINVAL;
+				else {
+					exp.ex_uuid =
+						kmemdup(buf, 16, GFP_KERNEL);
+					if (exp.ex_uuid == NULL)
+						err = -ENOMEM;
+				}
+			} else
+				/* quietly ignore unknown words and anything
+				 * following. Newer user-space can try to set
+				 * new values, then see what the result was.
+				 */
+				break;
+			if (err)
+				goto out;
+		}
 
-		err = fsloc_parse(&mesg, buf, &exp.ex_fslocs);
-		if (err)
-			goto out;
+		err = check_export(nd.dentry->d_inode, exp.ex_flags,
+				   exp.ex_uuid);
+		if (err) goto out;
 	}
 
 	expp = svc_export_lookup(&exp);
@@ -562,6 +580,8 @@
 	else
 		exp_put(expp);
  out:
+	nfsd4_fslocs_free(&exp.ex_fslocs);
+	kfree(exp.ex_uuid);
  	kfree(exp.ex_path);
 	if (nd.dentry)
 		path_release(&nd);
@@ -591,9 +611,19 @@
 	seq_escape(m, exp->ex_client->name, " \t\n\\");
 	seq_putc(m, '(');
 	if (test_bit(CACHE_VALID, &h->flags) && 
-	    !test_bit(CACHE_NEGATIVE, &h->flags))
+	    !test_bit(CACHE_NEGATIVE, &h->flags)) {
 		exp_flags(m, exp->ex_flags, exp->ex_fsid,
 			  exp->ex_anon_uid, exp->ex_anon_gid, &exp->ex_fslocs);
+		if (exp->ex_uuid) {
+			int i;
+			seq_puts(m, ",uuid=");
+			for (i=0; i<16; i++) {
+				if ((i&3) == 0 && i)
+					seq_putc(m, ':');
+				seq_printf(m, "%02x", exp->ex_uuid[i]);
+			}
+		}
+	}
 	seq_puts(m, ")\n");
 	return 0;
 }
@@ -630,6 +660,8 @@
 	new->ex_anon_uid = item->ex_anon_uid;
 	new->ex_anon_gid = item->ex_anon_gid;
 	new->ex_fsid = item->ex_fsid;
+	new->ex_uuid = item->ex_uuid;
+	item->ex_uuid = NULL;
 	new->ex_path = item->ex_path;
 	item->ex_path = NULL;
 	new->ex_fslocs.locations = item->ex_fslocs.locations;
@@ -752,11 +784,11 @@
 	u32 fsidv[3];
 	
 	if (old_valid_dev(dev)) {
-		mk_fsid_v0(fsidv, dev, ino);
-		return exp_find_key(clp, 0, fsidv, NULL);
+		mk_fsid(FSID_DEV, fsidv, dev, ino, 0, NULL);
+		return exp_find_key(clp, FSID_DEV, fsidv, NULL);
 	}
-	mk_fsid_v3(fsidv, dev, ino);
-	return exp_find_key(clp, 3, fsidv, NULL);
+	mk_fsid(FSID_ENCODE_DEV, fsidv, dev, ino, 0, NULL);
+	return exp_find_key(clp, FSID_ENCODE_DEV, fsidv, NULL);
 }
 
 /*
@@ -767,9 +799,9 @@
 {
 	u32 fsidv[2];
 
-	mk_fsid_v1(fsidv, fsid);
+	mk_fsid(FSID_NUM, fsidv, 0, 0, fsid, NULL);
 
-	return exp_find_key(clp, 1, fsidv, NULL);
+	return exp_find_key(clp, FSID_NUM, fsidv, NULL);
 }
 
 svc_export *
@@ -883,8 +915,8 @@
 	if ((exp->ex_flags & NFSEXP_FSID) == 0)
 		return 0;
 
-	mk_fsid_v1(fsid, exp->ex_fsid);
-	return exp_set_key(clp, 1, fsid, exp);
+	mk_fsid(FSID_NUM, fsid, 0, 0, exp->ex_fsid, NULL);
+	return exp_set_key(clp, FSID_NUM, fsid, exp);
 }
 
 static int exp_hash(struct auth_domain *clp, struct svc_export *exp)
@@ -894,11 +926,11 @@
 	dev_t dev = inode->i_sb->s_dev;
 
 	if (old_valid_dev(dev)) {
-		mk_fsid_v0(fsid, dev, inode->i_ino);
-		return exp_set_key(clp, 0, fsid, exp);
+		mk_fsid(FSID_DEV, fsid, dev, inode->i_ino, 0, NULL);
+		return exp_set_key(clp, FSID_DEV, fsid, exp);
 	}
-	mk_fsid_v3(fsid, dev, inode->i_ino);
-	return exp_set_key(clp, 3, fsid, exp);
+	mk_fsid(FSID_ENCODE_DEV, fsid, dev, inode->i_ino, 0, NULL);
+	return exp_set_key(clp, FSID_ENCODE_DEV, fsid, exp);
 }
 
 static void exp_unhash(struct svc_export *exp)
@@ -977,7 +1009,7 @@
 		goto finish;
 	}
 
-	err = check_export(nd.dentry->d_inode, nxp->ex_flags);
+	err = check_export(nd.dentry->d_inode, nxp->ex_flags, NULL);
 	if (err) goto finish;
 
 	err = -ENOMEM;
@@ -1170,9 +1202,9 @@
 	__be32 rv;
 	u32 fsidv[2];
 
-	mk_fsid_v1(fsidv, 0);
+	mk_fsid(FSID_NUM, fsidv, 0, 0, 0, NULL);
 
-	exp = exp_find(clp, 1, fsidv, creq);
+	exp = exp_find(clp, FSID_NUM, fsidv, creq);
 	if (IS_ERR(exp))
 		return nfserrno(PTR_ERR(exp));
 	if (exp == NULL)