diff --git a/support/export/export.c b/support/export/export.c index e1bebce..0b8a858 100644 --- a/support/export/export.c +++ b/support/export/export.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "xmalloc.h" #include "nfslib.h" #include "exportfs.h" @@ -96,6 +98,69 @@ export_read(char *fname) } /** + * export_d_read - read entries from /etc/exports. + * @fname: name of directory to read from + * + * Returns number of read entries. + * Based on mnt_table_parse_dir() in + * util-linux-ng/shlibs/mount/src/tab_parse.c + */ +int +export_d_read(const char *dname) +{ + int n = 0, i; + struct dirent **namelist = NULL; + int volumes = 0; + + + n = scandir(dname, &namelist, NULL, versionsort); + if (n < 0) { + if (errno == ENOENT) + /* Silently return */ + return volumes; + xlog(L_NOTICE, "scandir %s: %s", dname, strerror(errno)); + } else if (n == 0) + return volumes; + + for (i = 0; i < n; i++) { + struct dirent *d = namelist[i]; + size_t namesz; + char fname[PATH_MAX + 1]; + int fname_len; + + + if (d->d_type != DT_UNKNOWN + && d->d_type != DT_REG + && d->d_type != DT_LNK) + continue; + if (*d->d_name == '.') + continue; + +#define _EXT_EXPORT_SIZ (sizeof(_EXT_EXPORT) - 1) + namesz = strlen(d->d_name); + if (!namesz + || namesz < _EXT_EXPORT_SIZ + 1 + || strcmp(d->d_name + (namesz - _EXT_EXPORT_SIZ), + _EXT_EXPORT)) + continue; + + fname_len = snprintf(fname, PATH_MAX +1, "%s/%s", dname, d->d_name); + if (fname_len > PATH_MAX) { + xlog(L_WARNING, "Too long file name: %s in %s", d->d_name, dname); + continue; + } + + volumes += export_read(fname); + } + + for (i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + + return volumes; +} + +/** * export_create - create an in-core nfs_export record from an export entry * @xep: export entry to lookup * @canonical: if set, e_hostname is known to be canonical DNS name diff --git a/support/include/exportfs.h b/support/include/exportfs.h index 4cac203..f033329 100644 --- a/support/include/exportfs.h +++ b/support/include/exportfs.h @@ -135,6 +135,7 @@ int client_member(const char *client, const char *name); int export_read(char *fname); +int export_d_read(const char *dname); void export_reset(nfs_export *); nfs_export * export_lookup(char *hname, char *path, int caconical); nfs_export * export_find(const struct addrinfo *ai, diff --git a/systemd/Makefile.am b/systemd/Makefile.am index 03f96e9..49c9b8d 100644 --- a/systemd/Makefile.am +++ b/systemd/Makefile.am @@ -39,8 +39,16 @@ endif EXTRA_DIST = $(unit_files) unit_dir = /usr/lib/systemd/system +generator_dir = /usr/lib/systemd/system-generators + +EXTRA_PROGRAMS = nfs-server-generator +genexecdir = $(generator_dir) +nfs_server_generator_LDADD = ../support/export/libexport.a \ + ../support/nfs/libnfs.a \ + ../support/misc/libmisc.a if INSTALL_SYSTEMD +genexec_PROGRAMS = nfs-server-generator install-data-hook: $(unit_files) mkdir -p $(DESTDIR)/$(unitdir) cp $(unit_files) $(DESTDIR)/$(unitdir) diff --git a/systemd/nfs-server-generator.c b/systemd/nfs-server-generator.c new file mode 100644 index 0000000..af8bb52 --- /dev/null +++ b/systemd/nfs-server-generator.c @@ -0,0 +1,144 @@ +/* + * nfs-server-generator: + * systemd generator to create ordering dependencies between + * nfs-server and various filesystem mounts + * + * 1/ nfs-server should start Before any 'nfs' mountpoints are + * mounted, in case they are loop-back mounts. This ordering is particularly + * important for the shutdown side, so the nfs-server is stopped + * after the filesystems are unmounted. + * 2/ nfs-server should start After all exported filesystems are mounted + * so there is no risk of exporting the underlying directory. + * This is particularly important for _net mounts which + * are not caught by "local-fs.target". + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "nfslib.h" +#include "exportfs.h" + +/* A simple "set of strings" to remove duplicates + * found in /etc/exports + */ +struct list { + struct list *next; + char *name; +}; +static int is_unique(struct list **lp, char *path) +{ + struct list *l = *lp; + + while (l) { + if (strcmp(l->name, path) == 0) + return 0; + l = l->next; + } + l = malloc(sizeof(*l)); + l->name = path; + l->next = *lp; + *lp = l; + return 1; +} + +/* We need to convert a path name to a systemd unit + * name. This requires some translation ('/' -> '-') + * and some escaping. + */ +static void systemd_escape(FILE *f, char *path) +{ + while (*path == '/') + path++; + if (!*path) { + /* "/" becomes "-", otherwise leading "/" is ignored */ + fputs("-", f); + return; + } + while (*path) { + char c = *path++; + + if (c == '/') { + /* multiple non-trailing slashes become '-' */ + while (*path == '/') + path++; + if (*path) + fputs("-", f); + } else if (isalnum(c) || c == ':' || c == '.') + fputc(c, f); + else + fprintf(f, "\\x%02x", c & 0xff); + } +} + +int main(int argc, char *argv[]) +{ + char *path; + char dirbase[] = "/nfs-server.service.d"; + char filebase[] = "/order-with-mounts.conf"; + nfs_export *exp; + int i; + struct list *list = NULL; + FILE *f, *fstab; + struct mntent *mnt; + + if (argc != 4 || argv[1][0] != '/') { + fprintf(stderr, "nfs-server-generator: create systemd dependencies for nfs-server\n"); + fprintf(stderr, "Usage: normal-dir early-dir late-dir\n"); + exit(1); + } + + path = malloc(strlen(argv[1]) + sizeof(dirbase) + sizeof(filebase)); + if (!path) + exit(2); + if (export_read(_PATH_EXPORTS) + + export_d_read(_PATH_EXPORTS_D) == 0) + /* Nothing is exported, so nothing to do */ + exit(0); + + strcat(strcpy(path, argv[1]), dirbase); + mkdir(path, 0755); + strcat(path, filebase); + f = fopen(path, "w"); + if (!f) + return 1; + fprintf(f, "# Automatically generated by nfs-server-generator\n\n[Unit]\n"); + + for (i = 0; i < MCL_MAXTYPES; i++) { + for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { + if (!is_unique(&list, exp->m_export.e_path)) + continue; + if (strchr(exp->m_export.e_path, ' ')) + fprintf(f, "RequiresMountsFor=\"%s\"\n", + exp->m_export.e_path); + else + fprintf(f, "RequiresMountsFor=%s\n", + exp->m_export.e_path); + } + } + + fstab = setmntent("/etc/fstab", "r"); + while ((mnt = getmntent(fstab)) != NULL) { + if (strcmp(mnt->mnt_type, "nfs") != 0 && + strcmp(mnt->mnt_type, "nfs4") != 0) + continue; + fprintf(f, "Before= "); + systemd_escape(f, mnt->mnt_dir); + fprintf(f, ".mount\n"); + } + + fclose(f); + + exit(0); +} diff --git a/systemd/nfs-server.service b/systemd/nfs-server.service index 2ccdc63..196c818 100644 --- a/systemd/nfs-server.service +++ b/systemd/nfs-server.service @@ -16,9 +16,6 @@ Before= rpc-statd-notify.service Wants=auth-rpcgss-module.service After=rpc-gssd.service gssproxy.service rpc-svcgssd.service -# start/stop server before/after client -Before=remote-fs-pre.target - Wants=nfs-config.service After=nfs-config.service diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c index a00b5ea..4ac2c15 100644 --- a/utils/exportfs/exportfs.c +++ b/utils/exportfs/exportfs.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include @@ -47,7 +46,6 @@ static void error(nfs_export *exp, int err); static void usage(const char *progname, int n); static void validate_export(nfs_export *exp); static int matchhostname(const char *hostname1, const char *hostname2); -static int export_d_read(const char *dname); static void grab_lockfile(void); static void release_lockfile(void); @@ -700,63 +698,6 @@ out: return result; } -/* Based on mnt_table_parse_dir() in - util-linux-ng/shlibs/mount/src/tab_parse.c */ -static int -export_d_read(const char *dname) -{ - int n = 0, i; - struct dirent **namelist = NULL; - int volumes = 0; - - - n = scandir(dname, &namelist, NULL, versionsort); - if (n < 0) { - if (errno == ENOENT) - /* Silently return */ - return volumes; - xlog(L_NOTICE, "scandir %s: %s", dname, strerror(errno)); - } else if (n == 0) - return volumes; - - for (i = 0; i < n; i++) { - struct dirent *d = namelist[i]; - size_t namesz; - char fname[PATH_MAX + 1]; - int fname_len; - - - if (d->d_type != DT_UNKNOWN - && d->d_type != DT_REG - && d->d_type != DT_LNK) - continue; - if (*d->d_name == '.') - continue; - -#define _EXT_EXPORT_SIZ (sizeof(_EXT_EXPORT) - 1) - namesz = strlen(d->d_name); - if (!namesz - || namesz < _EXT_EXPORT_SIZ + 1 - || strcmp(d->d_name + (namesz - _EXT_EXPORT_SIZ), - _EXT_EXPORT)) - continue; - - fname_len = snprintf(fname, PATH_MAX +1, "%s/%s", dname, d->d_name); - if (fname_len > PATH_MAX) { - xlog(L_WARNING, "Too long file name: %s in %s", d->d_name, dname); - continue; - } - - volumes += export_read(fname); - } - - for (i = 0; i < n; i++) - free(namelist[i]); - free(namelist); - - return volumes; -} - static char dumpopt(char c, char *fmt, ...) { diff --git a/utils/idmapd/idmapd.man b/utils/idmapd/idmapd.man index b9200c7..d4ab894 100644 --- a/utils/idmapd/idmapd.man +++ b/utils/idmapd/idmapd.man @@ -23,6 +23,29 @@ is the NFSv4 ID <-> name mapping daemon. It provides functionality to the NFSv4 kernel client and server, to which it communicates via upcalls, by translating user and group IDs to names, and vice versa. .Pp +The system derives the +.I user +part of the string by performing a password or group lookup. +The lookup mechanism is configured in +.Pa /etc/idmapd.conf +.Pp +By default, the +.I domain +part of the string is the system's DNS domain name. +It can also be specified in +.Pa /etc/idmapd.conf +if the system is multi-homed, +or if the system's DNS domain name does +not match the name of the system's Kerberos realm. +.Pp +When the domain is not specified in /etc/idmapd.conf +the local DNS server will be queried for the +.Sy _nfsv4idmapdomain +text record. If the record exists +that will be used as the domain. When the record +does not exist, the domain part of the DNS domain +will used. +.Pp Note that on more recent kernels only the NFSv4 server uses .Nm . The NFSv4 client instead uses diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c index 9de6794..d5dfb5e 100644 --- a/utils/mount/stropts.c +++ b/utils/mount/stropts.c @@ -948,6 +948,7 @@ static int nfs_is_permanent_error(int error) case ETIMEDOUT: case ECONNREFUSED: case EHOSTUNREACH: + case EOPNOTSUPP: /* aka RPC_PROGNOTREGISTERED */ case EAGAIN: return 0; /* temporary */ default: @@ -1019,8 +1020,7 @@ static int nfsmount_parent(struct nfsmount_info *mi) if (nfs_try_mount(mi)) return EX_SUCCESS; - /* retry background mounts when the server is not up */ - if (nfs_is_permanent_error(errno) && errno != EOPNOTSUPP) { + if (nfs_is_permanent_error(errno)) { mount_error(mi->spec, mi->node, errno); return EX_FAIL; } @@ -1055,8 +1055,7 @@ static int nfsmount_child(struct nfsmount_info *mi) if (nfs_try_mount(mi)) return EX_SUCCESS; - /* retry background mounts when the server is not up */ - if (nfs_is_permanent_error(errno) && errno != EOPNOTSUPP) + if (nfs_is_permanent_error(errno)) break; if (time(NULL) > timeout) diff --git a/utils/nfsidmap/nfsidmap.man b/utils/nfsidmap/nfsidmap.man index 2f17cf2..2af16f3 100644 --- a/utils/nfsidmap/nfsidmap.man +++ b/utils/nfsidmap/nfsidmap.man @@ -39,6 +39,15 @@ if the system is multi-homed, or if the system's DNS domain name does not match the name of the system's Kerberos realm. .PP +When the domain is not specified in +.I /etc/idmapd.conf +the local DNS server will be queried for the +.I _nfsv4idmapdomain +text record. If the record exists +that will be used as the domain. When the record +does not exist, the domain part of the DNS domain +will used. +.PP The .I /usr/sbin/nfsidmap program performs translations on behalf of the kernel.