diff --git a/SOURCES/nfs-utils-1.3.0-blkmapd-loop.patch b/SOURCES/nfs-utils-1.3.0-blkmapd-loop.patch new file mode 100644 index 0000000..b8ca230 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-blkmapd-loop.patch @@ -0,0 +1,35 @@ +commit c6b8191374d9ad064eb96423400a6314c2d0102e +Author: Kinglong Mee +Date: Tue Jun 30 14:12:38 2015 -0400 + + blkmapd: Fix infinite loop when reading serial + + If (dev_id->ids & 0xf) < current_id, must updates pos when continue. + Otherwise an infinite loop. + + No other places use the pos value, just move to the top of while. + + Signed-off-by: Kinglong Mee + Signed-off-by: Steve Dickson + +diff --git a/utils/blkmapd/device-inq.c b/utils/blkmapd/device-inq.c +index c5bf71f..6b56b67 100644 +--- a/utils/blkmapd/device-inq.c ++++ b/utils/blkmapd/device-inq.c +@@ -196,6 +196,8 @@ struct bl_serial *bldev_read_serial(int fd, const char *filename) + + while (pos < (len - devid_len)) { + dev_id = (struct bl_dev_id *)&(dev_root->data[pos]); ++ pos += (dev_id->len + devid_len); ++ + if ((dev_id->ids & 0xf) < current_id) + continue; + switch (dev_id->ids & 0xf) { +@@ -226,7 +228,6 @@ struct bl_serial *bldev_read_serial(int fd, const char *filename) + } + if (current_id == 3) + break; +- pos += (dev_id->len + devid_len); + } + out: + if (!serial_out) diff --git a/SOURCES/nfs-utils-1.3.0-blkmapd-pnfs.patch b/SOURCES/nfs-utils-1.3.0-blkmapd-pnfs.patch new file mode 100644 index 0000000..e1c8e3e --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-blkmapd-pnfs.patch @@ -0,0 +1,354 @@ +diff --git a/Makefile.am b/Makefile.am +index ae7cd16..c9e9f87 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -2,7 +2,7 @@ + + AUTOMAKE_OPTIONS = foreign + +-SUBDIRS = tools support utils linux-nfs tests ++SUBDIRS = tools support utils linux-nfs tests systemd + + MAINTAINERCLEANFILES = Makefile.in + +diff --git a/configure.ac b/configure.ac +index 7b93de6..4ee4db5 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -54,6 +54,16 @@ AC_ARG_WITH(start-statd, + ) + AC_SUBST(startstatd) + AC_DEFINE_UNQUOTED(START_STATD, "$startstatd", [Define this to a script which can start statd on mount]) ++unitdir=/usr/lib/systemd/system ++AC_ARG_WITH(systemd, ++ [AC_HELP_STRING([--with-systemd@<:@=unit-dir-path@:>@], ++ [install systemd unit files @<:@Default: no, and path defaults to /usr/lib/systemd/system if not given@:>@])], ++ test "$withval" = "no" && use_systemd=0 || unitdir=$withval use_systemd=1 ++ use_systemd=0 ++ ) ++ AM_CONDITIONAL(INSTALL_SYSTEMD, [test "$use_systemd" = 1]) ++ AC_SUBST(unitdir) ++ + AC_ARG_ENABLE(nfsv4, + [AC_HELP_STRING([--enable-nfsv4], + [enable support for NFSv4 @<:@default=yes@:>@])], +@@ -506,6 +516,7 @@ AC_CONFIG_FILES([ + utils/showmount/Makefile + utils/statd/Makefile + utils/osd_login/Makefile ++ systemd/Makefile + tests/Makefile + tests/nsm_client/Makefile]) + AC_OUTPUT +diff --git a/support/include/nfs/export.h b/support/include/nfs/export.h +index 2f59e6a..1194255 100644 +--- a/support/include/nfs/export.h ++++ b/support/include/nfs/export.h +@@ -26,6 +26,7 @@ + #define NFSEXP_CROSSMOUNT 0x4000 + #define NFSEXP_NOACL 0x8000 /* reserved for possible ACL related use */ + #define NFSEXP_V4ROOT 0x10000 ++#define NFSEXP_PNFS 0x20000 + /* + * All flags supported by the kernel before addition of the + * export_features interface: +diff --git a/support/nfs/exports.c b/support/nfs/exports.c +index 5451ed7..9399a12 100644 +--- a/support/nfs/exports.c ++++ b/support/nfs/exports.c +@@ -275,6 +275,7 @@ putexportent(struct exportent *ep) + "no_" : ""); + if (ep->e_flags & NFSEXP_NOREADDIRPLUS) + fprintf(fp, "nordirplus,"); ++ fprintf(fp, "%spnfs,", (ep->e_flags & NFSEXP_PNFS)? "" : "no_"); + if (ep->e_flags & NFSEXP_FSID) { + fprintf(fp, "fsid=%d,", ep->e_fsid); + } +@@ -581,6 +582,10 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) + clearflags(NFSEXP_NOACL, active, ep); + else if (strcmp(opt, "no_acl") == 0) + setflags(NFSEXP_NOACL, active, ep); ++ else if (!strcmp(opt, "pnfs")) ++ setflags(NFSEXP_PNFS, active, ep); ++ else if (!strcmp(opt, "no_pnfs")) ++ clearflags(NFSEXP_PNFS, active, ep); + else if (strncmp(opt, "anonuid=", 8) == 0) { + char *oe; + ep->e_anonuid = strtol(opt+8, &oe, 10); +diff --git a/systemd/Makefile.am b/systemd/Makefile.am +new file mode 100644 +index 0000000..fbcabb1 +--- /dev/null ++++ b/systemd/Makefile.am +@@ -0,0 +1,31 @@ ++## Process this file with automake to produce Makefile.in ++ ++MAINTAINERCLEANFILES = Makefile.in ++ ++unit_files = \ ++ nfs-client.target \ ++ \ ++ auth-rpcgss-module.service \ ++ nfs-blkmap.service \ ++ nfs-config.service \ ++ nfs-idmapd.service \ ++ nfs-mountd.service \ ++ nfs-server.service \ ++ nfs-utils.service \ ++ rpc-gssd.service \ ++ rpc-statd-notify.service \ ++ rpc-statd.service \ ++ rpc-svcgssd.service \ ++ \ ++ proc-fs-nfsd.mount \ ++ var-lib-nfs-rpc_pipefs.mount ++ ++EXTRA_DIST = $(unit_files) ++ ++unit_dir = /usr/lib/systemd/system ++ ++if INSTALL_SYSTEMD ++install-data-hook: $(unit_files) ++ mkdir -p $(DESTDIR)/$(unitdir) ++ cp $(unit_files) $(DESTDIR)/$(unitdir) ++endif +diff --git a/systemd/README b/systemd/README +index a2a5f06..bbd7790 100644 +--- a/systemd/README ++++ b/systemd/README +@@ -24,7 +24,7 @@ by a suitable 'preset' setting: + is started by /usr/sbin/start-statd which mount.nfs will run + if statd is needed. + +- nfs-blkmap.target ++ nfs-blkmap.service + If enabled, then blkmapd will be run when nfs-client.target is + started. + +diff --git a/systemd/nfs-blkmap.service b/systemd/nfs-blkmap.service +index f470e3d..ddbf4e9 100644 +--- a/systemd/nfs-blkmap.service ++++ b/systemd/nfs-blkmap.service +@@ -5,12 +5,13 @@ Conflicts=umount.target + After=var-lib-nfs-rpc_pipefs.mount + Requires=var-lib-nfs-rpc_pipefs.mount + +-Requisite=nfs-blkmap.target +-After=nfs-blkmap.target +- + PartOf=nfs-utils.service + + [Service] + Type=forking + PIDFile=/var/run/blkmapd.pid ++EnvironmentFile=-/run/sysconfig/nfs-utils + ExecStart=/usr/sbin/blkmapd $BLKMAPDARGS ++ ++[Install] ++WantedBy=nfs-client.target +diff --git a/systemd/nfs-blkmap.target b/systemd/nfs-blkmap.target +deleted file mode 100644 +index fbcc111..0000000 +--- a/systemd/nfs-blkmap.target ++++ /dev/null +@@ -1,8 +0,0 @@ +-[Unit] +-Description= PNFS blkmaping enablement. +-# If this target is enabled, then blkmapd will be started +-# as required. If it is not enabled it won't. +- +-[Install] +-WantedBy=remote-fs.target +-WantedBy=multi-user.target +\ No newline at end of file +diff --git a/systemd/nfs-client.target b/systemd/nfs-client.target +index 9b792a3..8a8300a 100644 +--- a/systemd/nfs-client.target ++++ b/systemd/nfs-client.target +@@ -5,8 +5,7 @@ Wants=remote-fs-pre.target + + # Note: we don't "Wants=rpc-statd.service" as "mount.nfs" will arrange to + # start that on demand if needed. +-Wants=nfs-blkmap.service rpc-statd-notify.service +-After=nfs-blkmap.service ++Wants=rpc-statd-notify.service + + # GSS services dependencies and ordering + Wants=auth-rpcgss-module.service +diff --git a/utils/blkmapd/device-discovery.c b/utils/blkmapd/device-discovery.c +index df4627e..b52afe2 100644 +--- a/utils/blkmapd/device-discovery.c ++++ b/utils/blkmapd/device-discovery.c +@@ -77,16 +77,6 @@ struct bl_disk_path *bl_get_path(const char *filepath, + return tmp; + } + +-/* Check whether valid_path is a substring(partition) of path */ +-int bl_is_partition(struct bl_disk_path *valid_path, struct bl_disk_path *path) +-{ +- if (!strncmp(valid_path->full_path, path->full_path, +- strlen(valid_path->full_path))) +- return 1; +- +- return 0; +-} +- + /* + * For multipath devices, devices state could be PASSIVE/ACTIVE/PSEUDO, + * where PSEUDO > ACTIVE > PASSIVE. Device with highest state is used to +@@ -95,19 +85,13 @@ int bl_is_partition(struct bl_disk_path *valid_path, struct bl_disk_path *path) + * If device-mapper multipath support is a must, pseudo devices should + * exist for each multipath device. If not, active device path will be + * chosen for device creation. +- * Treat partition as invalid path. + */ +-int bl_update_path(struct bl_disk_path *path, enum bl_path_state_e state, +- struct bl_disk *disk) ++int bl_update_path(enum bl_path_state_e state, struct bl_disk *disk) + { + struct bl_disk_path *valid_path = disk->valid_path; + +- if (valid_path) { +- if (valid_path->state >= state) { +- if (bl_is_partition(valid_path, path)) +- return 0; +- } +- } ++ if (valid_path && valid_path->state >= state) ++ return 0; + return 1; + } + +@@ -164,15 +148,16 @@ void bl_add_disk(char *filepath) + + dev = sb.st_rdev; + serial = bldev_read_serial(fd, filepath); +- if (dm_is_dm_major(major(dev))) ++ if (!serial) { ++ BL_LOG_ERR("%s: no serial found for %s\n", ++ __func__, filepath); ++ ap_state = BL_PATH_STATE_PASSIVE; ++ } else if (dm_is_dm_major(major(dev))) + ap_state = BL_PATH_STATE_PSEUDO; + else + ap_state = bldev_read_ap_state(fd); + close(fd); + +- if (ap_state != BL_PATH_STATE_ACTIVE) +- return; +- + for (disk = visible_disk_list; disk != NULL; disk = disk->next) { + /* Already scanned or a partition? + * XXX: if released each time, maybe not need to compare +@@ -216,7 +201,7 @@ void bl_add_disk(char *filepath) + path->next = disk->paths; + disk->paths = path; + /* check whether we need to update disk info */ +- if (bl_update_path(path, path->state, disk)) { ++ if (bl_update_path(path->state, disk)) { + disk->dev = dev; + disk->size = size; + disk->valid_path = path; +diff --git a/utils/blkmapd/device-inq.c b/utils/blkmapd/device-inq.c +index eabc70c..c5bf71f 100644 +--- a/utils/blkmapd/device-inq.c ++++ b/utils/blkmapd/device-inq.c +@@ -179,6 +179,7 @@ struct bl_serial *bldev_read_serial(int fd, const char *filename) + char *buffer; + struct bl_dev_id *dev_root, *dev_id; + unsigned int pos, len, current_id = 0; ++ size_t devid_len = sizeof(struct bl_dev_id) - sizeof(unsigned char); + + status = bldev_inquire_pages(fd, 0x83, &buffer); + if (status) +@@ -189,7 +190,11 @@ struct bl_serial *bldev_read_serial(int fd, const char *filename) + pos = 0; + current_id = 0; + len = dev_root->len; +- while (pos < (len - sizeof(struct bl_dev_id) + sizeof(unsigned char))) { ++ ++ if (len < devid_len) ++ goto out; ++ ++ while (pos < (len - devid_len)) { + dev_id = (struct bl_dev_id *)&(dev_root->data[pos]); + if ((dev_id->ids & 0xf) < current_id) + continue; +@@ -221,8 +226,7 @@ struct bl_serial *bldev_read_serial(int fd, const char *filename) + } + if (current_id == 3) + break; +- pos += (dev_id->len + sizeof(struct bl_dev_id) - +- sizeof(unsigned char)); ++ pos += (dev_id->len + devid_len); + } + out: + if (!serial_out) +diff --git a/utils/blkmapd/device-process.c b/utils/blkmapd/device-process.c +index 5fe3dff..f53a616 100644 +--- a/utils/blkmapd/device-process.c ++++ b/utils/blkmapd/device-process.c +@@ -181,6 +181,8 @@ static int map_sig_to_device(struct bl_sig *sig, struct bl_volume *vol) + /* FIXME: should we use better algorithm for disk scan? */ + mapped = verify_sig(disk, sig); + if (mapped) { ++ BL_LOG_INFO("%s: using device %s\n", ++ __func__, disk->valid_path->full_path); + vol->param.bv_dev = disk->dev; + vol->bv_size = disk->size; + break; +diff --git a/utils/blkmapd/dm-device.c b/utils/blkmapd/dm-device.c +index 0f4f148..24ffcbf 100644 +--- a/utils/blkmapd/dm-device.c ++++ b/utils/blkmapd/dm-device.c +@@ -400,6 +400,8 @@ uint64_t dm_device_create(struct bl_volume *vols, int num_vols) + } + dev = node->bv_vols[0]->param.bv_dev; + tmp = table->params; ++ BL_LOG_INFO("%s: major %lu minor %lu", __func__, ++ MAJOR(dev), MINOR(dev)); + if (!dm_format_dev(tmp, DM_PARAMS_LEN, + MAJOR(dev), MINOR(dev))) { + free(table); +@@ -459,6 +461,8 @@ uint64_t dm_device_create(struct bl_volume *vols, int num_vols) + strcpy(table->target_type, "linear"); + tmp = table->params; + dev = node->bv_vols[i]->param.bv_dev; ++ BL_LOG_INFO("%s: major %lu minor %lu", __func__, ++ MAJOR(dev), MINOR(dev)); + if (!dm_format_dev(tmp, DM_PARAMS_LEN, + MAJOR(dev), MINOR(dev))) { + free(table); +diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c +index 8391615..53e86ec 100644 +--- a/utils/exportfs/exportfs.c ++++ b/utils/exportfs/exportfs.c +@@ -815,6 +815,8 @@ dump(int verbose, int export_format) + c = dumpopt(c, "insecure_locks"); + if (ep->e_flags & NFSEXP_NOACL) + c = dumpopt(c, "no_acl"); ++ if (ep->e_flags & NFSEXP_PNFS) ++ c = dumpopt(c, "pnfs"); + if (ep->e_flags & NFSEXP_FSID) + c = dumpopt(c, "fsid=%d", ep->e_fsid); + if (ep->e_uuid) +diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man +index 3d974d9..59358e6 100644 +--- a/utils/exportfs/exports.man ++++ b/utils/exportfs/exports.man +@@ -378,6 +378,15 @@ If the client asks for alternative locations for the export point, it + will be given this list of alternatives. (Note that actual replication + of the filesystem must be handled elsewhere.) + ++.TP ++.IR pnfs ++This option allows enables the use of pNFS extension if protocol level ++is NFSv4.1 or higher, and the filesystem supports pNFS exports. With ++pNFS clients can bypass the server and perform I/O directly to storage ++devices. The default can be explicitly requested with the ++.I no_pnfs ++option. ++ + .SS User ID Mapping + .PP + .B nfsd diff --git a/SOURCES/nfs-utils-1.3.0-gssd-noclear-retval.patch b/SOURCES/nfs-utils-1.3.0-gssd-noclear-retval.patch new file mode 100644 index 0000000..c19d730 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-gssd-noclear-retval.patch @@ -0,0 +1,27 @@ +commit a705076172b274463563416adffe55f129740267 +Author: Steve Dickson +Date: Thu Jul 30 17:06:39 2015 -0400 + + rpc.gssd: Only clear the retval if it has not been set + + In gssd_search_krb5_keytab() an error code can be + cleared by blindly setting retval to zero. + + Reported-by: Jianhong Yin + Signed-off-by: Steve Dickson + +diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c +index f1ebc0d..ecf17a2 100644 +--- a/utils/gssd/krb5_util.c ++++ b/utils/gssd/krb5_util.c +@@ -772,7 +772,9 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, + "keytab '%s'\n", k5err, kt_name); + } + +- retval = 0; ++ /* Only clear the retval if has not been set */ ++ if (retval < 0) ++ retval = 0; + out: + free(k5err); + return retval; diff --git a/SOURCES/nfs-utils-1.3.0-gssd-tgt-flood.patch b/SOURCES/nfs-utils-1.3.0-gssd-tgt-flood.patch new file mode 100644 index 0000000..5dfef5c --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-gssd-tgt-flood.patch @@ -0,0 +1,287 @@ +diff -up nfs-utils-1.3.0/utils/gssd/gssd_proc.c.old nfs-utils-1.3.0/utils/gssd/gssd_proc.c +--- nfs-utils-1.3.0/utils/gssd/gssd_proc.c.old 2015-09-24 09:48:40.833593000 -0400 ++++ nfs-utils-1.3.0/utils/gssd/gssd_proc.c 2015-09-24 09:50:58.747069000 -0400 +@@ -1023,6 +1023,113 @@ change_identity(uid_t uid) + return 0; + } + ++AUTH * ++krb5_not_machine_creds(struct clnt_info *clp, uid_t uid, char *tgtname, ++ int *downcall_err, int *chg_err, CLIENT **rpc_clnt) ++{ ++ AUTH *auth = NULL; ++ gss_cred_id_t gss_cred; ++ char **dname; ++ int err, resp = -1; ++ ++ printerr(1, "krb5_not_machine_creds: uid %d tgtname %s\n", ++ uid, tgtname); ++ ++ *chg_err = change_identity(uid); ++ if (*chg_err) { ++ printerr(0, "WARNING: failed to change identity: %s", ++ strerror(*chg_err)); ++ goto out; ++ } ++ ++ /** Tell krb5 gss which credentials cache to use. ++ * Try first to acquire credentials directly via GSSAPI ++ */ ++ err = gssd_acquire_user_cred(&gss_cred); ++ if (err == 0) ++ resp = create_auth_rpc_client(clp, tgtname, rpc_clnt, ++ &auth, uid, ++ AUTHTYPE_KRB5, gss_cred); ++ ++ /** if create_auth_rplc_client fails try the traditional ++ * method of trolling for credentials ++ */ ++ for (dname = ccachesearch; resp != 0 && *dname != NULL; dname++) { ++ err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, ++ *dname); ++ if (err == -EKEYEXPIRED) ++ *downcall_err = -EKEYEXPIRED; ++ else if (err == 0) ++ resp = create_auth_rpc_client(clp, tgtname, rpc_clnt, ++ &auth, uid,AUTHTYPE_KRB5, ++ GSS_C_NO_CREDENTIAL); ++ } ++ ++out: ++ return auth; ++} ++ ++AUTH * ++krb5_use_machine_creds(struct clnt_info *clp, uid_t uid, char *tgtname, ++ char *service, CLIENT **rpc_clnt) ++{ ++ AUTH *auth = NULL; ++ char **credlist = NULL; ++ char **ccname; ++ int nocache = 0; ++ int success = 0; ++ ++ printerr(1, "krb5_use_machine_creds: uid %d tgtname %s\n", ++ uid, tgtname); ++ ++ do { ++ gssd_refresh_krb5_machine_credential(clp->servername, NULL, ++ service); ++ /* ++ * Get a list of credential cache names and try each ++ * of them until one works or we've tried them all ++ */ ++ if (gssd_get_krb5_machine_cred_list(&credlist)) { ++ printerr(0, "ERROR: No credentials found " ++ "for connection to server %s\n", ++ clp->servername); ++ goto out; ++ } ++ for (ccname = credlist; ccname && *ccname; ccname++) { ++ gssd_setup_krb5_machine_gss_ccache(*ccname); ++ if ((create_auth_rpc_client(clp, tgtname, rpc_clnt, ++ &auth, uid, ++ AUTHTYPE_KRB5, ++ GSS_C_NO_CREDENTIAL)) == 0) { ++ /* Success! */ ++ success++; ++ break; ++ } ++ printerr(2, "WARNING: Failed to create machine krb5" ++ "context with cred cache %s for server %s\n", ++ *ccname, clp->servername); ++ } ++ gssd_free_krb5_machine_cred_list(credlist); ++ if (!success) { ++ if(nocache == 0) { ++ nocache++; ++ printerr(2, "WARNING: Machine cache prematurely" "expired or corrupted trying to" ++ "recreate cache for server %s\n", ++ clp->servername); ++ } else { ++ printerr(1, "WARNING: Failed to create machine" ++ "krb5 context with any credentials" ++ "cache for server %s\n", ++ clp->servername); ++ goto out; ++ } ++ } ++ } while(!success); ++ ++out: ++ return auth; ++} ++ + /* + * this code uses the userland rpcsec gss library to create a krb5 + * context on behalf of the kernel +@@ -1035,40 +1142,13 @@ process_krb5_upcall(struct clnt_info *cl + AUTH *auth = NULL; + struct authgss_private_data pd; + gss_buffer_desc token; +- char **credlist = NULL; +- char **ccname; +- char **dirname; +- int create_resp = -1; + int err, downcall_err = -EACCES; +- gss_cred_id_t gss_cred; + OM_uint32 maj_stat, min_stat, lifetime_rec; +- pid_t pid; ++ pid_t pid, childpid = -1; + gss_name_t gacceptor = GSS_C_NO_NAME; + gss_OID mech; + gss_buffer_desc acceptor = {0}; + +- pid = fork(); +- switch(pid) { +- case 0: +- /* Child: fall through to rest of function */ +- break; +- case -1: +- /* fork() failed! */ +- printerr(0, "WARNING: unable to fork() to handle upcall: %s\n", +- strerror(errno)); +- return; +- default: +- /* Parent: just wait on child to exit and return */ +- do { +- pid = wait(&err); +- } while(pid == -1 && errno != -ECHILD); +- +- if (WIFSIGNALED(err)) +- printerr(0, "WARNING: forked child was killed with signal %d\n", +- WTERMSIG(err)); +- return; +- } +- + printerr(1, "handling krb5 upcall (%s)\n", clp->dirname); + + token.length = 0; +@@ -1101,76 +1181,48 @@ process_krb5_upcall(struct clnt_info *cl + if (uid != 0 || (uid == 0 && root_uses_machine_creds == 0 && + service == NULL)) { + +- err = change_identity(uid); +- if (err) { +- printerr(0, "WARNING: failed to change identity: %s", +- strerror(err)); +- goto out_return_error; +- } ++ /* already running as uid 0 */ ++ if (uid == 0) ++ goto no_fork; ++ ++ pid = fork(); ++ switch(pid) { ++ case 0: ++ /* Child: fall through to rest of function */ ++ childpid = getpid(); ++ unsetenv("KRB5CCNAME"); ++ printerr(1, "CHILD forked pid %d \n", childpid); ++ break; ++ case -1: ++ /* fork() failed! */ ++ printerr(0, "WARNING: unable to fork() to handle" ++ "upcall: %s\n", strerror(errno)); ++ return; ++ default: ++ /* Parent: just wait on child to exit and return */ ++ do { ++ pid = wait(&err); ++ } while(pid == -1 && errno != -ECHILD); + +- /* Tell krb5 gss which credentials cache to use */ +- /* Try first to acquire credentials directly via GSSAPI */ +- err = gssd_acquire_user_cred(&gss_cred); +- if (!err) +- create_resp = create_auth_rpc_client(clp, tgtname, &rpc_clnt, &auth, uid, +- AUTHTYPE_KRB5, gss_cred); +- /* if create_auth_rplc_client fails try the traditional method of +- * trolling for credentials */ +- for (dirname = ccachesearch; create_resp != 0 && *dirname != NULL; dirname++) { +- err = gssd_setup_krb5_user_gss_ccache(uid, clp->servername, *dirname); +- if (err == -EKEYEXPIRED) +- downcall_err = -EKEYEXPIRED; +- else if (!err) +- create_resp = create_auth_rpc_client(clp, tgtname, &rpc_clnt, &auth, uid, +- AUTHTYPE_KRB5, GSS_C_NO_CREDENTIAL); ++ if (WIFSIGNALED(err)) ++ printerr(0, "WARNING: forked child was killed" ++ "with signal %d\n", WTERMSIG(err)); ++ return; + } ++no_fork: ++ ++ auth = krb5_not_machine_creds(clp, uid, tgtname, &downcall_err, ++ &err, &rpc_clnt); ++ if (err) ++ goto out_return_error; + } +- if (create_resp != 0) { ++ if (auth == NULL) { + if (uid == 0 && (root_uses_machine_creds == 1 || + service != NULL)) { +- int nocache = 0; +- int success = 0; +- do { +- gssd_refresh_krb5_machine_credential(clp->servername, +- NULL, service); +- /* +- * Get a list of credential cache names and try each +- * of them until one works or we've tried them all +- */ +- if (gssd_get_krb5_machine_cred_list(&credlist)) { +- printerr(0, "ERROR: No credentials found " +- "for connection to server %s\n", +- clp->servername); +- goto out_return_error; +- } +- for (ccname = credlist; ccname && *ccname; ccname++) { +- gssd_setup_krb5_machine_gss_ccache(*ccname); +- if ((create_auth_rpc_client(clp, tgtname, &rpc_clnt, +- &auth, uid, +- AUTHTYPE_KRB5, +- GSS_C_NO_CREDENTIAL)) == 0) { +- /* Success! */ +- success++; +- break; +- } +- printerr(2, "WARNING: Failed to create machine krb5 context " +- "with credentials cache %s for server %s\n", +- *ccname, clp->servername); +- } +- gssd_free_krb5_machine_cred_list(credlist); +- if (!success) { +- if(nocache == 0) { +- nocache++; +- printerr(2, "WARNING: Machine cache is prematurely expired or corrupted " +- "trying to recreate cache for server %s\n", clp->servername); +- } else { +- printerr(1, "WARNING: Failed to create machine krb5 context " +- "with any credentials cache for server %s\n", +- clp->servername); +- goto out_return_error; +- } +- } +- } while(!success); ++ auth = krb5_use_machine_creds(clp, uid, tgtname, ++ service, &rpc_clnt); ++ if (auth == NULL) ++ goto out_return_error; + } else { + printerr(1, "WARNING: Failed to create krb5 context " + "for user with uid %d for server %s\n", +@@ -1225,7 +1277,12 @@ out: + AUTH_DESTROY(auth); + if (rpc_clnt) + clnt_destroy(rpc_clnt); +- exit(0); ++ ++ pid = getpid(); ++ if (pid == childpid) ++ exit(0); ++ else ++ return; + + out_return_error: + do_error_downcall(fd, uid, downcall_err); diff --git a/SOURCES/nfs-utils-1.3.0-mountd-manpage-args.patch b/SOURCES/nfs-utils-1.3.0-mountd-manpage-args.patch new file mode 100644 index 0000000..b9eadc1 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-mountd-manpage-args.patch @@ -0,0 +1,57 @@ +diff -up nfs-utils-1.3.0/utils/mountd/mountd.man.start nfs-utils-1.3.0/utils/mountd/mountd.man +--- nfs-utils-1.3.0/utils/mountd/mountd.man.start 2015-07-14 15:42:00.817994335 -0400 ++++ nfs-utils-1.3.0/utils/mountd/mountd.man 2015-07-14 15:42:17.347273962 -0400 +@@ -86,7 +86,7 @@ Turn on debugging. Valid kinds are: all, + .B \-F " or " \-\-foreground + Run in foreground (do not daemonize) + .TP +-.B \-f " or " \-\-exports-file ++.B \-f export-file " or " \-\-exports-file export-file + This option specifies the exports file, listing the clients that this + server is prepared to serve and parameters to apply to each + such mount (see +@@ -101,7 +101,7 @@ Display usage message. + Set the limit of the number of open file descriptors to num. The + default is to leave the limit unchanged. + .TP +-.B \-N " or " \-\-no-nfs-version ++.B \-N mountd-version " or " \-\-no-nfs-version mountd-version + This option can be used to request that + .B rpc.mountd + do not offer certain versions of NFS. The current version of +@@ -118,7 +118,7 @@ Don't advertise TCP for mount. + .B \-P + Ignored (compatibility with unfsd??). + .TP +-.B \-p " or " \-\-port num ++.B \-p num " or " \-\-port num + Specifies the port number used for RPC listener sockets. + If this option is not specified, + .B rpc.mountd +@@ -132,7 +132,7 @@ This option can be used to fix the port + listeners when NFS MOUNT requests must traverse a firewall + between clients and servers. + .TP +-.B \-H " or " \-\-ha-callout prog ++.B \-H " prog or " \-\-ha-callout prog + Specify a high availability callout program. + This program receives callouts for all MOUNT and UNMOUNT requests. + This allows +@@ -174,7 +174,7 @@ to perform a reverse lookup on each IP a + Enabling this can have a substantial negative effect on performance + in some situations. + .TP +-.BR "\-t N" " or " "\-\-num\-threads=N" ++.BR "\-t N" " or " "\-\-num\-threads=N " or " \-\-num\-threads N " + This option specifies the number of worker threads that rpc.mountd + spawns. The default is 1 thread, which is probably enough. More + threads are usually only needed for NFS servers which need to handle +@@ -184,7 +184,7 @@ your DNS server is slow or unreliable. + .B \-u " or " \-\-no-udp + Don't advertise UDP for mounting + .TP +-.B \-V " or " \-\-nfs-version ++.B \-V version " or " \-\-nfs-version version + This option can be used to request that + .B rpc.mountd + offer certain versions of NFS. The current version of diff --git a/SOURCES/nfs-utils-1.3.0-mountd-manpage-netconfig.patch b/SOURCES/nfs-utils-1.3.0-mountd-manpage-netconfig.patch new file mode 100644 index 0000000..23c1313 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-mountd-manpage-netconfig.patch @@ -0,0 +1,39 @@ +diff -up nfs-utils-1.3.0/utils/mount/mount.nfs.man.orig nfs-utils-1.3.0/utils/mount/mount.nfs.man +--- nfs-utils-1.3.0/utils/mount/mount.nfs.man.orig 2014-03-25 11:12:07.000000000 -0400 ++++ nfs-utils-1.3.0/utils/mount/mount.nfs.man 2015-06-25 10:27:18.001345779 -0400 +@@ -76,10 +76,13 @@ file system table + .TP + .I /etc/mtab + table of mounted file systems +- ++.TP ++.I /etc/nfsmount.conf ++Configuration file for NFS mounts + .PD + .SH "SEE ALSO" + .BR nfs (5), ++.BR nfsmount.conf (5), + .BR mount (8), + + .SH "AUTHOR" +diff -up nfs-utils-1.3.0/utils/mount/nfs.man.orig nfs-utils-1.3.0/utils/mount/nfs.man +--- nfs-utils-1.3.0/utils/mount/nfs.man.orig 2014-03-25 11:12:07.000000000 -0400 ++++ nfs-utils-1.3.0/utils/mount/nfs.man 2015-06-25 10:27:18.001345779 -0400 +@@ -1706,6 +1706,9 @@ with the mount options already saved on + .TP 1.5i + .I /etc/fstab + file system table ++.TP 1.5i ++.I /etc/nfsmount.conf ++Configuration file for NFS mounts + .SH BUGS + Before 2.4.7, the Linux NFS client did not support NFS over TCP. + .P +@@ -1735,6 +1738,7 @@ such as security negotiation, server ref + .BR mount.nfs (5), + .BR umount.nfs (5), + .BR exports (5), ++.BR nfsmount.conf (5), + .BR netconfig (5), + .BR ipv6 (7), + .BR nfsd (8), diff --git a/SOURCES/nfs-utils-1.3.0-mountd-v4root-sec.patch b/SOURCES/nfs-utils-1.3.0-mountd-v4root-sec.patch new file mode 100644 index 0000000..7485825 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-mountd-v4root-sec.patch @@ -0,0 +1,102 @@ +commit 4a1ad4aa3028d26d830d9a9003ff9e3337b0e0d5 +Author: Scott Mayhew +Date: Thu Apr 2 11:15:15 2015 -0400 + + mountd: Enable all auth flavors on pseudofs exports + + With the current mountd code it's possible to craft exports in such a + manner that clients will be unable to mount exports that they *should* + be able to mount. + + Consider the following example: + + /foo *(rw,insecure,no_root_squash,sec=krb5p) + /bar client.example.com(rw,insecure,no_root_squash) + + Initially, client.example.com will be able to mount the /foo export + using sec=krb5p, but attempts to mount /bar using sec=sys will return + EPERM. Once the nfsd.export cache entry expires, client.example.com + will then be able to mount /bar using sec=sys but attempts to mount /foo + using sec=krb5p will return EPERM. + + The reason this happens is because the initial nfsd.export cache entry + is actually pre-populated by nfsd_fh(), which is the handler for the + nfsd.fh cache, while later cache requests (once the initial entry + expires) are handled by nfsd_export(). These functions have slightly + different logic in how they select a v4root export from the cache -- + nfsd_fh() takes last matching v4root export it finds, while + nfsd_export() (actually lookup_export()) takes the first. Either way + it's wrong because the client should be able to mount both exports. + + Both rfc3503bis and rfc5661 say: + + A common and convenient practice, unless strong security requirements + dictate otherwise, is to make the entire pseudo file system + accessible by all of the valid security mechanisms. + + ...so lets do that. + + Acked-by: J. Bruce Fields + Signed-off-by: Scott Mayhew + Signed-off-by: Steve Dickson + +diff --git a/utils/mountd/v4root.c b/utils/mountd/v4root.c +index 34d098a..429ebb8 100644 +--- a/utils/mountd/v4root.c ++++ b/utils/mountd/v4root.c +@@ -26,6 +26,7 @@ + #include "nfslib.h" + #include "misc.h" + #include "v4root.h" ++#include "pseudoflavors.h" + + int v4root_needed; + +@@ -56,22 +57,22 @@ static nfs_export pseudo_root = { + }; + + static void +-set_pseudofs_security(struct exportent *pseudo, struct exportent *source) ++set_pseudofs_security(struct exportent *pseudo, int flags) + { +- struct sec_entry *se; ++ struct flav_info *flav; + int i; + +- if (source->e_flags & NFSEXP_INSECURE_PORT) ++ if (flags & NFSEXP_INSECURE_PORT) + pseudo->e_flags |= NFSEXP_INSECURE_PORT; +- if ((source->e_flags & NFSEXP_ROOTSQUASH) == 0) ++ if ((flags & NFSEXP_ROOTSQUASH) == 0) + pseudo->e_flags &= ~NFSEXP_ROOTSQUASH; +- for (se = source->e_secinfo; se->flav; se++) { ++ for (flav = flav_map; flav < flav_map + flav_map_size; flav++) { + struct sec_entry *new; + +- i = secinfo_addflavor(se->flav, pseudo); ++ i = secinfo_addflavor(flav, pseudo); + new = &pseudo->e_secinfo[i]; + +- if (se->flags & NFSEXP_INSECURE_PORT) ++ if (flags & NFSEXP_INSECURE_PORT) + new->flags |= NFSEXP_INSECURE_PORT; + } + } +@@ -91,7 +92,7 @@ v4root_create(char *path, nfs_export *export) + strncpy(eep.e_path, path, sizeof(eep.e_path)); + if (strcmp(path, "/") != 0) + eep.e_flags &= ~NFSEXP_FSID; +- set_pseudofs_security(&eep, curexp); ++ set_pseudofs_security(&eep, curexp->e_flags); + exp = export_create(&eep, 0); + if (exp == NULL) + return NULL; +@@ -139,7 +140,7 @@ pseudofs_update(char *hostname, char *path, nfs_export *source) + return 0; + } + /* Update an existing V4ROOT export: */ +- set_pseudofs_security(&exp->m_export, &source->m_export); ++ set_pseudofs_security(&exp->m_export, source->m_export.e_flags); + return 0; + } + diff --git a/SOURCES/nfs-utils-1.3.0-mountstats-update.patch b/SOURCES/nfs-utils-1.3.0-mountstats-update.patch new file mode 100644 index 0000000..247b0aa --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-mountstats-update.patch @@ -0,0 +1,1326 @@ +diff -up nfs-utils-1.3.0/tools/mountstats/mountstats.man.good nfs-utils-1.3.0/tools/mountstats/mountstats.man +--- nfs-utils-1.3.0/tools/mountstats/mountstats.man.good 2015-07-30 15:19:01.718000115 -0400 ++++ nfs-utils-1.3.0/tools/mountstats/mountstats.man 2015-07-30 15:19:35.430613995 -0400 +@@ -1,32 +1,146 @@ + .\" + .\" mountstats(8) + .\" +-.TH mountstats 8 "15 Apr 2010" ++.TH mountstats 8 "12 Dec 2014" + .SH NAME +-mountstats \- Displays NFS client per-mount statistics ++mountstats \- Displays various NFS client per-mount statistics + .SH SYNOPSIS +-.BI "mountstats [" "] " " [ " "]" +-.SH DESCRIPTION +-The + .B mountstats +-command displays NFS client statisitics on each given +-.I ++.RB [ \-h | \-\-help ] ++.RB [ \-v | \-\-version ] ++.RB [ \-f | \-\-file ++.IR infile ] ++.RB [ \-S | \-\-since ++.IR sincefile ] ++.\" .RB [ \-n | \-\-nfs | \-r | \-\-rpc | \-R | \-\-raw ] ++.R [ ++.RB [ \-n | \-\-nfs ] ++.R | ++.RB [ \-r | \-\-rpc ] ++.R | ++.RB [ \-R | \-\-raw ] ++.R ] ++.RI [ mountpoint ] ... ++.P ++.B mountstats iostat ++.RB [ \-h | \-\-help ] ++.RB [ \-v | \-\-version ] ++.RB [ \-f | \-\-file ++.IR infile ] ++.RB [ \-S | \-\-since ++.IR sincefile ] ++.RI [ interval ] ++.RI [ count ] ++.RI [ mountpoint ] ... ++.P ++.B mounstats nfsstat ++.RB [ \-h | \-\-help ] ++.RB [ \-v | \-\-version ] ++.RB [ \-f | \-\-file ++.IR infile ] ++.RB [ \-S | \-\-since ++.IR sincefile ] ++.RB [ \-3 ] ++.RB [ \-4 ] ++.RI [ mountpoint ] ... ++.P ++.SH DESCRIPTION ++.RB "The " mountstats " command displays various NFS client statisitics for each given" ++.IR mountpoint . ++.P ++.RI "If no " mountpoint " is given, statistics will be displayed for all NFS mountpoints on the client." ++.SS Sub-commands ++Valid ++.BR mountstats (8) ++subcommands are: ++.IP "\fBmountstats\fP" ++Display a combination of per-op RPC statistics, NFS event counts, and NFS byte counts. This is the default sub-command that will be executed if no sub-command is given. ++.IP "\fBiostat\fP" ++Display iostat-like statistics. ++.IP "\fBnfsstat\fP" ++Display nfsstat-like statistics. + .SH OPTIONS ++.SS Options valid for all sub-commands ++.TP ++.B \-h, \-\-help ++show the help message and exit ++.TP ++.B \-v, \-\-version ++show program's version number and exit ++.TP ++\fB\-f \fIinfile\fR, \fB\-\-file \fIinfile ++Read stats from ++.I infile ++instead of ++.IR /proc/self/mountstats ". " infile ++must be in the same format as ++.IR /proc/self/mountstats . ++This may be used with the ++.BR \-S | \-\-since ++options to display the delta between two different points in time. ++This may not be used with the ++.IR interval " or " count ++options of the ++.B iostat ++sub-command. + .TP +-.B " \-\-nfs +-display only the NFS statistics ++\fB\-S \fIsincefile\fR, \fB\-\-since \fIsincefile ++Show difference between current stats and those in ++.IR sincefile ". " sincefile ++must be in the same format as ++.IR /proc/self/mountstats . ++This may be used with the ++.BR \-f | \-\-file ++options to display the delta between two different points in time. ++This may not be used with the ++.IR interval " or " count ++options of the ++.B iostat ++sub-command. ++.SS Options specific to the mountstats sub-command ++.B \-n, \-\-nfs ++Display only the NFS statistics + .TP +-.B " \-\-rpc +-display only the RPC statistics ++.B \-r, \-\-rpc ++Display only the RPC statistics + .TP +-.B " \-\-version +-display the version of this command ++.B \-R, \-\-raw ++Display only the raw statistics. This is intended for use with the ++.BR \-f | \-\-file ++and ++.BR \-S | \-\-since ++options. ++.SS Options specific to the iostat sub-command ++.IP "\fIinterval\fP" ++Specifies the amount of time in seconds between each report. The first report contains statistics for the time since each file system was mounted. Each subsequent report contains statistics collected during the interval since the previous report. This may not be used with the ++.BR \-f | \-\-file ++or ++.BR \-s | \-\-since ++options. ++.P ++.IP "\fIcount\fP" ++Determines the number of reports generated at ++.I interval ++seconds apart. If the ++.I interval ++parameter is specified without the ++.I count ++parameter, the command generates reports continuously. This may not be used with the ++.BR \-f | \-\-file ++or ++.BR \-S | \-\-since ++options. ++.SS Options specific to the nfsstat sub-command ++.IP "\fB\-3\fP" ++Show only NFS version 3 statistics. The default is to show both version 3 and version 4 statistics. ++.IP "\fB\-4\fP" ++Show only NFS version 4 statistics. The default is to show both version 3 and version 4 statistics. + .SH FILES + .TP + .B /proc/self/mountstats + .SH SEE ALSO + .BR iostat (8), + .BR nfsiostat (8), +-.BR nfsstat(8) ++.BR nfsstat (8) + .SH AUTHOR + Chuck Lever +diff -up nfs-utils-1.3.0/tools/mountstats/mountstats.py.good nfs-utils-1.3.0/tools/mountstats/mountstats.py +--- nfs-utils-1.3.0/tools/mountstats/mountstats.py.good 2015-07-30 15:16:42.390463126 -0400 ++++ nfs-utils-1.3.0/tools/mountstats/mountstats.py 2015-07-30 15:16:20.391062604 -0400 +@@ -24,14 +24,189 @@ MA 02110-1301 USA + """ + + import sys, os, time ++from operator import itemgetter, add ++try: ++ import argparse ++except ImportError: ++ print('%s: Failed to import argparse - make sure argparse is installed!' ++ % sys.argv[0]) ++ sys.exit(1) + +-Mountstats_version = '0.2' ++Mountstats_version = '0.3' + + def difference(x, y): + """Used for a map() function + """ + return x - y + ++NfsEventCounters = [ ++ 'inoderevalidates', ++ 'dentryrevalidates', ++ 'datainvalidates', ++ 'attrinvalidates', ++ 'vfsopen', ++ 'vfslookup', ++ 'vfspermission', ++ 'vfsupdatepage', ++ 'vfsreadpage', ++ 'vfsreadpages', ++ 'vfswritepage', ++ 'vfswritepages', ++ 'vfsreaddir', ++ 'vfssetattr', ++ 'vfsflush', ++ 'vfsfsync', ++ 'vfslock', ++ 'vfsrelease', ++ 'congestionwait', ++ 'setattrtrunc', ++ 'extendwrite', ++ 'sillyrenames', ++ 'shortreads', ++ 'shortwrites', ++ 'delay', ++ 'pnfsreads', ++ 'pnfswrites' ++] ++ ++NfsByteCounters = [ ++ 'normalreadbytes', ++ 'normalwritebytes', ++ 'directreadbytes', ++ 'directwritebytes', ++ 'serverreadbytes', ++ 'serverwritebytes', ++ 'readpages', ++ 'writepages' ++] ++ ++XprtUdpCounters = [ ++ 'port', ++ 'bind_count', ++ 'rpcsends', ++ 'rpcreceives', ++ 'badxids', ++ 'inflightsends', ++ 'backlogutil' ++] ++ ++XprtTcpCounters = [ ++ 'port', ++ 'bind_count', ++ 'connect_count', ++ 'connect_time', ++ 'idle_time', ++ 'rpcsends', ++ 'rpcreceives', ++ 'badxids', ++ 'inflightsends', ++ 'backlogutil' ++] ++ ++XprtRdmaCounters = [ ++ 'port', ++ 'bind_count', ++ 'connect_count', ++ 'connect_time', ++ 'idle_time', ++ 'rpcsends', ++ 'rpcreceives', ++ 'badxids', ++ 'backlogutil', ++ 'read_chunks', ++ 'write_chunks', ++ 'reply_chunks', ++ 'total_rdma_req', ++ 'total_rdma_rep', ++ 'pullup', ++ 'fixup', ++ 'hardway', ++ 'failed_marshal', ++ 'bad_reply' ++] ++ ++Nfsv3ops = [ ++ 'NULL', ++ 'GETATTR', ++ 'SETATTR', ++ 'LOOKUP', ++ 'ACCESS', ++ 'READLINK', ++ 'READ', ++ 'WRITE', ++ 'CREATE', ++ 'MKDIR', ++ 'SYMLINK', ++ 'MKNOD', ++ 'REMOVE', ++ 'RMDIR', ++ 'RENAME', ++ 'LINK', ++ 'READDIR', ++ 'READDIRPLUS', ++ 'FSSTAT', ++ 'FSINFO', ++ 'PATHCONF', ++ 'COMMIT' ++] ++ ++Nfsv4ops = [ ++ 'NULL', ++ 'READ', ++ 'WRITE', ++ 'COMMIT', ++ 'OPEN', ++ 'OPEN_CONFIRM', ++ 'OPEN_NOATTR', ++ 'OPEN_DOWNGRADE', ++ 'CLOSE', ++ 'SETATTR', ++ 'FSINFO', ++ 'RENEW', ++ 'SETCLIENTID', ++ 'SETCLIENTID_CONFIRM', ++ 'LOCK', ++ 'LOCKT', ++ 'LOCKU', ++ 'ACCESS', ++ 'GETATTR', ++ 'LOOKUP', ++ 'LOOKUP_ROOT', ++ 'REMOVE', ++ 'RENAME', ++ 'LINK', ++ 'SYMLINK', ++ 'CREATE', ++ 'PATHCONF', ++ 'STATFS', ++ 'READLINK', ++ 'READDIR', ++ 'SERVER_CAPS', ++ 'DELEGRETURN', ++ 'GETACL', ++ 'SETACL', ++ 'FS_LOCATIONS', ++ 'RELEASE_LOCKOWNER', ++ 'SECINFO', ++ 'FSID_PRESENT', ++ 'EXCHANGE_ID', ++ 'CREATE_SESSION', ++ 'DESTROY_SESSION', ++ 'SEQUENCE', ++ 'GET_LEASE_TIME', ++ 'RECLAIM_COMPLETE', ++ 'LAYOUTGET', ++ 'GETDEVICEINFO', ++ 'LAYOUTCOMMIT', ++ 'LAYOUTRETURN', ++ 'SECINFO_NO_NAME', ++ 'TEST_STATEID', ++ 'FREE_STATEID', ++ 'GETDEVICELIST', ++ 'BIND_CONN_TO_SESSION', ++ 'DESTROY_CLIENTID' ++] ++ + class DeviceData: + """DeviceData objects provide methods for parsing and displaying + data for a single mount grabbed from /proc/self/mountstats +@@ -46,13 +221,13 @@ class DeviceData: + self.__nfs_data['export'] = words[1] + self.__nfs_data['mountpoint'] = words[4] + self.__nfs_data['fstype'] = words[7] +- if words[7].find('nfs') != -1: ++ if words[7].find('nfs') != -1 and words[7] != 'nfsd': + self.__nfs_data['statvers'] = words[8] + elif 'nfs' in words or 'nfs4' in words: + self.__nfs_data['export'] = words[0] + self.__nfs_data['mountpoint'] = words[3] + self.__nfs_data['fstype'] = words[6] +- if words[6].find('nfs') != -1: ++ if words[6].find('nfs') != -1 and words[6] != 'nfsd': + self.__nfs_data['statvers'] = words[7] + elif words[0] == 'age:': + self.__nfs_data['age'] = int(words[1]) +@@ -69,36 +244,18 @@ class DeviceData: + if self.__nfs_data['flavor'] == 6: + self.__nfs_data['pseudoflavor'] = int(keys[1].split('=')[1]) + elif words[0] == 'events:': +- self.__nfs_data['inoderevalidates'] = int(words[1]) +- self.__nfs_data['dentryrevalidates'] = int(words[2]) +- self.__nfs_data['datainvalidates'] = int(words[3]) +- self.__nfs_data['attrinvalidates'] = int(words[4]) +- self.__nfs_data['syncinodes'] = int(words[5]) +- self.__nfs_data['vfsopen'] = int(words[6]) +- self.__nfs_data['vfslookup'] = int(words[7]) +- self.__nfs_data['vfspermission'] = int(words[8]) +- self.__nfs_data['vfsreadpage'] = int(words[9]) +- self.__nfs_data['vfsreadpages'] = int(words[10]) +- self.__nfs_data['vfswritepage'] = int(words[11]) +- self.__nfs_data['vfswritepages'] = int(words[12]) +- self.__nfs_data['vfsreaddir'] = int(words[13]) +- self.__nfs_data['vfsflush'] = int(words[14]) +- self.__nfs_data['vfsfsync'] = int(words[15]) +- self.__nfs_data['vfslock'] = int(words[16]) +- self.__nfs_data['vfsrelease'] = int(words[17]) +- self.__nfs_data['setattrtrunc'] = int(words[18]) +- self.__nfs_data['extendwrite'] = int(words[19]) +- self.__nfs_data['sillyrenames'] = int(words[20]) +- self.__nfs_data['shortreads'] = int(words[21]) +- self.__nfs_data['shortwrites'] = int(words[22]) +- self.__nfs_data['delay'] = int(words[23]) ++ i = 1 ++ for key in NfsEventCounters: ++ try: ++ self.__nfs_data[key] = int(words[i]) ++ except IndexError as err: ++ self.__nfs_data[key] = 0 ++ i += 1 + elif words[0] == 'bytes:': +- self.__nfs_data['normalreadbytes'] = int(words[1]) +- self.__nfs_data['normalwritebytes'] = int(words[2]) +- self.__nfs_data['directreadbytes'] = int(words[3]) +- self.__nfs_data['directwritebytes'] = int(words[4]) +- self.__nfs_data['serverreadbytes'] = int(words[5]) +- self.__nfs_data['serverwritebytes'] = int(words[6]) ++ i = 1 ++ for key in NfsByteCounters: ++ self.__nfs_data[key] = int(words[i]) ++ i += 1 + + def __parse_rpc_line(self, words): + if words[0] == 'RPC': +@@ -107,44 +264,20 @@ class DeviceData: + elif words[0] == 'xprt:': + self.__rpc_data['protocol'] = words[1] + if words[1] == 'udp': +- self.__rpc_data['port'] = int(words[2]) +- self.__rpc_data['bind_count'] = int(words[3]) +- self.__rpc_data['rpcsends'] = int(words[4]) +- self.__rpc_data['rpcreceives'] = int(words[5]) +- self.__rpc_data['badxids'] = int(words[6]) +- self.__rpc_data['inflightsends'] = int(words[7]) +- self.__rpc_data['backlogutil'] = int(words[8]) ++ i = 2 ++ for key in XprtUdpCounters: ++ self.__rpc_data[key] = int(words[i]) ++ i += 1 + elif words[1] == 'tcp': +- self.__rpc_data['port'] = words[2] +- self.__rpc_data['bind_count'] = int(words[3]) +- self.__rpc_data['connect_count'] = int(words[4]) +- self.__rpc_data['connect_time'] = int(words[5]) +- self.__rpc_data['idle_time'] = int(words[6]) +- self.__rpc_data['rpcsends'] = int(words[7]) +- self.__rpc_data['rpcreceives'] = int(words[8]) +- self.__rpc_data['badxids'] = int(words[9]) +- self.__rpc_data['inflightsends'] = int(words[10]) +- self.__rpc_data['backlogutil'] = int(words[11]) ++ i = 2 ++ for key in XprtTcpCounters: ++ self.__rpc_data[key] = int(words[i]) ++ i += 1 + elif words[1] == 'rdma': +- self.__rpc_data['port'] = words[2] +- self.__rpc_data['bind_count'] = int(words[3]) +- self.__rpc_data['connect_count'] = int(words[4]) +- self.__rpc_data['connect_time'] = int(words[5]) +- self.__rpc_data['idle_time'] = int(words[6]) +- self.__rpc_data['rpcsends'] = int(words[7]) +- self.__rpc_data['rpcreceives'] = int(words[8]) +- self.__rpc_data['badxids'] = int(words[9]) +- self.__rpc_data['backlogutil'] = int(words[10]) +- self.__rpc_data['read_chunks'] = int(words[11]) +- self.__rpc_data['write_chunks'] = int(words[12]) +- self.__rpc_data['reply_chunks'] = int(words[13]) +- self.__rpc_data['total_rdma_req'] = int(words[14]) +- self.__rpc_data['total_rdma_rep'] = int(words[15]) +- self.__rpc_data['pullup'] = int(words[16]) +- self.__rpc_data['fixup'] = int(words[17]) +- self.__rpc_data['hardway'] = int(words[18]) +- self.__rpc_data['failed_marshal'] = int(words[19]) +- self.__rpc_data['bad_reply'] = int(words[20]) ++ i = 2 ++ for key in XprtRdmaCounters: ++ self.__rpc_data[key] = int(words[i]) ++ i += 1 + elif words[0] == 'per-op': + self.__rpc_data['per-op'] = words + else: +@@ -178,12 +311,55 @@ class DeviceData: + return True + return False + +- def display_nfs_options(self): +- """Pretty-print the NFS options ++ def nfs_version(self): ++ if self.is_nfs_mountpoint(): ++ prog, vers = self.__rpc_data['programversion'].split('/') ++ return int(vers) ++ ++ def display_raw_stats(self): ++ """Prints out stats in the same format as /proc/self/mountstats + """ ++ print('device %s mounted on %s with fstype %s %s' % \ ++ (self.__nfs_data['export'], self.__nfs_data['mountpoint'], \ ++ self.__nfs_data['fstype'], self.__nfs_data['statvers'])) ++ print('\topts:\t%s' % ','.join(self.__nfs_data['mountoptions'])) ++ print('\tage:\t%d' % self.__nfs_data['age']) ++ print('\tcaps:\t%s' % ','.join(self.__nfs_data['servercapabilities'])) ++ print('\tsec:\tflavor=%d,pseudoflavor=%d' % (self.__nfs_data['flavor'], \ ++ self.__nfs_data['pseudoflavor'])) ++ print('\tevents:\t%s' % " ".join([str(self.__nfs_data[key]) for key in NfsEventCounters])) ++ print('\tbytes:\t%s' % " ".join([str(self.__nfs_data[key]) for key in NfsByteCounters])) ++ print('\tRPC iostats version: %1.1f p/v: %s (nfs)' % (self.__rpc_data['statsvers'], \ ++ self.__rpc_data['programversion'])) ++ if self.__rpc_data['protocol'] == 'udp': ++ print('\txprt:\tudp %s' % " ".join([str(self.__rpc_data[key]) for key in XprtUdpCounters])) ++ elif self.__rpc_data['protocol'] == 'tcp': ++ print('\txprt:\ttcp %s' % " ".join([str(self.__rpc_data[key]) for key in XprtTcpCounters])) ++ elif self.__rpc_data['protocol'] == 'rdma': ++ print('\txprt:\trdma %s' % " ".join([str(self.__rpc_data[key]) for key in XprtRdmaCounters])) ++ else: ++ raise Exception('Unknown RPC transport protocol %s' % self.__rpc_data['protocol']) ++ print('\tper-op statistics') ++ prog, vers = self.__rpc_data['programversion'].split('/') ++ if vers == '3': ++ for op in Nfsv3ops: ++ print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op]))) ++ elif vers == '4': ++ for op in Nfsv4ops: ++ print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op]))) ++ else: ++ print('\tnot implemented for version %d' % vers) ++ print() ++ ++ def display_stats_header(self): + print('Stats for %s mounted on %s:' % \ + (self.__nfs_data['export'], self.__nfs_data['mountpoint'])) + ++ def display_nfs_options(self): ++ """Pretty-print the NFS options ++ """ ++ self.display_stats_header() ++ + print(' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions'])) + print(' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities'])) + if 'nfsv4flags' in self.__nfs_data: +@@ -201,7 +377,6 @@ class DeviceData: + print('Cache events:') + print(' data cache invalidated %d times' % self.__nfs_data['datainvalidates']) + print(' attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates']) +- print(' inodes synced %d times' % self.__nfs_data['syncinodes']) + print() + print('VFS calls:') + print(' VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates']) +@@ -262,29 +437,82 @@ class DeviceData: + """ + sends = self.__rpc_data['rpcsends'] + +- # XXX: these should be sorted by 'count' +- print() ++ allstats = [] + for op in self.__rpc_data['ops']: +- stats = self.__rpc_data[op] +- count = stats[0] +- retrans = stats[1] - count ++ allstats.append([op] + self.__rpc_data[op]) ++ ++ print() ++ for stats in sorted(allstats, key=itemgetter(1), reverse=True): ++ count = stats[1] + if count != 0: +- print('%s:' % op) ++ print('%s:' % stats[0]) + print('\t%d ops (%d%%)' % \ + (count, ((count * 100) / sends)), end=' ') +- print('\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)), end=' ') +- print('\t%d major timeouts' % stats[2]) ++ retrans = stats[2] - count ++ if retrans != 0: ++ print('\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)), end=' ') ++ print('\t%d major timeouts' % stats[3]) ++ else: ++ print('') + print('\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \ +- (stats[3] / count, stats[4] / count)) +- print('\tbacklog wait: %f' % (float(stats[5]) / count), end=' ') +- print('\tRTT: %f' % (float(stats[6]) / count), end=' ') ++ (stats[4] / count, stats[5] / count)) ++ print('\tbacklog wait: %f' % (float(stats[6]) / count), end=' ') ++ print('\tRTT: %f' % (float(stats[7]) / count), end=' ') + print('\ttotal execute time: %f (milliseconds)' % \ +- (float(stats[7]) / count)) ++ (float(stats[8]) / count)) ++ ++ def client_rpc_stats(self): ++ """Tally high-level rpc stats for the nfsstat command ++ """ ++ sends = 0 ++ trans = 0 ++ authrefrsh = 0 ++ for op in self.__rpc_data['ops']: ++ sends += self.__rpc_data[op][0] ++ trans += self.__rpc_data[op][1] ++ retrans = trans - sends ++ # authrefresh stats don't actually get captured in ++ # /proc/self/mountstats, so we fudge it here ++ authrefrsh = sends ++ return (sends, trans, authrefrsh) ++ ++ def display_nfsstat_stats(self): ++ """Pretty-print nfsstat-style stats ++ """ ++ sends = 0 ++ for op in self.__rpc_data['ops']: ++ sends += self.__rpc_data[op][0] ++ if sends == 0: ++ return ++ print() ++ vers = self.nfs_version() ++ print('Client nfs v%d' % vers) ++ info = [] ++ for op in self.__rpc_data['ops']: ++ print('%-13s' % str.lower(op)[:12], end='') ++ count = self.__rpc_data[op][0] ++ pct = (count * 100) / sends ++ info.append((count, pct)) ++ if (self.__rpc_data['ops'].index(op) + 1) % 6 == 0: ++ print() ++ for (count, pct) in info: ++ print('%-8u%3u%% ' % (count, pct), end='') ++ print() ++ info = [] ++ print() ++ if len(info) > 0: ++ for (count, pct) in info: ++ print('%-8u%3u%% ' % (count, pct), end='') ++ print() + + def compare_iostats(self, old_stats): + """Return the difference between two sets of stats + """ ++ if old_stats.__nfs_data['age'] > self.__nfs_data['age']: ++ return self ++ + result = DeviceData() ++ protocol = self.__rpc_data['protocol'] + + # copy self into result + for key, value in self.__nfs_data.items(): +@@ -299,70 +527,118 @@ class DeviceData: + for op in result.__rpc_data['ops']: + result.__rpc_data[op] = list(map(difference, self.__rpc_data[op], old_stats.__rpc_data[op])) + +- # update the remaining keys we care about +- result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends'] +- result.__rpc_data['backlogutil'] -= old_stats.__rpc_data['backlogutil'] +- result.__nfs_data['serverreadbytes'] -= old_stats.__nfs_data['serverreadbytes'] +- result.__nfs_data['serverwritebytes'] -= old_stats.__nfs_data['serverwritebytes'] +- ++ # update the remaining keys ++ if protocol == 'udp': ++ for key in XprtUdpCounters: ++ result.__rpc_data[key] -= old_stats.__rpc_data[key] ++ elif protocol == 'tcp': ++ for key in XprtTcpCounters: ++ result.__rpc_data[key] -= old_stats.__rpc_data[key] ++ elif protocol == 'rdma': ++ for key in XprtRdmaCounters: ++ result.__rpc_data[key] -= old_stats.__rpc_data[key] ++ result.__nfs_data['age'] -= old_stats.__nfs_data['age'] ++ for key in NfsEventCounters: ++ result.__nfs_data[key] -= old_stats.__nfs_data[key] ++ for key in NfsByteCounters: ++ result.__nfs_data[key] -= old_stats.__nfs_data[key] + return result + ++ def setup_accumulator(self, ops): ++ """Initialize a DeviceData instance to tally stats for all mountpoints ++ with the same major version. This is for the nfsstat command. ++ """ ++ if ops == Nfsv3ops: ++ self.__rpc_data['programversion'] = '100003/3' ++ self.__nfs_data['fstype'] = 'nfs' ++ elif ops == Nfsv4ops: ++ self.__rpc_data['programversion'] = '100003/4' ++ self.__nfs_data['fstype'] = 'nfs4' ++ self.__rpc_data['ops'] = ops ++ for op in ops: ++ self.__rpc_data[op] = [0 for i in range(8)] ++ ++ def accumulate_iostats(self, new_stats): ++ """Accumulate counters from all RPC op buckets in new_stats. This is ++ for the nfsstat command. ++ """ ++ for op in new_stats.__rpc_data['ops']: ++ self.__rpc_data[op] = list(map(add, self.__rpc_data[op], new_stats.__rpc_data[op])) ++ ++ def __print_rpc_op_stats(self, op, sample_time): ++ """Print generic stats for one RPC op ++ """ ++ if op not in self.__rpc_data: ++ return ++ ++ rpc_stats = self.__rpc_data[op] ++ ops = float(rpc_stats[0]) ++ retrans = float(rpc_stats[1] - rpc_stats[0]) ++ kilobytes = float(rpc_stats[3] + rpc_stats[4]) / 1024 ++ rtt = float(rpc_stats[6]) ++ exe = float(rpc_stats[7]) ++ ++ # prevent floating point exceptions ++ if ops != 0: ++ kb_per_op = kilobytes / ops ++ retrans_percent = (retrans * 100) / ops ++ rtt_per_op = rtt / ops ++ exe_per_op = exe / ops ++ else: ++ kb_per_op = 0.0 ++ retrans_percent = 0.0 ++ rtt_per_op = 0.0 ++ exe_per_op = 0.0 ++ ++ op += ':' ++ print(format(op.lower(), '<16s'), end='') ++ print(format('ops/s', '>8s'), end='') ++ print(format('kB/s', '>16s'), end='') ++ print(format('kB/op', '>16s'), end='') ++ print(format('retrans', '>16s'), end='') ++ print(format('avg RTT (ms)', '>16s'), end='') ++ print(format('avg exe (ms)', '>16s')) ++ ++ print(format((ops / sample_time), '>24.3f'), end='') ++ print(format((kilobytes / sample_time), '>16.3f'), end='') ++ print(format(kb_per_op, '>16.3f'), end='') ++ retransmits = '{0:>10.0f} ({1:>3.1f}%)'.format(retrans, retrans_percent).strip() ++ print(format(retransmits, '>16'), end='') ++ print(format(rtt_per_op, '>16.3f'), end='') ++ print(format(exe_per_op, '>16.3f')) ++ + def display_iostats(self, sample_time): + """Display NFS and RPC stats in an iostat-like way + """ + sends = float(self.__rpc_data['rpcsends']) + if sample_time == 0: + sample_time = float(self.__nfs_data['age']) ++ # sample_time could still be zero if the export was just mounted. ++ # Set it to 1 to avoid divide by zero errors in this case since we'll ++ # likely still have relevant mount statistics to show. ++ # ++ if sample_time == 0: ++ sample_time = 1; ++ if sends != 0: ++ backlog = (float(self.__rpc_data['backlogutil']) / sends) / sample_time ++ else: ++ backlog = 0.0 + + print() + print('%s mounted on %s:' % \ + (self.__nfs_data['export'], self.__nfs_data['mountpoint'])) ++ print() + +- print('\top/s\trpc bklog') +- print('\t%.2f' % (sends / sample_time), end=' ') +- if sends != 0: +- print('\t%.2f' % \ +- ((float(self.__rpc_data['backlogutil']) / sends) / sample_time)) +- else: +- print('\t0.00') +- +- # reads: ops/s, kB/s, avg rtt, and avg exe +- # XXX: include avg xfer size and retransmits? +- read_rpc_stats = self.__rpc_data['READ'] +- ops = float(read_rpc_stats[0]) +- kilobytes = float(self.__nfs_data['serverreadbytes']) / 1024 +- rtt = float(read_rpc_stats[6]) +- exe = float(read_rpc_stats[7]) +- +- print('\treads:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)') +- print('\t\t%.2f' % (ops / sample_time), end=' ') +- print('\t\t%.2f' % (kilobytes / sample_time), end=' ') +- if ops != 0: +- print('\t\t%.2f' % (rtt / ops), end=' ') +- print('\t\t%.2f' % (exe / ops)) +- else: +- print('\t\t0.00', end=' ') +- print('\t\t0.00') ++ print(format('ops/s', '>16') + format('rpc bklog', '>16')) ++ print(format((sends / sample_time), '>16.3f'), end='') ++ print(format(backlog, '>16.3f')) ++ print() + +- # writes: ops/s, kB/s, avg rtt, and avg exe +- # XXX: include avg xfer size and retransmits? +- write_rpc_stats = self.__rpc_data['WRITE'] +- ops = float(write_rpc_stats[0]) +- kilobytes = float(self.__nfs_data['serverwritebytes']) / 1024 +- rtt = float(write_rpc_stats[6]) +- exe = float(write_rpc_stats[7]) +- +- print('\twrites:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)') +- print('\t\t%.2f' % (ops / sample_time), end=' ') +- print('\t\t%.2f' % (kilobytes / sample_time), end=' ') +- if ops != 0: +- print('\t\t%.2f' % (rtt / ops), end=' ') +- print('\t\t%.2f' % (exe / ops)) +- else: +- print('\t\t0.00', end=' ') +- print('\t\t0.00') ++ self.__print_rpc_op_stats('READ', sample_time) ++ self.__print_rpc_op_stats('WRITE', sample_time) ++ sys.stdout.flush() + +-def parse_stats_file(filename): ++def parse_stats_file(f): + """pop the contents of a mountstats file into a dictionary, + keyed by mount point. each value object is a list of the + lines in the mountstats file corresponding to the mount +@@ -371,7 +647,7 @@ def parse_stats_file(filename): + ms_dict = dict() + key = '' + +- f = file(filename) ++ f.seek(0) + for line in f.readlines(): + words = line.split() + if len(words) == 0: +@@ -385,132 +661,156 @@ def parse_stats_file(filename): + else: + new += [ line.strip() ] + ms_dict[key] = new +- f.close + + return ms_dict + +-def print_mountstats_help(name): +- print('usage: %s [ options ] ' % name) +- print() +- print(' Version %s' % Mountstats_version) +- print() +- print(' Display NFS client per-mount statistics.') +- print() +- print(' --version display the version of this command') +- print(' --nfs display only the NFS statistics') +- print(' --rpc display only the RPC statistics') +- print(' --start sample and save statistics') +- print(' --end resample statistics and compare them with saved') ++def print_mountstats(stats, nfs_only, rpc_only, raw): ++ if nfs_only: ++ stats.display_nfs_options() ++ stats.display_nfs_events() ++ stats.display_nfs_bytes() ++ elif rpc_only: ++ stats.display_stats_header() ++ stats.display_rpc_generic_stats() ++ stats.display_rpc_op_stats() ++ elif raw: ++ stats.display_raw_stats() ++ else: ++ stats.display_nfs_options() ++ stats.display_nfs_bytes() ++ stats.display_rpc_generic_stats() ++ stats.display_rpc_op_stats() + print() + +-def mountstats_command(): ++def mountstats_command(args): + """Mountstats command + """ +- mountpoints = [] +- nfs_only = False +- rpc_only = False +- +- for arg in sys.argv: +- if arg in ['-h', '--help', 'help', 'usage']: +- print_mountstats_help(prog) +- return ++ mountstats = parse_stats_file(args.infile) ++ mountpoints = [os.path.normpath(mp) for mp in args.mountpoints] + +- if arg in ['-v', '--version', 'version']: +- print('%s version %s' % (sys.argv[0], Mountstats_version)) +- sys.exit(0) +- +- if arg in ['-n', '--nfs']: +- nfs_only = True +- continue +- +- if arg in ['-r', '--rpc']: +- rpc_only = True +- continue +- +- if arg in ['-s', '--start']: +- raise Exception('Sampling is not yet implemented') ++ # make certain devices contains only NFS mount points ++ if len(mountpoints) > 0: ++ check = [] ++ for device in mountpoints: ++ stats = DeviceData() ++ try: ++ stats.parse_stats(mountstats[device]) ++ if stats.is_nfs_mountpoint(): ++ check += [device] ++ except KeyError: ++ continue ++ mountpoints = check ++ else: ++ for device, descr in mountstats.items(): ++ stats = DeviceData() ++ stats.parse_stats(descr) ++ if stats.is_nfs_mountpoint(): ++ mountpoints += [device] ++ if len(mountpoints) == 0: ++ print('No NFS mount points were found') ++ return 1 + +- if arg in ['-e', '--end']: +- raise Exception('Sampling is not yet implemented') ++ if args.since: ++ old_mountstats = parse_stats_file(args.since) + +- if arg == sys.argv[0]: +- continue ++ for mp in mountpoints: ++ stats = DeviceData() ++ stats.parse_stats(mountstats[mp]) ++ if not args.since: ++ print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw) ++ elif args.since and mp not in old_mountstats: ++ print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw) ++ else: ++ old_stats = DeviceData() ++ old_stats.parse_stats(old_mountstats[mp]) ++ diff_stats = stats.compare_iostats(old_stats) ++ print_mountstats(diff_stats, args.nfs_only, args.rpc_only, args.raw) + +- mountpoints += [arg] ++ args.infile.close() ++ if args.since: ++ args.since.close() ++ return 0 + +- if mountpoints == []: +- print_mountstats_help(prog) +- return ++def nfsstat_command(args): ++ """nfsstat-like command for NFS mount points ++ """ ++ mountstats = parse_stats_file(args.infile) ++ mountpoints = [os.path.normpath(mp) for mp in args.mountpoints] ++ v3stats = DeviceData() ++ v3stats.setup_accumulator(Nfsv3ops) ++ v4stats = DeviceData() ++ v4stats.setup_accumulator(Nfsv4ops) ++ ++ # ensure stats get printed if neither v3 nor v4 was specified ++ if args.show_v3 or args.show_v4: ++ show_both = False ++ else: ++ show_both = True + +- if rpc_only == True and nfs_only == True: +- print_mountstats_help(prog) +- return ++ # make certain devices contains only NFS mount points ++ if len(mountpoints) > 0: ++ check = [] ++ for device in mountpoints: ++ stats = DeviceData() ++ try: ++ stats.parse_stats(mountstats[device]) ++ if stats.is_nfs_mountpoint(): ++ check += [device] ++ except KeyError: ++ continue ++ mountpoints = check ++ else: ++ for device, descr in mountstats.items(): ++ stats = DeviceData() ++ stats.parse_stats(descr) ++ if stats.is_nfs_mountpoint(): ++ mountpoints += [device] ++ if len(mountpoints) == 0: ++ print('No NFS mount points were found') ++ return 1 + +- mountstats = parse_stats_file('/proc/self/mountstats') ++ if args.since: ++ old_mountstats = parse_stats_file(args.since) + + for mp in mountpoints: +- if mp not in mountstats: +- print('Statistics for mount point %s not found' % mp) +- continue +- + stats = DeviceData() + stats.parse_stats(mountstats[mp]) ++ vers = stats.nfs_version() + +- if not stats.is_nfs_mountpoint(): +- print('Mount point %s exists but is not an NFS mount' % mp) +- continue +- +- if nfs_only: +- stats.display_nfs_options() +- stats.display_nfs_events() +- stats.display_nfs_bytes() +- elif rpc_only: +- stats.display_rpc_generic_stats() +- stats.display_rpc_op_stats() +- else: +- stats.display_nfs_options() +- stats.display_nfs_bytes() +- stats.display_rpc_generic_stats() +- stats.display_rpc_op_stats() +- +-def print_nfsstat_help(name): +- print('usage: %s [ options ]' % name) +- print() +- print(' Version %s' % Mountstats_version) +- print() +- print(' nfsstat-like program that uses NFS client per-mount statistics.') +- print() +- +-def nfsstat_command(): +- print_nfsstat_help(prog) ++ if not args.since: ++ acc_stats = stats ++ elif args.since and mp not in old_mountstats: ++ acc_stats = stats ++ else: ++ old_stats = DeviceData() ++ old_stats.parse_stats(old_mountstats[mp]) ++ acc_stats = stats.compare_iostats(old_stats) + +-def print_iostat_help(name): +- print('usage: %s [ [ ] ] [ ] ' % name) +- print() +- print(' Version %s' % Mountstats_version) +- print() +- print(' iostat-like program to display NFS client per-mount statistics.') +- print() +- print(' The parameter specifies the amount of time in seconds between') +- print(' each report. The first report contains statistics for the time since each') +- print(' file system was mounted. Each subsequent report contains statistics') +- print(' collected during the interval since the previous report.') +- print() +- print(' If the parameter is specified, the value of determines the') +- print(' number of reports generated at seconds apart. If the interval') +- print(' parameter is specified without the parameter, the command generates') +- print(' reports continuously.') +- print() +- print(' If one or more names are specified, statistics for only these') +- print(' mount points will be displayed. Otherwise, all NFS mount points on the') +- print(' client are listed.') +- print() ++ if vers == 3 and (show_both or args.show_v3): ++ v3stats.accumulate_iostats(acc_stats) ++ elif vers == 4 and (show_both or args.show_v4): ++ v4stats.accumulate_iostats(acc_stats) ++ ++ sends, retrans, authrefrsh = map(add, v3stats.client_rpc_stats(), v4stats.client_rpc_stats()) ++ print('Client rpc stats:') ++ print('calls retrans authrefrsh') ++ print('%-11u%-11u%-11u' % (sends, retrans, authrefrsh)) ++ ++ if show_both or args.show_v3: ++ v3stats.display_nfsstat_stats() ++ if show_both or args.show_v4: ++ v4stats.display_nfsstat_stats() ++ ++ args.infile.close() ++ if args.since: ++ args.since.close() ++ return 0 + + def print_iostat_summary(old, new, devices, time): + for device in devices: + stats = DeviceData() + stats.parse_stats(new[device]) +- if not old: ++ if not old or device not in old: + stats.display_iostats(time) + else: + old_stats = DeviceData() +@@ -518,51 +818,28 @@ def print_iostat_summary(old, new, devic + diff_stats = stats.compare_iostats(old_stats) + diff_stats.display_iostats(time) + +-def iostat_command(): ++def iostat_command(args): + """iostat-like command for NFS mount points + """ +- mountstats = parse_stats_file('/proc/self/mountstats') +- devices = [] +- interval_seen = False +- count_seen = False +- +- for arg in sys.argv: +- if arg in ['-h', '--help', 'help', 'usage']: +- print_iostat_help(prog) +- return ++ mountstats = parse_stats_file(args.infile) ++ devices = [os.path.normpath(mp) for mp in args.mountpoints] + +- if arg in ['-v', '--version', 'version']: +- print('%s version %s' % (sys.argv[0], Mountstats_version)) +- return +- +- if arg == sys.argv[0]: +- continue +- +- if arg in mountstats: +- devices += [arg] +- elif not interval_seen: +- interval = int(arg) +- if interval > 0: +- interval_seen = True +- else: +- print('Illegal value') +- return +- elif not count_seen: +- count = int(arg) +- if count > 0: +- count_seen = True +- else: +- print('Illegal value') +- return ++ if args.since: ++ old_mountstats = parse_stats_file(args.since) ++ else: ++ old_mountstats = None + + # make certain devices contains only NFS mount points + if len(devices) > 0: + check = [] + for device in devices: + stats = DeviceData() +- stats.parse_stats(mountstats[device]) +- if stats.is_nfs_mountpoint(): +- check += [device] ++ try: ++ stats.parse_stats(mountstats[device]) ++ if stats.is_nfs_mountpoint(): ++ check += [device] ++ except KeyError: ++ continue + devices = check + else: + for device, descr in mountstats.items(): +@@ -572,45 +849,148 @@ def iostat_command(): + devices += [device] + if len(devices) == 0: + print('No NFS mount points were found') +- return ++ return 1 + +- old_mountstats = None + sample_time = 0 + +- if not interval_seen: ++ if args.interval is None: + print_iostat_summary(old_mountstats, mountstats, devices, sample_time) + return + +- if count_seen: ++ if args.count is not None: ++ count = args.count + while count != 0: + print_iostat_summary(old_mountstats, mountstats, devices, sample_time) + old_mountstats = mountstats +- time.sleep(interval) +- sample_time = interval +- mountstats = parse_stats_file('/proc/self/mountstats') ++ time.sleep(args.interval) ++ sample_time = args.interval ++ mountstats = parse_stats_file(args.infile) + count -= 1 + else: + while True: + print_iostat_summary(old_mountstats, mountstats, devices, sample_time) + old_mountstats = mountstats +- time.sleep(interval) +- sample_time = interval +- mountstats = parse_stats_file('/proc/self/mountstats') +- +-# +-# Main +-# +-prog = os.path.basename(sys.argv[0]) ++ time.sleep(args.interval) ++ sample_time = args.interval ++ mountstats = parse_stats_file(args.infile) ++ ++ args.infile.close() ++ if args.since: ++ args.since.close() ++ return 0 ++ ++class ICMAction(argparse.Action): ++ """Custom action to deal with interval, count, and mountpoints. ++ """ ++ def __call__(self, parser, namespace, values, option_string=None): ++ if namespace.mountpoints is None: ++ namespace.mountpoints = [] ++ if values is None: ++ return ++ elif (type(values) == type([])): ++ for value in values: ++ self._handle_one(namespace, value) ++ else: ++ self._handle_one(namespace, values) ++ ++ def _handle_one(self, namespace, value): ++ try: ++ intval = int(value) ++ if namespace.infile.name != '/proc/self/mountstats': ++ raise argparse.ArgumentError(self, "not allowed with argument -f/--file or -S/--since") ++ self._handle_int(namespace, intval) ++ except ValueError: ++ namespace.mountpoints.append(value) ++ ++ def _handle_int(self, namespace, value): ++ if namespace.interval is None: ++ namespace.interval = value ++ elif namespace.count is None: ++ namespace.count = value ++ else: ++ raise argparse.ArgumentError(self, "too many integer arguments") ++ ++def main(): ++ parser = argparse.ArgumentParser(epilog='For specific sub-command help, ' ++ 'run \'mountstats SUB-COMMAND -h|--help\'') ++ subparsers = parser.add_subparsers(help='sub-command help') ++ ++ common_parser = argparse.ArgumentParser(add_help=False) ++ common_parser.add_argument('-v', '--version', action='version', ++ version='mountstats ' + Mountstats_version) ++ common_parser.add_argument('-f', '--file', default=open('/proc/self/mountstats', 'r'), ++ type=argparse.FileType('r'), dest='infile', ++ help='Read stats from %(dest)s instead of /proc/self/mountstats') ++ common_parser.add_argument('-S', '--since', type=argparse.FileType('r'), ++ metavar='SINCEFILE', ++ help='Show difference between current stats and those in SINCEFILE') ++ ++ mountstats_parser = subparsers.add_parser('mountstats', ++ parents=[common_parser], ++ help='Display a combination of per-op RPC statistics, NFS event counts, and NFS byte counts. ' ++ 'This is the default sub-command if no sub-command is given.') ++ group = mountstats_parser.add_mutually_exclusive_group() ++ group.add_argument('-n', '--nfs', action='store_true', dest='nfs_only', ++ help='Display only the NFS statistics') ++ group.add_argument('-r', '--rpc', action='store_true', dest='rpc_only', ++ help='Display only the RPC statistics') ++ group.add_argument('-R', '--raw', action='store_true', ++ help='Display only the raw statistics') ++ # The mountpoints argument cannot be moved into the common_parser because ++ # it will screw up the parsing of the iostat arguments (interval and count) ++ mountstats_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint', ++ help='Display statistics for this mountpoint. More than one may be specified. ' ++ 'If absent, statistics for all NFS mountpoints will be generated.') ++ mountstats_parser.set_defaults(func=mountstats_command) ++ ++ nfsstat_parser = subparsers.add_parser('nfsstat', ++ parents=[common_parser], ++ help='Display nfsstat-like statistics.') ++ nfsstat_parser.add_argument('-3', action='store_true', dest='show_v3', ++ help='Show NFS version 3 statistics') ++ nfsstat_parser.add_argument('-4', action='store_true', dest='show_v4', ++ help='Show NFS version 4 statistics') ++ # The mountpoints argument cannot be moved into the common_parser because ++ # it will screw up the parsing of the iostat arguments (interval and count) ++ nfsstat_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint', ++ help='Display statistics for this mountpoint. More than one may be specified. ' ++ 'If absent, statistics for all NFS mountpoints will be generated.') ++ nfsstat_parser.set_defaults(func=nfsstat_command) ++ ++ iostat_parser = subparsers.add_parser('iostat', ++ parents=[common_parser], ++ help='Display iostat-like statistics.') ++ iostat_parser.add_argument('interval', nargs='?', action=ICMAction, ++ help='Number of seconds between reports. If absent, only one report will ' ++ 'be generated.') ++ iostat_parser.add_argument('count', nargs='?', action=ICMAction, ++ help='Number of reports generated at seconds apart. If absent, ' ++ 'reports will be generated continuously.') ++ # The mountpoints argument cannot be moved into the common_parser because ++ # it will screw up the parsing of the iostat arguments (interval and count) ++ iostat_parser.add_argument('mountpoints', nargs='*', action=ICMAction, metavar='mountpoint', ++ help='Display statsistics for this mountpoint. More than one may be specified. ' ++ 'If absent, statistics for all NFS mountpoints will be generated.') ++ iostat_parser.set_defaults(func=iostat_command) ++ ++ args = parser.parse_args() ++ return args.func(args) + + try: +- if prog == 'mountstats': +- mountstats_command() +- elif prog == 'ms-nfsstat': +- nfsstat_command() +- elif prog == 'ms-iostat': +- iostat_command() +-except KeyboardInterrupt: +- print('Caught ^C... exiting') ++ if __name__ == '__main__': ++ # Run the mounstats sub-command if no sub-command (or the help flag) ++ # is given. If the argparse module ever gets support for optional ++ # (default) sub-commands, then this can be changed. ++ if len(sys.argv) == 1: ++ sys.argv.insert(1, 'mountstats') ++ elif sys.argv[1] not in ['-h', '--help', 'mountstats', 'iostat', 'nfsstat']: ++ sys.argv.insert(1, 'mountstats') ++ res = main() ++ sys.stdout.close() ++ sys.stderr.close() ++ sys.exit(res) ++except (KeyboardInterrupt, RuntimeError): + sys.exit(1) ++except IOError: ++ pass + +-sys.exit(0) +diff -up nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py.good nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py +--- nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py.good 2015-07-30 15:16:58.774761466 -0400 ++++ nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py 2015-07-30 15:16:20.391062604 -0400 +@@ -213,7 +213,8 @@ class DeviceData: + # the reference to them. so we build new lists here + # for the result object. + for op in result.__rpc_data['ops']: +- result.__rpc_data[op] = map(difference, self.__rpc_data[op], old_stats.__rpc_data[op]) ++ result.__rpc_data[op] = list(map( ++ difference, self.__rpc_data[op], old_stats.__rpc_data[op])) + + # update the remaining keys we care about + result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends'] diff --git a/SOURCES/nfs-utils-1.3.0-nfsdcltrack-v2schema.patch b/SOURCES/nfs-utils-1.3.0-nfsdcltrack-v2schema.patch new file mode 100644 index 0000000..2331f2b --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-nfsdcltrack-v2schema.patch @@ -0,0 +1,850 @@ +diff --git a/Makefile.am b/Makefile.am +index c9e9f87..6f3f3d6 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -9,34 +9,6 @@ MAINTAINERCLEANFILES = Makefile.in + EXTRA_DIST = \ + autogen.sh \ + \ +- debian/changelog \ +- debian/control \ +- debian/copyright \ +- debian/etc.exports \ +- debian/idmapd.conf \ +- debian/nfs-common.conffiles \ +- debian/nfs-common.default \ +- debian/nfs-common.dirs \ +- debian/nfs-common.files \ +- debian/nfs-common.init \ +- debian/nfs-common.install \ +- debian/nfs-common.postinst \ +- debian/nfs-common.postrm \ +- debian/nfs-common.prerm \ +- debian/nfs-kernel-server.NEWS \ +- debian/nfs-kernel-server.conffiles \ +- debian/nfs-kernel-server.default \ +- debian/nfs-kernel-server.dirs \ +- debian/nfs-kernel-server.init \ +- debian/nfs-kernel-server.postinst \ +- debian/nfs-kernel-server.postrm \ +- debian/nfs-kernel-server.prerm \ +- debian/nhfsstone.dirs \ +- debian/nhfsstone.files \ +- debian/nhfsstone.postinst \ +- debian/nhfsstone.prerm \ +- debian/rules \ +- \ + aclocal/bsdsignals.m4 \ + aclocal/nfs-utils.m4 \ + aclocal/kerberos5.m4 \ +diff --git a/support/include/Makefile.am b/support/include/Makefile.am +index 4b33ee9..5c80c8b 100644 +--- a/support/include/Makefile.am ++++ b/support/include/Makefile.am +@@ -3,6 +3,7 @@ + SUBDIRS = nfs rpcsvc sys + + noinst_HEADERS = \ ++ cld.h \ + exportfs.h \ + ha-callout.h \ + misc.h \ +@@ -10,9 +11,13 @@ noinst_HEADERS = \ + nfs_paths.h \ + nfslib.h \ + nfsrpc.h \ ++ nls.h \ + nsm.h \ ++ pseudoflavors.h \ + rpcmisc.h \ ++ sockaddr.h \ + tcpwrapper.h \ ++ v4root.h \ + xio.h \ + xlog.h \ + xmalloc.h \ +diff --git a/tests/Makefile.am b/tests/Makefile.am +index faa8197..1f96264 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -11,3 +11,4 @@ SUBDIRS = nsm_client + MAINTAINERCLEANFILES = Makefile.in + + TESTS = t0001-statd-basic-mon-unmon.sh ++EXTRA_DIST = test-lib.sh $(TESTS) +diff --git a/tests/nsm_client/Makefile.am b/tests/nsm_client/Makefile.am +index 4c15346..a8fc131 100644 +--- a/tests/nsm_client/Makefile.am ++++ b/tests/nsm_client/Makefile.am +@@ -7,6 +7,7 @@ GENFILES_H = nlm_sm_inter.h + + GENFILES = $(GENFILES_CLNT) $(GENFILES_SVC) $(GENFILES_XDR) $(GENFILES_H) + ++EXTRA_DIST = nlm_sm_inter.x + + check_PROGRAMS = nsm_client + nsm_client_SOURCES = $(GENFILES) nsm_client.c +diff --git a/utils/gssd/Makefile.am b/utils/gssd/Makefile.am +index af59791..a4e9c56 100644 +--- a/utils/gssd/Makefile.am ++++ b/utils/gssd/Makefile.am +@@ -8,7 +8,6 @@ sbin_PREFIXED = gssd svcgssd + sbin_PROGRAMS = $(sbin_PREFIXED) + + EXTRA_DIST = \ +- gss_destroy_creds \ + $(man8_MANS) + + COMMON_SRCS = \ +diff --git a/utils/idmapd/Makefile.am b/utils/idmapd/Makefile.am +index 58b33ec..c2f8ba1 100644 +--- a/utils/idmapd/Makefile.am ++++ b/utils/idmapd/Makefile.am +@@ -7,8 +7,7 @@ KPREFIX = @kprefix@ + sbin_PROGRAMS = idmapd + + EXTRA_DIST = \ +- $(man8_MANS) \ +- idmapd.conf ++ $(man8_MANS) + + idmapd_SOURCES = \ + idmapd.c \ +diff --git a/utils/mount/Makefile.am b/utils/mount/Makefile.am +index 5810936..e24f3bd 100644 +--- a/utils/mount/Makefile.am ++++ b/utils/mount/Makefile.am +@@ -8,19 +8,21 @@ man8_MANS = mount.nfs.man umount.nfs.man + man5_MANS = nfs.man + + sbin_PROGRAMS = mount.nfs +-EXTRA_DIST = nfsmount.x $(man8_MANS) $(man5_MANS) ++EXTRA_DIST = nfsmount.conf $(man8_MANS) $(man5_MANS) + mount_common = error.c network.c token.c \ + parse_opt.c parse_dev.c \ + nfsmount.c nfs4mount.c stropts.c\ + mount_constants.h error.h network.h token.h \ + parse_opt.h parse_dev.h \ +- nfs4_mount.h nfs_mount4.h stropts.h version.h \ +- mount_config.h utils.c utils.h ++ nfs4_mount.h stropts.h version.h \ ++ mount_config.h utils.c utils.h \ ++ nfs_mount.h + + if MOUNT_CONFIG + mount_common += configfile.c + man5_MANS += nfsmount.conf.man +-EXTRA_DIST += nfsmount.conf ++else ++EXTRA_DIST += nfsmount.conf.man + endif + + mount_nfs_LDADD = ../../support/nfs/libnfs.a \ +diff --git a/utils/mountd/Makefile.am b/utils/mountd/Makefile.am +index 7db968b..9e1ab5c 100644 +--- a/utils/mountd/Makefile.am ++++ b/utils/mountd/Makefile.am +@@ -7,6 +7,7 @@ RPCPREFIX = rpc. + KPREFIX = @kprefix@ + sbin_PROGRAMS = mountd + ++noinst_HEADERS = fsloc.h + mountd_SOURCES = mountd.c mount_dispatch.c auth.c rmtab.c cache.c \ + svc_run.c fsloc.c v4root.c mountd.h + mountd_LDADD = ../../support/export/libexport.a \ +diff --git a/utils/nfsd/Makefile.am b/utils/nfsd/Makefile.am +index 1536065..39a6e6f 100644 +--- a/utils/nfsd/Makefile.am ++++ b/utils/nfsd/Makefile.am +@@ -7,6 +7,7 @@ RPCPREFIX = rpc. + KPREFIX = @kprefix@ + sbin_PROGRAMS = nfsd + ++noinst_HEADERS = nfssvc.h + nfsd_SOURCES = nfsd.c nfssvc.c + nfsd_LDADD = ../../support/nfs/libnfs.a $(LIBTIRPC) + +diff --git a/utils/nfsdcltrack/Makefile.am b/utils/nfsdcltrack/Makefile.am +index a860ffb..d603f92 100644 +--- a/utils/nfsdcltrack/Makefile.am ++++ b/utils/nfsdcltrack/Makefile.am +@@ -6,6 +6,8 @@ EXTRA_DIST = $(man8_MANS) + AM_CFLAGS += -D_LARGEFILE64_SOURCE + sbin_PROGRAMS = nfsdcltrack + ++noinst_HEADERS = sqlite.h ++ + nfsdcltrack_SOURCES = nfsdcltrack.c sqlite.c + nfsdcltrack_LDADD = ../../support/nfs/libnfs.a $(LIBSQLITE) $(LIBCAP) + +diff --git a/utils/nfsdcltrack/nfsdcltrack.c b/utils/nfsdcltrack/nfsdcltrack.c +index 4334340..fcdda7f 100644 +--- a/utils/nfsdcltrack/nfsdcltrack.c ++++ b/utils/nfsdcltrack/nfsdcltrack.c +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + #ifdef HAVE_SYS_CAPABILITY_H + #include + #include +@@ -49,6 +50,8 @@ + #define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack" + #endif + ++#define NFSD_END_GRACE_FILE "/proc/fs/nfsd/v4_end_grace" ++ + /* defined by RFC 3530 */ + #define NFS4_OPAQUE_LIMIT 1024 + +@@ -210,6 +213,64 @@ cltrack_set_caps(void) + return ret; + } + ++/* Inform the kernel that it's OK to lift nfsd's grace period */ ++static void ++cltrack_lift_grace_period(void) ++{ ++ int fd; ++ ++ fd = open(NFSD_END_GRACE_FILE, O_WRONLY); ++ if (fd < 0) { ++ /* Don't warn if file isn't present */ ++ if (errno != ENOENT) ++ xlog(L_WARNING, "Unable to open %s: %m", ++ NFSD_END_GRACE_FILE); ++ return; ++ } ++ ++ if (write(fd, "Y", 1) < 0) ++ xlog(L_WARNING, "Unable to write to %s: %m", ++ NFSD_END_GRACE_FILE); ++ ++ close(fd); ++ return; ++} ++ ++/* ++ * Fetch the contents of the NFSDCLTRACK_GRACE_START env var. If it's not set ++ * or there's an error converting it to time_t, then return LONG_MAX. ++ */ ++static time_t ++cltrack_get_grace_start(void) ++{ ++ time_t grace_start; ++ char *end; ++ char *grace_start_str = getenv("NFSDCLTRACK_GRACE_START"); ++ ++ if (!grace_start_str) ++ return LONG_MAX; ++ ++ errno = 0; ++ grace_start = strtol(grace_start_str, &end, 0); ++ /* Problem converting or value is too large? */ ++ if (errno) ++ return LONG_MAX; ++ ++ return grace_start; ++} ++ ++static bool ++cltrack_reclaims_complete(void) ++{ ++ time_t grace_start = cltrack_get_grace_start(); ++ ++ /* Don't query DB if we didn't get a valid boot time */ ++ if (grace_start == LONG_MAX) ++ return false; ++ ++ return !sqlite_query_reclaiming(grace_start); ++} ++ + static int + cltrack_init(const char __attribute__((unused)) *unused) + { +@@ -241,7 +302,7 @@ cltrack_init(const char __attribute__((unused)) *unused) + } + + /* set up storage db */ +- ret = sqlite_maindb_init(storagedir); ++ ret = sqlite_prepare_dbh(storagedir); + if (ret) { + xlog(L_ERROR, "Failed to init database: %d", ret); + /* +@@ -250,15 +311,36 @@ cltrack_init(const char __attribute__((unused)) *unused) + * stop upcalling until the problem is resolved. + */ + ret = -EACCES; ++ } else { ++ if (cltrack_reclaims_complete()) ++ cltrack_lift_grace_period(); + } ++ + return ret; + } + ++/* ++ * Fetch the contents of the NFSDCLTRACK_CLIENT_HAS_SESSION env var. If ++ * it's set and the first character is 'Y' then return true. Otherwise ++ * return false. ++ */ ++static bool ++cltrack_client_has_session(void) ++{ ++ char *has_session = getenv("NFSDCLTRACK_CLIENT_HAS_SESSION"); ++ ++ if (has_session && *has_session == 'Y') ++ return true; ++ ++ return false; ++} ++ + static int + cltrack_create(const char *id) + { + int ret; + ssize_t len; ++ bool has_session; + + xlog(D_GENERAL, "%s: create client record.", __func__); + +@@ -270,7 +352,12 @@ cltrack_create(const char *id) + if (len < 0) + return (int)len; + +- ret = sqlite_insert_client(blob, len); ++ has_session = cltrack_client_has_session(); ++ ++ ret = sqlite_insert_client(blob, len, has_session, false); ++ ++ if (!ret && has_session && cltrack_reclaims_complete()) ++ cltrack_lift_grace_period(); + + return ret ? -EREMOTEIO : ret; + } +@@ -297,7 +384,8 @@ cltrack_remove(const char *id) + } + + static int +-cltrack_check_legacy(const unsigned char *blob, const ssize_t len) ++cltrack_check_legacy(const unsigned char *blob, const ssize_t len, ++ bool has_session) + { + int ret; + struct stat st; +@@ -323,7 +411,7 @@ cltrack_check_legacy(const unsigned char *blob, const ssize_t len) + } + + /* Dir exists, try to insert record into db */ +- ret = sqlite_insert_client(blob, len); ++ ret = sqlite_insert_client(blob, len, has_session, has_session); + if (ret) { + xlog(D_GENERAL, "Failed to insert client: %d", ret); + return -EREMOTEIO; +@@ -343,6 +431,7 @@ cltrack_check(const char *id) + { + int ret; + ssize_t len; ++ bool has_session; + + xlog(D_GENERAL, "%s: check client record", __func__); + +@@ -354,9 +443,11 @@ cltrack_check(const char *id) + if (len < 0) + return (int)len; + +- ret = sqlite_check_client(blob, len); ++ has_session = cltrack_client_has_session(); ++ ++ ret = sqlite_check_client(blob, len, has_session); + if (ret) +- ret = cltrack_check_legacy(blob, len); ++ ret = cltrack_check_legacy(blob, len, has_session); + + return ret ? -EPERM : ret; + } +diff --git a/utils/nfsdcltrack/sqlite.c b/utils/nfsdcltrack/sqlite.c +index bac6789..eb1711a 100644 +--- a/utils/nfsdcltrack/sqlite.c ++++ b/utils/nfsdcltrack/sqlite.c +@@ -21,17 +21,15 @@ + * Explanation: + * + * This file contains the code to manage the sqlite backend database for the +- * clstated upcall daemon. ++ * nfsdcltrack usermodehelper upcall program. + * + * The main database is called main.sqlite and contains the following tables: + * + * parameters: simple key/value pairs for storing database info + * +- * clients: one column containing a BLOB with the as sent by the client +- * and a timestamp (in epoch seconds) of when the record was +- * established +- * +- * FIXME: should we also record the fsid being accessed? ++ * clients: an "id" column containing a BLOB with the long-form clientid as ++ * sent by the client, a "time" column containing a timestamp (in ++ * epoch seconds) of when the record was last updated. + */ + + #ifdef HAVE_CONFIG_H +@@ -52,10 +50,10 @@ + + #include "xlog.h" + +-#define CLD_SQLITE_SCHEMA_VERSION 1 ++#define CLTRACK_SQLITE_LATEST_SCHEMA_VERSION 1 + + /* in milliseconds */ +-#define CLD_SQLITE_BUSY_TIMEOUT 10000 ++#define CLTRACK_SQLITE_BUSY_TIMEOUT 10000 + + /* private data structures */ + +@@ -90,135 +88,192 @@ mkdir_if_not_exist(const char *dirname) + return ret; + } + +-/* Open the database and set up the database handle for it */ +-int +-sqlite_prepare_dbh(const char *topdir) ++static int ++sqlite_query_schema_version(void) + { + int ret; ++ sqlite3_stmt *stmt = NULL; + +- /* Do nothing if the database handle is already set up */ +- if (dbh) +- return 0; +- +- ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", topdir); +- if (ret < 0) +- return ret; +- +- buf[PATH_MAX - 1] = '\0'; +- +- ret = sqlite3_open(buf, &dbh); ++ /* prepare select query */ ++ ret = sqlite3_prepare_v2(dbh, ++ "SELECT value FROM parameters WHERE key == \"version\";", ++ -1, &stmt, NULL); + if (ret != SQLITE_OK) { +- xlog(L_ERROR, "Unable to open main database: %d", ret); +- dbh = NULL; +- return ret; ++ xlog(L_ERROR, "Unable to prepare select statement: %s", ++ sqlite3_errmsg(dbh)); ++ ret = 0; ++ goto out; + } + +- ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT); +- if (ret != SQLITE_OK) { +- xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret); +- sqlite3_close(dbh); +- dbh = NULL; ++ /* query schema version */ ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_ROW) { ++ xlog(L_ERROR, "Select statement execution failed: %s", ++ sqlite3_errmsg(dbh)); ++ ret = 0; ++ goto out; + } + ++ ret = sqlite3_column_int(stmt, 0); ++out: ++ sqlite3_finalize(stmt); + return ret; + } + + /* +- * Open the "main" database, and attempt to initialize it by creating the +- * parameters table and inserting the schema version into it. Ignore any errors +- * from that, and then attempt to select the version out of it again. If the +- * version appears wrong, then assume that the DB is corrupt or has been +- * upgraded, and return an error. If all of that works, then attempt to create +- * the "clients" table. ++ * Start an exclusive transaction and recheck the DB schema version. If it's ++ * still zero (indicating a new database) then set it up. If that all works, ++ * then insert schema version into the parameters table and commit the ++ * transaction. On any error, rollback the transaction. + */ + int +-sqlite_maindb_init(const char *topdir) ++sqlite_maindb_init_v1(void) + { + int ret; + char *err = NULL; +- sqlite3_stmt *stmt = NULL; + +- ret = mkdir_if_not_exist(topdir); +- if (ret) ++ /* Start a transaction */ ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); + return ret; ++ } + +- ret = sqlite_prepare_dbh(topdir); +- if (ret) +- return ret; ++ /* ++ * Check schema version again. This time, under an exclusive ++ * transaction to guard against racing DB setup attempts ++ */ ++ ret = sqlite_query_schema_version(); ++ switch (ret) { ++ case 0: ++ /* Query failed again -- set up DB */ ++ break; ++ case CLTRACK_SQLITE_LATEST_SCHEMA_VERSION: ++ /* Someone else raced in and set it up */ ++ ret = 0; ++ goto rollback; ++ default: ++ /* Something went wrong -- fail! */ ++ ret = -EINVAL; ++ goto rollback; ++ } + +- /* Try to create table */ +- ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters " ++ ret = sqlite3_exec(dbh, "CREATE TABLE parameters " + "(key TEXT PRIMARY KEY, value TEXT);", + NULL, NULL, &err); + if (ret != SQLITE_OK) { +- xlog(L_ERROR, "Unable to create parameter table: %d", ret); +- goto out_err; ++ xlog(L_ERROR, "Unable to create parameter table: %s", err); ++ goto rollback; + } + +- /* insert version into table -- ignore error if it fails */ +- ret = snprintf(buf, sizeof(buf), +- "INSERT OR IGNORE INTO parameters values (\"version\", " +- "\"%d\");", CLD_SQLITE_SCHEMA_VERSION); ++ /* create the "clients" table */ ++ ret = sqlite3_exec(dbh, "CREATE TABLE clients (id BLOB PRIMARY KEY, " ++ "time INTEGER);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create clients table: %s", err); ++ goto rollback; ++ } ++ ++ ++ /* insert version into parameters table */ ++ ret = snprintf(buf, sizeof(buf), "INSERT OR FAIL INTO parameters " ++ "values (\"version\", \"%d\");", ++ CLTRACK_SQLITE_LATEST_SCHEMA_VERSION); + if (ret < 0) { +- goto out_err; ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; + } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); + ret = -EINVAL; +- goto out_err; ++ goto rollback; + } + + ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); + if (ret != SQLITE_OK) { +- xlog(L_ERROR, "Unable to insert into parameter table: %d", +- ret); +- goto out_err; ++ xlog(L_ERROR, "Unable to insert into parameter table: %s", err); ++ goto rollback; + } + +- ret = sqlite3_prepare_v2(dbh, +- "SELECT value FROM parameters WHERE key == \"version\";", +- -1, &stmt, NULL); ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); + if (ret != SQLITE_OK) { +- xlog(L_ERROR, "Unable to prepare select statement: %d", ret); +- goto out_err; ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; + } ++out: ++ sqlite3_free(err); ++ return ret; + +- /* check schema version */ +- ret = sqlite3_step(stmt); +- if (ret != SQLITE_ROW) { +- xlog(L_ERROR, "Select statement execution failed: %s", ++rollback: ++ /* Attempt to rollback the transaction */ ++ sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ goto out; ++} ++ ++/* Open the database and set up the database handle for it */ ++int ++sqlite_prepare_dbh(const char *topdir) ++{ ++ int ret; ++ ++ /* Do nothing if the database handle is already set up */ ++ if (dbh) ++ return 0; ++ ++ ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", topdir); ++ if (ret < 0) ++ return ret; ++ ++ buf[PATH_MAX - 1] = '\0'; ++ ++ /* open a new DB handle */ ++ ret = sqlite3_open(buf, &dbh); ++ if (ret != SQLITE_OK) { ++ /* try to create the dir */ ++ ret = mkdir_if_not_exist(topdir); ++ if (ret) ++ goto out_close; ++ ++ /* retry open */ ++ ret = sqlite3_open(buf, &dbh); ++ if (ret != SQLITE_OK) ++ goto out_close; ++ } ++ ++ /* set busy timeout */ ++ ret = sqlite3_busy_timeout(dbh, CLTRACK_SQLITE_BUSY_TIMEOUT); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to set sqlite busy timeout: %s", + sqlite3_errmsg(dbh)); +- goto out_err; ++ goto out_close; + } + +- /* process SELECT result */ +- ret = sqlite3_column_int(stmt, 0); +- if (ret != CLD_SQLITE_SCHEMA_VERSION) { ++ ret = sqlite_query_schema_version(); ++ switch (ret) { ++ case CLTRACK_SQLITE_LATEST_SCHEMA_VERSION: ++ /* DB is already set up. Do nothing */ ++ ret = 0; ++ break; ++ case 0: ++ /* Query failed -- try to set up new DB */ ++ ret = sqlite_maindb_init_v1(); ++ if (ret) ++ goto out_close; ++ break; ++ default: ++ /* Unknown DB version -- downgrade? Fail */ + xlog(L_ERROR, "Unsupported database schema version! " + "Expected %d, got %d.", +- CLD_SQLITE_SCHEMA_VERSION, ret); ++ CLTRACK_SQLITE_LATEST_SCHEMA_VERSION, ret); + ret = -EINVAL; +- goto out_err; +- } +- +- /* now create the "clients" table */ +- ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients " +- "(id BLOB PRIMARY KEY, time INTEGER);", +- NULL, NULL, &err); +- if (ret != SQLITE_OK) { +- xlog(L_ERROR, "Unable to create clients table: %s", err); +- goto out_err; ++ goto out_close; + } + +- sqlite3_free(err); +- sqlite3_finalize(stmt); +- return 0; +- +-out_err: +- if (err) { +- xlog(L_ERROR, "sqlite error: %s", err); +- sqlite3_free(err); +- } +- sqlite3_finalize(stmt); ++ return ret; ++out_close: + sqlite3_close(dbh); ++ dbh = NULL; + return ret; + } + +@@ -228,14 +283,20 @@ out_err: + * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0) + */ + int +-sqlite_insert_client(const unsigned char *clname, const size_t namelen) ++sqlite_insert_client(const unsigned char *clname, const size_t namelen, ++ const bool has_session, const bool zerotime) + { + int ret; + sqlite3_stmt *stmt = NULL; + +- ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES " +- "(?, strftime('%s', 'now'));", -1, +- &stmt, NULL); ++ if (zerotime) ++ ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients " ++ "VALUES (?, 0, ?);", -1, &stmt, NULL); ++ else ++ ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients " ++ "VALUES (?, strftime('%s', 'now'), ?);", -1, ++ &stmt, NULL); ++ + if (ret != SQLITE_OK) { + xlog(L_ERROR, "%s: insert statement prepare failed: %s", + __func__, sqlite3_errmsg(dbh)); +@@ -250,6 +311,13 @@ sqlite_insert_client(const unsigned char *clname, const size_t namelen) + goto out_err; + } + ++ ret = sqlite3_bind_int(stmt, 2, (int)has_session); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind int failed: %s", __func__, ++ sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ + ret = sqlite3_step(stmt); + if (ret == SQLITE_DONE) + ret = SQLITE_OK; +@@ -305,7 +373,8 @@ out_err: + * return an error. + */ + int +-sqlite_check_client(const unsigned char *clname, const size_t namelen) ++sqlite_check_client(const unsigned char *clname, const size_t namelen, ++ const bool has_session) + { + int ret; + sqlite3_stmt *stmt = NULL; +@@ -340,6 +409,12 @@ sqlite_check_client(const unsigned char *clname, const size_t namelen) + goto out_err; + } + ++ /* Only update timestamp for v4.0 clients */ ++ if (has_session) { ++ ret = SQLITE_OK; ++ goto out_err; ++ } ++ + sqlite3_finalize(stmt); + stmt = NULL; + ret = sqlite3_prepare_v2(dbh, "UPDATE OR FAIL clients SET " +@@ -398,3 +473,43 @@ sqlite_remove_unreclaimed(time_t grace_start) + sqlite3_free(err); + return ret; + } ++ ++/* ++ * Are there any clients that are possibly still reclaiming? Return a positive ++ * integer (usually number of clients) if so. If not, then return 0. On any ++ * error, return non-zero. ++ */ ++int ++sqlite_query_reclaiming(const time_t grace_start) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ ret = sqlite3_prepare_v2(dbh, "SELECT count(*) FROM clients WHERE " ++ "time < ? OR has_session != 1", -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: unable to prepare select statement: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_bind_int64(stmt, 1, (sqlite3_int64)grace_start); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind int64 failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_ROW) { ++ xlog(L_ERROR, "%s: unexpected return code from select: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_column_int(stmt, 0); ++ sqlite3_finalize(stmt); ++ xlog(D_GENERAL, "%s: there are %d clients that have not completed " ++ "reclaim", __func__, ret); ++ return ret; ++} +diff --git a/utils/nfsdcltrack/sqlite.h b/utils/nfsdcltrack/sqlite.h +index ebf04c3..06e7c04 100644 +--- a/utils/nfsdcltrack/sqlite.h ++++ b/utils/nfsdcltrack/sqlite.h +@@ -21,10 +21,12 @@ + #define _SQLITE_H_ + + int sqlite_prepare_dbh(const char *topdir); +-int sqlite_maindb_init(const char *topdir); +-int sqlite_insert_client(const unsigned char *clname, const size_t namelen); ++int sqlite_insert_client(const unsigned char *clname, const size_t namelen, ++ const bool has_session, const bool zerotime); + int sqlite_remove_client(const unsigned char *clname, const size_t namelen); +-int sqlite_check_client(const unsigned char *clname, const size_t namelen); ++int sqlite_check_client(const unsigned char *clname, const size_t namelen, ++ const bool has_session); + int sqlite_remove_unreclaimed(const time_t grace_start); ++int sqlite_query_reclaiming(const time_t grace_start); + + #endif /* _SQLITE_H */ +diff --git a/utils/nfsidmap/Makefile.am b/utils/nfsidmap/Makefile.am +index 737a219..91cedfd 100644 +--- a/utils/nfsidmap/Makefile.am ++++ b/utils/nfsidmap/Makefile.am +@@ -7,4 +7,4 @@ nfsidmap_SOURCES = nfsidmap.c + nfsidmap_LDADD = $(LIBNFSIDMAP) -lkeyutils ../../support/nfs/libnfs.a + + MAINTAINERCLEANFILES = Makefile.in +-EXTRA_DIST = id_resolver.conf ++EXTRA_DIST = id_resolver.conf $(man8_MANS) +diff --git a/utils/osd_login/Makefile.am b/utils/osd_login/Makefile.am +index 20c2d8c..ded1fd3 100644 +--- a/utils/osd_login/Makefile.am ++++ b/utils/osd_login/Makefile.am +@@ -4,6 +4,6 @@ + # overridden at config time. + sbindir = /sbin + +-sbin_SCRIPTS = osd_login ++dist_sbin_SCRIPTS = osd_login + + MAINTAINERCLEANFILES = Makefile.in +diff --git a/utils/statd/Makefile.am b/utils/statd/Makefile.am +index dc2bfc4..152b680 100644 +--- a/utils/statd/Makefile.am ++++ b/utils/statd/Makefile.am +@@ -8,7 +8,7 @@ sbin_PROGRAMS = statd sm-notify + dist_sbin_SCRIPTS = start-statd + statd_SOURCES = callback.c notlist.c misc.c monitor.c hostname.c \ + simu.c stat.c statd.c svc_run.c rmtcall.c \ +- notlist.h statd.h system.h version.h ++ notlist.h statd.h system.h + sm_notify_SOURCES = sm-notify.c + + BUILT_SOURCES = $(GENFILES) +@@ -20,7 +20,7 @@ sm_notify_LDADD = ../../support/nsm/libnsm.a \ + ../../support/nfs/libnfs.a \ + $(LIBNSL) $(LIBCAP) $(LIBTIRPC) + +-EXTRA_DIST = sim_sm_inter.x $(man8_MANS) COPYRIGHT simulate.c ++EXTRA_DIST = sim_sm_inter.x $(man8_MANS) simulate.c + + if CONFIG_RPCGEN + RPCGEN = $(top_builddir)/tools/rpcgen/rpcgen diff --git a/SOURCES/nfs-utils-1.3.0-systemd-decouple.patch b/SOURCES/nfs-utils-1.3.0-systemd-decouple.patch new file mode 100644 index 0000000..83805ef --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-systemd-decouple.patch @@ -0,0 +1,24 @@ +diff -up nfs-utils-1.3.0/systemd/nfs-server.service.orig nfs-utils-1.3.0/systemd/nfs-server.service +--- nfs-utils-1.3.0/systemd/nfs-server.service.orig 2015-09-29 16:45:31.998991287 -0400 ++++ nfs-utils-1.3.0/systemd/nfs-server.service 2015-09-29 16:55:00.386866977 -0400 +@@ -1,7 +1,7 @@ + [Unit] + Description=NFS server and services + DefaultDependencies=no +-Requires= network.target proc-fs-nfsd.mount rpcbind.service ++Requires= network.target proc-fs-nfsd.mount rpcbind.target + Requires= nfs-mountd.service + Wants=rpc-statd.service nfs-idmapd.service + Wants=rpc-statd-notify.service +diff -up nfs-utils-1.3.0/systemd/rpc-statd.service.orig nfs-utils-1.3.0/systemd/rpc-statd.service +--- nfs-utils-1.3.0/systemd/rpc-statd.service.orig 2015-09-29 16:45:31.999991304 -0400 ++++ nfs-utils-1.3.0/systemd/rpc-statd.service 2015-09-29 16:55:30.766394792 -0400 +@@ -2,7 +2,7 @@ + Description=NFS status monitor for NFSv2/3 locking. + DefaultDependencies=no + Conflicts=umount.target +-Requires=nss-lookup.target rpcbind.service ++Requires=nss-lookup.target rpcbind.target + After=network.target nss-lookup.target rpcbind.service + + PartOf=nfs-utils.service diff --git a/SOURCES/nfs-utils-1.3.0-systemd-idmapd-varlib.patch b/SOURCES/nfs-utils-1.3.0-systemd-idmapd-varlib.patch new file mode 100644 index 0000000..8ef0518 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-systemd-idmapd-varlib.patch @@ -0,0 +1,25 @@ +commit 6efdb0440daf3ed304a3c1115f01e76e89d792a7 +Author: Chris Mayo +Date: Fri Jan 23 10:35:32 2015 -0500 + + systemd: Ensure RPC pipefs is mounted before rpc.idmapd starts + + rpc.idmapd aborts on start-up if RPC pipefs is not present. + + Needed if GSS services are not used. + + Signed-off-by: Chris Mayo + Signed-off-by: Steve Dickson + +diff --git a/systemd/nfs-idmapd.service b/systemd/nfs-idmapd.service +index 726038d..e84f8c8 100644 +--- a/systemd/nfs-idmapd.service ++++ b/systemd/nfs-idmapd.service +@@ -1,5 +1,7 @@ + [Unit] + Description=NFSv4 ID-name mapping service ++Requires=var-lib-nfs-rpc_pipefs.mount ++After=var-lib-nfs-rpc_pipefs.mount + + BindsTo=nfs-server.service + diff --git a/SOURCES/nfs-utils-1.3.0-systemd-rpcbind.patch b/SOURCES/nfs-utils-1.3.0-systemd-rpcbind.patch new file mode 100644 index 0000000..4d7d2b8 --- /dev/null +++ b/SOURCES/nfs-utils-1.3.0-systemd-rpcbind.patch @@ -0,0 +1,109 @@ +diff -up nfs-utils-1.3.0/systemd/auth-rpcgss-module.service.orig nfs-utils-1.3.0/systemd/auth-rpcgss-module.service +--- nfs-utils-1.3.0/systemd/auth-rpcgss-module.service.orig 2015-06-25 15:01:27.650527038 -0400 ++++ nfs-utils-1.3.0/systemd/auth-rpcgss-module.service 2015-06-25 15:01:45.166856358 -0400 +@@ -6,6 +6,7 @@ + # unit will fail. But that's OK.) + [Unit] + Description=Kernel Module supporting RPCSEC_GSS ++DefaultDependencies=no + Before=gssproxy.service rpc-svcgssd.service rpc-gssd.service + Wants=gssproxy.service rpc-svcgssd.service rpc-gssd.service + ConditionPathExists=/etc/krb5.keytab +diff -up nfs-utils-1.3.0/systemd/nfs-config.service.orig nfs-utils-1.3.0/systemd/nfs-config.service +--- nfs-utils-1.3.0/systemd/nfs-config.service.orig 2014-03-25 11:12:07.000000000 -0400 ++++ nfs-utils-1.3.0/systemd/nfs-config.service 2015-06-25 15:01:45.166856358 -0400 +@@ -1,5 +1,7 @@ + [Unit] + Description=Preprocess NFS configuration ++After=local-fs.target ++DefaultDependencies=no + + [Service] + Type=oneshot +diff -up nfs-utils-1.3.0/systemd/nfs-idmapd.service.orig nfs-utils-1.3.0/systemd/nfs-idmapd.service +--- nfs-utils-1.3.0/systemd/nfs-idmapd.service.orig 2015-06-25 15:01:27.663527282 -0400 ++++ nfs-utils-1.3.0/systemd/nfs-idmapd.service 2015-06-25 15:01:45.166856358 -0400 +@@ -1,7 +1,8 @@ + [Unit] + Description=NFSv4 ID-name mapping service ++DefaultDependencies=no + Requires=var-lib-nfs-rpc_pipefs.mount +-After=var-lib-nfs-rpc_pipefs.mount ++After=var-lib-nfs-rpc_pipefs.mount local-fs.target + + BindsTo=nfs-server.service + +diff -up nfs-utils-1.3.0/systemd/nfs-mountd.service.orig nfs-utils-1.3.0/systemd/nfs-mountd.service +--- nfs-utils-1.3.0/systemd/nfs-mountd.service.orig 2015-06-25 15:01:27.657527169 -0400 ++++ nfs-utils-1.3.0/systemd/nfs-mountd.service 2015-06-25 15:01:45.166856358 -0400 +@@ -1,8 +1,9 @@ + [Unit] + Description=NFS Mount Daemon ++DefaultDependencies=no + Requires=proc-fs-nfsd.mount + After=proc-fs-nfsd.mount +-After=network.target ++After=network.target local-fs.target + BindsTo=nfs-server.service + + Wants=nfs-config.service +diff -up nfs-utils-1.3.0/systemd/nfs-server.service.orig nfs-utils-1.3.0/systemd/nfs-server.service +--- nfs-utils-1.3.0/systemd/nfs-server.service.orig 2015-06-25 15:01:45.166856358 -0400 ++++ nfs-utils-1.3.0/systemd/nfs-server.service 2015-06-25 15:02:29.916697572 -0400 +@@ -1,11 +1,13 @@ + [Unit] + Description=NFS server and services +-Requires= network.target proc-fs-nfsd.mount rpcbind.target ++DefaultDependencies=no ++Requires= network.target proc-fs-nfsd.mount rpcbind.service + Requires= nfs-mountd.service + Wants=rpc-statd.service nfs-idmapd.service + Wants=rpc-statd-notify.service + +-After= network.target proc-fs-nfsd.mount rpcbind.target nfs-mountd.service ++After= local-fs.target ++After= network.target proc-fs-nfsd.mount rpcbind.service nfs-mountd.service + After= nfs-idmapd.service rpc-statd.service + Before= rpc-statd-notify.service + +diff -up nfs-utils-1.3.0/systemd/rpc-statd-notify.service.orig nfs-utils-1.3.0/systemd/rpc-statd-notify.service +--- nfs-utils-1.3.0/systemd/rpc-statd-notify.service.orig 2015-06-25 15:01:27.658527188 -0400 ++++ nfs-utils-1.3.0/systemd/rpc-statd-notify.service 2015-06-25 15:05:13.667774624 -0400 +@@ -1,7 +1,8 @@ + [Unit] + Description=Notify NFS peers of a restart +-Requires=network-online.target +-After=network-online.target nss-lookup.target ++DefaultDependencies=no ++Requires=network.target ++After=local-fs.target network.target nss-lookup.target + + # Do not start up in HA environments + ConditionPathExists=!/var/lib/nfs/statd/sm.ha +diff -up nfs-utils-1.3.0/systemd/rpc-svcgssd.service.orig nfs-utils-1.3.0/systemd/rpc-svcgssd.service +--- nfs-utils-1.3.0/systemd/rpc-svcgssd.service.orig 2014-03-25 11:12:07.000000000 -0400 ++++ nfs-utils-1.3.0/systemd/rpc-svcgssd.service 2015-06-25 15:01:45.166856358 -0400 +@@ -1,7 +1,8 @@ + [Unit] + Description=RPC security service for NFS server ++DefaultDependencies=no + Requires=var-lib-nfs-rpc_pipefs.mount +-After=var-lib-nfs-rpc_pipefs.mount ++After=var-lib-nfs-rpc_pipefs.mount local-fs.target + PartOf=nfs-server.service + PartOf=nfs-utils.service + +diff -up nfs-utils-1.3.0/systemd/rpc-statd.service.orig nfs-utils-1.3.0/systemd/rpc-statd.service +--- nfs-utils-1.3.0/systemd/rpc-statd.service.orig 2014-03-25 11:12:07.000000000 -0400 ++++ nfs-utils-1.3.0/systemd/rpc-statd.service 2015-09-14 11:49:19.547384763 -0400 +@@ -2,8 +2,8 @@ + Description=NFS status monitor for NFSv2/3 locking. + DefaultDependencies=no + Conflicts=umount.target +-Requires=nss-lookup.target rpcbind.target +-After=network.target nss-lookup.target rpcbind.target ++Requires=nss-lookup.target rpcbind.service ++After=network.target nss-lookup.target rpcbind.service + + PartOf=nfs-utils.service + diff --git a/SOURCES/nfs-utils_env.sh b/SOURCES/nfs-utils_env.sh index e488596..6462a92 100644 --- a/SOURCES/nfs-utils_env.sh +++ b/SOURCES/nfs-utils_env.sh @@ -20,6 +20,22 @@ if [ -n "$LOCKD_TCPPORT" -o -n "$LOCKD_UDPPORT" ]; then /sbin/sysctl -w fs.nfs.nlm_udpport=$LOCKD_UDPPORT >/dev/null 2>&1 fi +if [ -n "$MOUNTD_PORT" ]; then + RPCMOUNTDOPTS="$RPCMOUNTDOPTS -p $MOUNTD_PORT" +fi + +if [ -n "$STATD_PORT" ]; then + STATDARG="$STATDARG -p $STATD_PORT" +fi + +if [ -n "$STATD_OUTGOING_PORT" ]; then + STATDARG="$STATDARG -o $STATD_OUTGOING_PORT" +fi + +if [ -n "$STATD_HA_CALLOUT" ]; then + STATDARG="$STATDARG -H $STATD_HA_CALLOUT" +fi + if [ -n "$NFSD_V4_GRACE" ]; then grace="-G $NFSD_V4_GRACE" fi diff --git a/SOURCES/nfs.sysconfig b/SOURCES/nfs.sysconfig index 7468ea9..4b71eff 100644 --- a/SOURCES/nfs.sysconfig +++ b/SOURCES/nfs.sysconfig @@ -24,9 +24,19 @@ RPCNFSDARGS="" # # Optional arguments passed to rpc.mountd. See rpc.mountd(8) RPCMOUNTDOPTS="" +# Port rpc.mountd should listen on. +#MOUNTD_PORT=892 # # Optional arguments passed to rpc.statd. See rpc.statd(8) STATDARG="" +# Port rpc.statd should listen on. +#STATD_PORT=662 +# Outgoing port statd should used. The default is port +# is random +#STATD_OUTGOING_PORT=2020 +# Specify callout program +#STATD_HA_CALLOUT="/usr/local/bin/foo" +# # # Optional arguments passed to sm-notify. See sm-notify(8) SMNOTIFYARGS="" diff --git a/SPECS/nfs-utils.spec b/SPECS/nfs-utils.spec index 527fc93..cf8d7b8 100644 --- a/SPECS/nfs-utils.spec +++ b/SPECS/nfs-utils.spec @@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser Name: nfs-utils URL: http://sourceforge.net/projects/nfs Version: 1.3.0 -Release: 0.8%{?dist} +Release: 0.21%{?dist} Epoch: 1 # group all 32bit related archs @@ -35,6 +35,21 @@ Patch015: nfs-utils-1.3.0-exportfs-ipv6-arg.patch Patch016: nfs-utils-1.3.0-exportfs-noreaddirplus.patch Patch017: nfs-utils-1.3.0-systemd-idmapd.patch Patch018: nfs-utils-1.3.0-systemd-ha-nonotify.patch +# +# RHEL7.2 +# +Patch019: nfs-utils-1.3.0-blkmapd-pnfs.patch +Patch020: nfs-utils-1.3.0-mountstats-update.patch +Patch021: nfs-utils-1.3.0-mountd-v4root-sec.patch +Patch022: nfs-utils-1.3.0-systemd-idmapd-varlib.patch +Patch023: nfs-utils-1.3.0-nfsdcltrack-v2schema.patch +Patch024: nfs-utils-1.3.0-mountd-manpage-args.patch +Patch025: nfs-utils-1.3.0-mountd-manpage-netconfig.patch +Patch026: nfs-utils-1.3.0-systemd-rpcbind.patch +Patch027: nfs-utils-1.3.0-blkmapd-loop.patch +Patch028: nfs-utils-1.3.0-gssd-noclear-retval.patch +Patch029: nfs-utils-1.3.0-gssd-tgt-flood.patch +Patch030: nfs-utils-1.3.0-systemd-decouple.patch Patch100: nfs-utils-1.2.1-statdpath-man.patch Patch101: nfs-utils-1.2.1-exp-subtree-warn-off.patch @@ -129,6 +144,30 @@ This package also contains the mount.nfs and umount.nfs program. %patch017 -p1 # 1182692 - disable sm-notify on 'systemctl start nfs-server' no longer works %patch018 -p1 +# 1214821 - nfs-utils updates for block pnfs +%patch019 -p1 +# 1215808 - Update mountstats command to the latest upstream version +%patch020 -p1 +# 1187223 - rpc.mountd can set up pseudo exports without... +%patch021 -p1 +# 1164064 - RHEL-7.1 regression fail: service nfs-idmapd start fail +%patch022 -p1 +# 1234598 - Early grace period expiry with NFSv4.1 +%patch023 -p1 +# 1003558 - In rpc.mountd man page -V -f -p -H and so on need and args +%patch024 -p1 +# 1196646 - man pages nfs/mount.fs should mention /etc/nfsmount.conf ... +%patch025 -p1 +# 1171603 - Require rpcbind.service in nfs-server.service rather than ... +%patch026 -p1 +# 237301 - blkmapd: Fix infinite loop when reading serial +%patch027 -p1 +# 1087350 - [gssd] code defects in gssd_search_krb5_keytab()... +%patch028 -p1 +# 1264999 - rpc.gssd fetches a TGT on every machine credential upcall +%patch029 -p1 +# 1266993 - restarting rpbind also restart the the nfs server +%patch030 -p1 %patch100 -p1 %patch101 -p1 @@ -202,9 +241,13 @@ install -m 755 %{SOURCE3} $RPM_BUILD_ROOT/usr/lib/systemd/scripts/nfs-utils_env. cd $RPM_BUILD_ROOT%{_unitdir} ln -s nfs-server.service nfs.service ln -s rpc-gssd.service nfs-secure.service +ln -s rpc-gssd.service rpcgssd.service ln -s rpc-svcgssd.service nfs-secure-server.service +ln -s rpc-svcgssd.service rpcsvcgssd.service ln -s nfs-idmapd.service nfs-idmap.service +ln -s nfs-idmapd.service rpcidmapd.service ln -s rpc-statd.service nfs-lock.service +ln -s rpc-statd.service nfslock.service mkdir -p $RPM_BUILD_ROOT%{_sharedstatedir}/nfs/rpc_pipefs @@ -221,7 +264,6 @@ mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/exports.d rm -rf $RPM_BUILD_ROOT/* %pre - # move files so the running service will have this applied as well for x in gssd svcgssd idmapd ; do if [ -f /var/lock/subsys/rpc.$x ]; then @@ -229,9 +271,23 @@ for x in gssd svcgssd idmapd ; do fi done -/usr/sbin/useradd -l -c "RPC Service User" -r \ - -s /sbin/nologin -u 29 -d /var/lib/nfs rpcuser 2>/dev/null || : -/usr/sbin/groupadd -g 29 rpcuser 2>/dev/null || : +%define rpcuser_uid 29 +# Create rpcuser gid as long as it does not already exist +cat /etc/group | cut -d':' -f 1 | grep --quiet rpcuser 2>/dev/null +if [ "$?" -eq 1 ]; then + /usr/sbin/groupadd -g %{rpcuser_uid} rpcuser >/dev/null 2>&1 || : +else + /usr/sbin/groupmod -g %{rpcuser_uid} rpcuser >/dev/null 2>&1 || : +fi + +# Create rpcuser uid as long as it does not already exist. +cat /etc/passwd | cut -d':' -f 1 | grep --quiet rpcuser 2>/dev/null +if [ "$?" -eq 1 ]; then + /usr/sbin/useradd -l -c "RPC Service User" -r -g %{rpcuser_uid} \ + -s /sbin/nologin -u %{rpcuser_uid} -d /var/lib/nfs rpcuser >/dev/null 2>&1 || : +else + /usr/sbin/usermod -u %{rpcuser_uid} -g %{rpcuser_uid} rpcuser >/dev/null 2>&1 || : +fi # Using the 16-bit value of -2 for the nfsnobody uid and gid %define nfsnobody_uid 65534 @@ -239,23 +295,27 @@ done # Create nfsnobody gid as long as it does not already exist cat /etc/group | cut -d':' -f 1 | grep --quiet nfsnobody 2>/dev/null if [ "$?" -eq 1 ]; then - /usr/sbin/groupadd -g %{nfsnobody_uid} nfsnobody 2>/dev/null || : + /usr/sbin/groupadd -g %{nfsnobody_uid} nfsnobody >/dev/null 2>&1 || : else - /usr/sbin/groupmod -g %{nfsnobody_uid} nfsnobody 2>/dev/null || : + /usr/sbin/groupmod -g %{nfsnobody_uid} nfsnobody >/dev/null 2>&1 || : fi # Create nfsnobody uid as long as it does not already exist. cat /etc/passwd | cut -d':' -f 1 | grep --quiet nfsnobody 2>/dev/null if [ "$?" -eq 1 ]; then /usr/sbin/useradd -l -c "Anonymous NFS User" -r -g %{nfsnobody_uid} \ - -s /sbin/nologin -u %{nfsnobody_uid} -d /var/lib/nfs nfsnobody 2>/dev/null || : + -s /sbin/nologin -u %{nfsnobody_uid} -d /var/lib/nfs nfsnobody >/dev/null 2>&1 || : else - /usr/sbin/usermod -u %{nfsnobody_uid} -g %{nfsnobody_uid} nfsnobody 2>/dev/null || : + /usr/sbin/usermod -u %{nfsnobody_uid} -g %{nfsnobody_uid} nfsnobody >/dev/null 2>&1 || : fi %post -%systemd_post nfs-client.target +if [ $1 -eq 1 ] ; then + # Initial installation + /bin/systemctl enable nfs-client.target >/dev/null 2>&1 || : + /bin/systemctl restart nfs-config >/dev/null 2>&1 || : +fi %systemd_post nfs-config %systemd_post nfs-server @@ -276,10 +336,11 @@ if [ $1 -eq 0 ]; then fi %postun -%systemd_postun_with_restart nfs-client.target -%systemd_postun_with_restart nfs-server - -/bin/systemctl --system daemon-reload >/dev/null 2>&1 || : +if [ ! -f /etc/sysconfig/nfs-utils-disable-postun ]; then + %systemd_postun_with_restart nfs-client.target + %systemd_postun_with_restart nfs-server + /bin/systemctl --system daemon-reload >/dev/null 2>&1 || : +fi %triggerun -- nfs-utils < 1:1.2.9-0.5 /bin/systemctl stop nfs-secure.service >/dev/null 2>&1 || : @@ -292,6 +353,13 @@ fi /bin/systemctl stop rpc-svcgssd >/dev/null 2>&1 || : /bin/systemctl enable nfs-client.target >/dev/null 2>&1 || : +%triggerin -- nfs-utils > 1:1.3.0-0.1 +# reset configuration files and running daemons +if [ $1 -eq 2 ] ; then + /bin/systemctl enable nfs-client.target >/dev/null 2>&1 || : + /bin/systemctl restart nfs-config >/dev/null 2>&1 || : +fi + %files %defattr(-,root,root,-) %config(noreplace) /etc/sysconfig/nfs @@ -338,6 +406,56 @@ fi /sbin/umount.nfs4 %changelog +* Tue Sep 29 2015 Steve Dickson 1.3.0-0.21 +- Stop gssd from flooding the KDC with TGT fetches (bz 1264999) +- Decouple the start/stop of rpcbind with nfs-server and rpc-statd (bz 1266993) + +* Mon Sep 14 2015 Steve Dickson 1.3.0-0.20 +- Added back MOUNT_PORT (bz 1208488) +- rpc-statd now Requires rpcbind.service (bz 1171603) + +* Thu Sep 3 2015 Steve Dickson 1.3.0-0.19 +- Removed the patch for bz1256469 (bz 1259771) + +* Thu Aug 27 2015 Steve Dickson 1.3.0-0.18 +- Stop gssd from silenty reaps cache (bz 1256469) +- Remove errant echo call from spec file (bz 1257144) +- Add more symlinks to make systemd scripts backwards compatible (bz 1159183) + +* Fri Jul 31 2015 Steve Dickson 1.3.0-0.17 +- Fixed return value being overrun in gssd (bz 1087350) + +* Thu Jul 30 2015 Steve Dickson 1.3.0-0.16 +- Updated the mountstats-update.patch to include doc changes (bz 1215808) + +* Thu Jul 23 2015 Steve Dickson 1.3.0-0.15 +- Make sure nfs-client target is enabled (bz 1245804) + +* Tue Jul 14 2015 Steve Dickson 1.3.0-0.14 +- Fixed typeo in rpc.mount man page (bz 1003558) + +* Wed Jul 1 2015 Steve Dickson 1.3.0-0.13 +- Fix infinite loop in blkmapd (bz 1237301) + +* Wed Jun 24 2015 Steve Dickson 1.3.0-0.12 +- Fixed nfs-idmap start race (bz 1164064) +- Updated nfsdcltrack v2 schema (bz 1234598) +- Added missing arguments in rpc.mountd man page (bz 1003558) +- Added nfsconfig.conf to nfs.man and mount.nfs.man (bz 1196646) +- nfs-server now Requires rpcbind.service (bz 1171603) + +* Thu Jun 11 2015 Steve Dickson 1.3.0-0.11 +- Added the rpcuser group before adding the rpcuser uid (bz 1190874) +- Added back variables to help get through firewalls (bz 1208488) +- Made the postuns conditional (bz 1200713) + +* Mon May 4 2015 Steve Dickson 1.3.0-0.10 +- Updated mountstats to latest upstream version (bz 1215808) +- Enable all auth flavors on pseudofs exports (bz 1187223) + +* Tue Apr 28 2015 Steve Dickson 1.3.0-0.9 +- Updates for block pNFS (bz 1214821) + * Fri Jan 23 2015 Steve Dickson 1.3.0-0.8 - Stop sm-notify from running in HA environments (bz 1182692) - Set the GSS_USE_PROXY variable in nfs-utils_env.sh (bz 1183821)