Use UUIDs to identify filesystems if kernel supports it.
authorNeil Brown <neilb@suse.de>
Mon, 12 Feb 2007 01:25:03 +0000 (12:25 +1100)
committerNeil Brown <neilb@suse.de>
Mon, 12 Feb 2007 01:25:03 +0000 (12:25 +1100)
This introduces a new dependancy on libblkid.
If a filesystem being exported has a UUID that libblkid
can extract, then that is passed to the kernel for use
in identifying the filesystem in filehandles.
This means that 'fsid=' is no longer needed to work around the
problem of device numbers changing.
fsid= is still needed for fielsystems that have no device,
and can now be given  16byute uuid instead of just a 32bit one.

configure.in
support/include/nfslib.h
support/nfs/cacheio.c
support/nfs/exports.c
utils/exportfs/exportfs.c
utils/exportfs/exports.man
utils/mountd/Makefile.am
utils/mountd/cache.c

index 99adee8..59dc869 100644 (file)
@@ -175,9 +175,12 @@ fi
 if test "$knfsd_cv_glibc2" = no; then
     AC_CHECK_LIB(bsd, daemon, [LIBBSD="-lbsd"])
 fi
+AC_CHECK_LIB(blkid, blkid_get_cache, [LIBBLKID="-lblkid"], AC_MSG_ERROR([libblkid needed]))
+AC_CHECK_HEADER(blkid/blkid.h, , AC_MSG_ERROR([Cannot file libblkid header file blkid/blkid.h]))
 AC_SUBST(LIBSOCKET)
 AC_SUBST(LIBCRYPT)
 AC_SUBST(LIBBSD)
+AC_SUBST(LIBBLKID)
 
 if test "$enable_gss" = yes; then
   dnl 'gss' also depends on nfsidmap.h - at least for svcgssd_proc.c
index aba37c2..13a89da 100644 (file)
@@ -80,6 +80,7 @@ struct exportent {
        int             e_nsqgids;
        int             e_fsid;
        char *          e_mountpoint;
+       char *          e_uuid;
 };
 
 struct rmtabent {
index a4bfedb..4df80a6 100644 (file)
@@ -249,7 +249,7 @@ cache_flush(int force)
        char path[200];
        time_t now;
        /* Note: the order of these caches is important.
-        * The need to be flushed in dependancy order. So
+        * They need to be flushed in dependancy order. So
         * a cache that references items in another cache,
         * as nfsd.fh entries reference items in nfsd.export,
         * must be flushed before the cache that it references.
index 9b010dc..0994ea2 100644 (file)
@@ -220,6 +220,8 @@ putexportent(struct exportent *ep)
        if (ep->e_flags & NFSEXP_FSID) {
                fprintf(fp, "fsid=%d,", ep->e_fsid);
        }
+       if (ep->e_uuid)
+               fprintf(fp, "fsid=%s,", ep->e_uuid);
        if (ep->e_mountpoint)
                fprintf(fp, "mountpoint%s%s,",
                        ep->e_mountpoint[0]?"=":"", ep->e_mountpoint);
@@ -302,6 +304,7 @@ mkexportent(char *hname, char *path, char *options)
        ee.e_mountpoint = NULL;
        ee.e_nsquids = 0;
        ee.e_nsqgids = 0;
+       ee.e_uuid = NULL;
 
        if (strlen(hname) >= sizeof(ee.e_hostname)) {
                xlog(L_WARNING, "client name %s too long", hname);
@@ -330,6 +333,17 @@ updateexportent(struct exportent *eep, char *options)
        return 1;
 }
 
+
+static int valid_uuid(char *uuid)
+{
+       /* must have 32 hex digits */
+       int cnt;
+       for (cnt = 0 ; *uuid; uuid++)
+               if (isxdigit(*uuid))
+                       cnt++;
+       return cnt == 32;
+}
+
 /*
  * Parse option string pointed to by cp and set mount options accordingly.
  */
