From e91ff0175602cc56f223f1d92de6511099fa40d1 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Mon, 12 Feb 2007 12:25:03 +1100 Subject: [PATCH] Use UUIDs to identify filesystems if kernel supports it. 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 | 3 + support/include/nfslib.h | 1 + support/nfs/cacheio.c | 2 +- support/nfs/exports.c | 34 ++++++-- utils/exportfs/exportfs.c | 2 + utils/exportfs/exports.man | 41 +++++---- utils/mountd/Makefile.am | 2 +- utils/mountd/cache.c | 166 ++++++++++++++++++++++++++++++++++--- 8 files changed, 216 insertions(+), 35 deletions(-) diff --git a/configure.in b/configure.in index 99adee8..59dc869 100644 --- a/configure.in +++ b/configure.in @@ -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 diff --git a/support/include/nfslib.h b/support/include/nfslib.h index aba37c2..13a89da 100644 --- a/support/include/nfslib.h +++ b/support/include/nfslib.h @@ -80,6 +80,7 @@ struct exportent { int e_nsqgids; int e_fsid; char * e_mountpoint; + char * e_uuid; }; struct rmtabent { diff --git a/support/nfs/cacheio.c b/support/nfs/cacheio.c index a4bfedb..4df80a6 100644 --- a/support/nfs/cacheio.c +++ b/support/nfs/cacheio.c @@ -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. diff --git a/support/nfs/exports.c b/support/nfs/exports.c index 9b010dc..0994ea2 100644 --- a/support/nfs/exports.c +++ b/support/nfs/exports.c @@ -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 || diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c index cd49a3b..2e2b6f3 100644 --- a/utils/exportfs/exportfs.c +++ b/utils/exportfs/exportfs.c @@ -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]?"=":"", diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man index 892533b..3aa8de8 100644 --- a/utils/exportfs/exports.man +++ b/utils/exportfs/exports.man @@ -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 diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am index dd400fd..c8500cb 100644 --- a/utils/mountd/Makefile.am +++ b/utils/mountd/Makefile.am @@ -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 diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c index 37e7160..6947203 100644 --- a/utils/mountd/cache.c +++ b/utils/mountd/cache.c @@ -27,6 +27,20 @@ #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); } -- 2.39.2