@@ -445,13 +459,21 @@ bad_option:
                        }
                } else if (strncmp(opt, "fsid=", 5) == 0) {
                        char *oe;
-                       ep->e_fsid = strtoul(opt+5, &oe, 0);
-                       if (opt[5]=='\0' || *oe != '\0') {
-                               xlog(L_ERROR, "%s: %d: bad fsid \"%s\"\n",
-                                    flname, flline, opt);      
-                               goto bad_option;
+                       if (strcmp(opt+5, "root") == 0) {
+                               ep->e_fsid = 0;
+                               ep->e_flags |= NFSEXP_FSID;
+                       } else {
+                               ep->e_fsid = strtoul(opt+5, &oe, 0);
+                               if (opt[5]!='\0' && *oe == '\0') 
+                                       ep->e_flags |= NFSEXP_FSID;
+                               else if (valid_uuid(opt+5))
+                                       ep->e_uuid = strdup(opt+7);
+                               else {
+                                       xlog(L_ERROR, "%s: %d: bad fsid \"%s\"\n",
+                                            flname, flline, opt);      
+                                       goto bad_option;
+                               }
                        }
-                       ep->e_flags |= NFSEXP_FSID;
                } else if (strcmp(opt, "mountpoint")==0 ||
                           strcmp(opt, "mp") == 0 ||
                           strncmp(opt, "mountpoint=", 11)==0 ||
index cd49a3b..2e2b6f3 100644 (file)
@@ -404,6 +404,8 @@ dump(int verbose)
                                c = dumpopt(c, "no_acl");
                        if (ep->e_flags & NFSEXP_FSID)
                                c = dumpopt(c, "fsid=%d", ep->e_fsid);
+                       if (ep->e_uuid)
+                               c = dumpopt(c, "fsid=%s", ep->e_uuid);
                        if (ep->e_mountpoint)
                                c = dumpopt(c, "mountpoint%s%s", 
                                            ep->e_mountpoint[0]?"=":"", 
index 892533b..3aa8de8 100644 (file)
@@ -306,26 +306,33 @@ then the nominated path must be a mountpoint for the exportpoint to be
 exported.
 
 .TP
-.IR fsid= num
-This option forces the filesystem identification portion of the file
-handle and file attributes used on the wire to be
-.I num
-instead of a number derived from the major and minor number of the
-block device on which the filesystem is mounted.  Any 32 bit number
-can be used, but it must be unique amongst all the exported filesystems.
+.IR fsid= num|root|uuid
+NFS needs to be able to identify each filesystem that it exports.
+Normally it will use a UUID for the filesystem (if the filesystem has
+such a thing) or the device number of the device holding the
+filesystem (if the filesystem is stored on the device).
 
-This can be useful for NFS failover, to ensure that both servers of
-the failover pair use the same NFS file handles for the shared filesystem
-thus avoiding stale file handles after failover.
+As not all filesystems are stored on devices, and not all filesystems
+have UUIDs, it is sometimes necessary to explicitly tell NFS how to
+identify a filesystem.  This is done with the
+.I fsid=
+option.
 
-Some Linux filesystems are not mounted on a block device; exporting
-these via NFS requires the use of the
-.I fsid
-option (although that may still not be enough).
+For NFSv4, there is a distinguished filesystem which is the root of
+all exported filesystem.  This is specified with
+.I fsid=root
+or
+.I fsid=0
+both of which mean exactly the same thing.
+
+Other filesystems can be identified with a small integer, or a UUID
+which should contain 32 hex digits and arbitrary punctuation.
 
-The value  0 has a special meaning when use with NFSv4.  NFSv4 has a
-concept of a root of the overall exported filesystem. The export point
-exported with fsid=0 will be used as this root.
+Linux kernels version 2.6.20 and earlier do not understand the UUID
+setting so a small integer must be used if an fsid option needs to be
+set for such kernels.  Setting both a small number and a UUID is
+supported so the same configuration can be made to work on old and new
+kernels alike.
 
 .SS User ID Mapping
 .PP
index dd400fd..c8500cb 100644 (file)
@@ -12,7 +12,7 @@ mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \
 mountd_LDADD = ../../support/export/libexport.a \
               ../../support/nfs/libnfs.a \
               ../../support/misc/libmisc.a \
-              $(LIBBSD) $(LIBWRAP) $(LIBNSL)
+              $(LIBBSD) $(LIBWRAP) $(LIBNSL) $(LIBBLKID)
 mountd_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) \
                  -I$(top_builddir)/support/include \
                  -I$(top_srcdir)/support/export
index 37e7160..6947203 100644 (file)
 #include "mountd.h"
 #include "xmalloc.h"
 
+#include "blkid/blkid.h"
+
+
+enum nfsd_fsid {
+       FSID_DEV = 0,
+       FSID_NUM,
+       FSID_MAJOR_MINOR,
+       FSID_ENCODE_DEV,
+       FSID_UUID4_INUM,
+       FSID_UUID8,
+       FSID_UUID16,
+       FSID_UUID16_INUM,
+};
+
 /*
  * Support routines for text-based upcalls.
  * Fields are separated by spaces.
@@ -87,6 +101,72 @@ void auth_unix_ip(FILE *f)
        
 }
 
+int get_uuid(char *path, char *uuid, int uuidlen, char *u)
+{
+       /* extract hex digits from uuidstr and compose a uuid
+        * of the given length (max 16), xoring bytes to make
+        * a smaller uuid.  Then compare with uuid
+        */
+       int i = 0;
+       const char *val;
+
+       if (path) {
+               static blkid_cache cache = NULL;
+               struct stat stb;
+               char *devname;
+               blkid_tag_iterate iter;
+               blkid_dev dev;
+               const char *type;
+               if (cache == NULL)
+                       blkid_get_cache(&cache, NULL);
+
+               blkid_probe_all_new(cache);
+
+               if (stat(path, &stb) != 0)
+                       return 0;
+               devname = blkid_devno_to_devname(stb.st_dev);
+               if (!devname)
+                       return 0;
+               dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
+               free(devname);
+               if (!dev)
+                       return 0;
+               iter = blkid_tag_iterate_begin(dev);
+               if (!iter)
+                       return 0;
+               while (blkid_tag_next(iter, &type, &val) == 0)
+                       if (strcmp(type, "UUID") == 0)
+                               break;
+               blkid_tag_iterate_end(iter);
+               if (!type)
+                       return 0;
+       } else {
+               val = uuid;
+       }
+       
+       memset(u, 0, uuidlen);
+       for ( ; *val ; val++) {
+               char c = *val;
+               if (!isxdigit(c))
+                       continue;
+               if (isalpha(c)) {
+                       if (isupper(c))
+                               c = c - 'A' + 10;
+                       else
+                               c = c - 'a' + 10;
+               } else
+                       c = c - '0' + 0;
+               if ((i&1) == 0)
+                       c <<= 4;
+               u[i/2] ^= c;
+               i++;
+               if (i == uuidlen*2)
+                       i = 0;
+       }
+       return 1;
+}
+       
+
 void nfsd_fh(FILE *f)
 {
        /* request are:
@@ -100,12 +180,15 @@ void nfsd_fh(FILE *f)
        int fsidlen;
        unsigned int dev, major=0, minor=0;
        unsigned int inode=0;
+       unsigned long long inode64;
        unsigned int fsidnum=0;
        char fsid[32];
        struct exportent *found = NULL;
        nfs_export *exp;
        int i;
        int dev_missing = 0;
+       int uuidlen = 0;
+       char *fhuuid = NULL;
 
        if (readline(fileno(f), &lbuf, &lbuflen) != 1)
                return;
@@ -119,12 +202,12 @@ void nfsd_fh(FILE *f)
                goto out;
        if (qword_get_int(&cp, &fsidtype) != 0)
                goto out;
-       if (fsidtype < 0 || fsidtype > 3)
+       if (fsidtype < 0 || fsidtype > 7)
                goto out; /* unknown type */
        if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0)
                goto out;
        switch(fsidtype) {
-       case 0: /* 4 bytes: 2 major, 2 minor, 4 inode */
+       case FSID_DEV: /* 4 bytes: 2 major, 2 minor, 4 inode */
                if (fsidlen != 8)
                        goto out;
                memcpy(&dev, fsid, 4);
@@ -133,13 +216,13 @@ void nfsd_fh(FILE *f)
                minor = ntohl(dev) & 0xFFFF;
                break;
 
-       case 1: /* 4 bytes - fsid */
+       case FSID_NUM: /* 4 bytes - fsid */
                if (fsidlen != 4)
                        goto out;
                memcpy(&fsidnum, fsid, 4);
                break;
 
-       case 2: /* 12 bytes: 4 major, 4 minor, 4 inode 
+       case FSID_MAJOR_MINOR: /* 12 bytes: 4 major, 4 minor, 4 inode 
                 * This format is never actually used but was
                 * an historical accident
                 */
@@ -150,7 +233,7 @@ void nfsd_fh(FILE *f)
                memcpy(&inode, fsid+8, 4);
                break;
 
-       case 3: /* 8 bytes: 4 byte packed device number, 4 inode */
+       case FSID_ENCODE_DEV: /* 8 bytes: 4 byte packed device number, 4 inode */
                /* This is *host* endian, not net-byte-order, because
                 * no-one outside this host has any business interpreting it
                 */
@@ -162,6 +245,33 @@ void nfsd_fh(FILE *f)
                minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
                break;
 
+       case FSID_UUID4_INUM: /* 4 byte inode number and 4 byte uuid */
+               if (fsidlen != 8)
+                       goto out;
+               memcpy(&inode, fsid, 4);
+               uuidlen = 4;
+               fhuuid = fsid+4;
+               break;
+       case FSID_UUID8: /* 8 byte uuid */
+               if (fsidlen != 8)
+                       goto out;
+               uuidlen = 8;
+               fhuuid = fsid;
+               break;
+       case FSID_UUID16: /* 16 byte uuid */
+               if (fsidlen != 16)
+                       goto out;
+               uuidlen = 16;
+               fhuuid = fsid;
+               break;
+       case FSID_UUID16_INUM: /* 8 byte inode number and 16 byte uuid */
+               if (fsidlen != 24)
+                       goto out;
+               memcpy(&inode64, fsid, 8);
+               inode = inode64;
+               uuidlen = 16;
+               fhuuid = fsid+8;
+               break;
        }
 
        auth_reload();
@@ -170,6 +280,7 @@ void nfsd_fh(FILE *f)
        for (i=0 ; i < MCL_MAXTYPES; i++) {
                for (exp = exportlist[i]; exp; exp = exp->m_next) {
                        struct stat stb;
+                       char u[16];                     
 
                        if (!client_member(dom, exp->m_client->m_hostname))
                                continue;
@@ -180,16 +291,41 @@ void nfsd_fh(FILE *f)
                                dev_missing ++;
                        if (stat(exp->m_export.e_path, &stb) != 0)
                                continue;
-                       if (fsidtype == 1 &&
-                           ((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
-                            exp->m_export.e_fsid != fsidnum))
-                               continue;
-                       if (fsidtype != 1) {
+                       switch(fsidtype){
+                       case FSID_DEV:
+                       case FSID_MAJOR_MINOR:
+                       case FSID_ENCODE_DEV:
                                if (stb.st_ino != inode)
                                        continue;
                                if (major != major(stb.st_dev) ||
                                    minor != minor(stb.st_dev))
                                        continue;
+                               break;
+                       case FSID_NUM:
+                               if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
+                                    exp->m_export.e_fsid != fsidnum))
+                                       continue;
+                               break;
+                       case FSID_UUID4_INUM:
+                       case FSID_UUID16_INUM:
+                               if (stb.st_ino != inode)
+                                       continue;
+                               goto check_uuid;
+                       case FSID_UUID8:
+                       case FSID_UUID16:
+                               if (!is_mountpoint(exp->m_export.e_path))
+                                       continue;
+                       check_uuid:
+                               if (exp->m_export.e_uuid)
+                                       get_uuid(NULL, exp->m_export.e_uuid,
+                                                uuidlen, u);
+                               else if (get_uuid(exp->m_export.e_path, NULL,
+                                                 uuidlen, u) == 0)
+                                       continue;
+
+                               if (memcmp(u, fhuuid, uuidlen) != 0)
+                                       continue;
+                               break;
                        }
                        /* It's a match !! */
                        if (!found)
@@ -246,6 +382,16 @@ static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *ex
                qword_printint(f, exp->e_anonuid);
                qword_printint(f, exp->e_anongid);
                qword_printint(f, exp->e_fsid);
+               if (exp->e_uuid == NULL) {
+                       char u[16];
+                       if (get_uuid(exp->e_path, NULL, 16, u)) {
+                               qword_print(f, "uuid");
+                               qword_printhex(f, u, 16);
+                       }
+               } else if (exp->e_uuid) {
+                       qword_print(f, "uuid");
+                       qword_printhex(f, exp->e_uuid, 16);
+               }
        }
        return qword_eol(f);
 }