diff --git a/configure.ac b/configure.ac index 50e9b32..e0f5a93 100644 --- a/configure.ac +++ b/configure.ac @@ -737,6 +737,7 @@ AC_CONFIG_FILES([ tools/rpcgen/Makefile tools/mountstats/Makefile tools/nfs-iostat/Makefile + tools/rpcctl/Makefile tools/nfsdclnts/Makefile tools/nfsconf/Makefile tools/nfsdclddb/Makefile diff --git a/support/nfs/rpcdispatch.c b/support/nfs/rpcdispatch.c index f7c27c9..7329f41 100644 --- a/support/nfs/rpcdispatch.c +++ b/support/nfs/rpcdispatch.c @@ -26,12 +26,13 @@ rpc_dispatch(struct svc_req *rqstp, SVCXPRT *transp, void *argp, void *resp) { struct rpc_dentry *dent; + int rq_vers = (int)rqstp->rq_vers; - if (((int)rqstp->rq_vers) > nvers) { + if (rq_vers < 1 || rq_vers > nvers) { svcerr_progvers(transp, 1, nvers); return; } - dtable += (rqstp->rq_vers - 1); + dtable += (rq_vers - 1); if (rqstp->rq_proc > dtable->nproc) { svcerr_noproc(transp); return; diff --git a/support/nfsidmap/idmapd.conf.5 b/support/nfsidmap/idmapd.conf.5 index f5b1816..87e39bb 100644 --- a/support/nfsidmap/idmapd.conf.5 +++ b/support/nfsidmap/idmapd.conf.5 @@ -198,8 +198,8 @@ With this group names of a central directory can be shortened for an isolated or .TP .B Group-Name-No-Prefix-Regex Case-insensitive regular expression to exclude groups from adding and removing the prefix set by -.B Group-Name-Prefix -. The regular expression must match both the remote and local group names. Multiple expressions may be concatenated with '|'. +.BR Group-Name-Prefix . +The regular expression must match both the remote and local group names. Multiple expressions may be concatenated with '|'. (Default: none) .\" .\" ------------------------------------------------------------------- diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man index 4436a38..be487a1 100644 --- a/systemd/nfs.conf.man +++ b/systemd/nfs.conf.man @@ -171,7 +171,6 @@ Recognized values: .BR lease-time , .BR udp , .BR tcp , -.BR vers2 , .BR vers3 , .BR vers4 , .BR vers4.0 , diff --git a/systemd/rpc-pipefs-generator.c b/systemd/rpc-pipefs-generator.c index c24db56..7b2bb4f 100644 --- a/systemd/rpc-pipefs-generator.c +++ b/systemd/rpc-pipefs-generator.c @@ -28,11 +28,12 @@ static int generate_mount_unit(const char *pipefs_path, const char *pipefs_unit, { char *path; FILE *f; + size_t size = (strlen(dirname) + 1 + strlen(pipefs_unit)); - path = malloc(strlen(dirname) + 1 + strlen(pipefs_unit)); + path = malloc(size); if (!path) return 1; - sprintf(path, "%s/%s", dirname, pipefs_unit); + snprintf(path, size, "%s/%s", dirname, pipefs_unit); f = fopen(path, "w"); if (!f) { diff --git a/tests/t0001-statd-basic-mon-unmon.sh b/tests/t0001-statd-basic-mon-unmon.sh index 92517a1..e1065e7 100755 --- a/tests/t0001-statd-basic-mon-unmon.sh +++ b/tests/t0001-statd-basic-mon-unmon.sh @@ -21,8 +21,9 @@ . ./test-lib.sh -# This test needs root privileges +# This test needs root privileges and /dev/log check_root +check_dev_log start_statd if [ $? -ne 0 ]; then diff --git a/tests/test-lib.sh b/tests/test-lib.sh index e47ad13..b62ac2a 100644 --- a/tests/test-lib.sh +++ b/tests/test-lib.sh @@ -37,6 +37,15 @@ check_root() { fi } +# Most tests require /dev/log. Skip the test if it doesn't exist in this +# environment. +check_dev_log() { + if ! [ -e /dev/log ]; then + echo "*** Skipping this tests as it requires /dev/log ***" + exit 77 + fi +} + # is lockd registered as a service? lockd_registered() { rpcinfo -p | grep -q nlockmgr diff --git a/tools/Makefile.am b/tools/Makefile.am index 9b4b080..c3feabb 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -12,6 +12,6 @@ if CONFIG_NFSDCLD OPTDIRS += nfsdclddb endif -SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat nfsdclnts $(OPTDIRS) +SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat rpcctl nfsdclnts $(OPTDIRS) MAINTAINERCLEANFILES = Makefile.in diff --git a/tools/rpcctl/Makefile.am b/tools/rpcctl/Makefile.am new file mode 100644 index 0000000..33fb431 --- /dev/null +++ b/tools/rpcctl/Makefile.am @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in +PYTHON_FILES = rpcctl.py + +man8_MANS = rpcctl.man + +EXTRA_DIST = $(man8_MANS) $(PYTHON_FILES) + +all-local: $(PYTHON_FILES) + +install-data-hook: + $(INSTALL) -m 755 rpcctl.py $(DESTDIR)$(sbindir)/rpcctl + +MAINTAINERCLEANFILES=Makefile.in diff --git a/tools/rpcctl/rpcctl.man b/tools/rpcctl/rpcctl.man new file mode 100644 index 0000000..b87ba0d --- /dev/null +++ b/tools/rpcctl/rpcctl.man @@ -0,0 +1,67 @@ +.\" +.\" rpcctl(8) +.\" +.TH rpcctl 8 "15 Feb 2022" +.SH NAME +rpcctl \- Displays SunRPC connection information +.SH SYNOPSIS +.nf +.BR rpcctl " [ \fB\-h \fR| \fB\-\-help \fR] { \fBclient \fR| \fBswitch \fR| \fBxprt \fR}" +.P +.BR "rpcctl client" " \fR[ \fB\-h \fR| \fB\-\-help \fR] { \fBshow \fR}" +.BR "rpcctl client show " "\fR[ \fB\-h \f| \fB\-\-help \fR] [ \fIXPRT \fR]" +.P +.BR "rpcctl switch" " \fR[ \fB\-h \fR| \fB\-\-help \fR] { \fBset \fR| \fBshow \fR}" +.BR "rpcctl switch set" " \fR[ \fB\-h \fR| \fB\-\-help \fR] \fISWITCH \fBdstaddr \fINEWADDR" +.BR "rpcctl switch show" " \fR[ \fB\-h \fR| \fB\-\-help \fR] [ \fISWITCH \fR]" +.P +.BR "rpcctl xprt" " \fR[ \fB\-h \fR| \fB\-\-help \fR] { \fBremove \fR| \fBset \fR| \fBshow \fR}" +.BR "rpcctl xprt remove" " \fR[ \fB\-h \fR| \fB\-\-help \fR] \fIXPRT" +.BR "rpcctl xprt set" " \fR[ \fB\-h \fR| \fB\-\-help \fR] \fIXPRT \fR{ \fBdstaddr \fINEWADDR \fR| \fBoffline \fR| \fBonline \fR}" +.BR "rpcctl xprt show" " \fR[ \fB\-h \fR| \fB\-\-help \fR] [ \fIXPRT \fR]" +.fi +.SH DESCRIPTION +.RB "The " rpcctl " command displays information collected in the SunRPC sysfs files about the system's SunRPC objects. +.P +.SS rpcctl client \fR- \fBCommands operating on RPC clients +.IP "\fBshow \fR[ \fICLIENT \fR] \fB(default)" +Show detailed information about the RPC clients on this system. +If \fICLIENT \fRwas provided, then only show information about a single RPC client. +.P +.SS rpcctl switch \fR- \fBCommands operating on groups of transports +.IP "\fBset \fISWITCH \fBdstaddr \fINEWADDR" +Change the destination address of all transports in the \fISWITCH \fRto \fINEWADDR\fR. +\fINEWADDR \fRcan be an IP address, DNS name, or anything else resolvable by \fBgethostbyname\fR(3). +.IP "\fBshow \fR[ \fISWITCH \fR] \fB(default)" +Show detailed information about groups of transports on this system. +If \fISWITCH \fRwas provided, then only show information about a single transport group. +.P +.SS rpcctl xprt \fR- \fBCommands operating on individual transports +.IP "\fBremove \fIXPRT" +Removes the specified \fIXPRT \fRfrom the system. +Note that "main" transports cannot be removed. +.P +.IP "\fBset \fIXPRT \fBdstaddr \fINEWADDR" +Change the destination address of the specified \fIXPRT \fR to \fINEWADDR\fR. +\fINEWADDR \fRcan be an IP address, DNS name, or anything else resolvable by \fBgethostbyname\fR(3). +.P +.IP "\fBset \fIXPRT \fBoffline" +Sets the specified \fIXPRT\fR's state to offline. +.P +.IP "\fBset \fIXPRT \fBonline" +Sets the specified \fIXPRT\fR's state to online. +.IP "\fBshow \fR[ \fIXPRT \fR] \fB(default)" +Show detailed information about this system's transports. +If \fIXPRT \fRwas provided, then only show information about a single transport. +.SH EXAMPLES +.IP "\fBrpcctl switch show switch-2" +Show details about the RPC switch named "switch-2". +.IP "\fBrpcctl xprt remove xprt-4" +Remove the xprt named "xprt-4" from the system. +.IP "\fBrpcctl xprt set xprt-3 dstaddr https://linux-nfs.org +Change the dstaddr of the xprt named "xprt-3" to point to linux-nfs.org +.SH DIRECTORY +.TP +.B /sys/kernel/sunrpc/ +.SH AUTHOR +Anna Schumaker diff --git a/tools/rpcctl/rpcctl.py b/tools/rpcctl/rpcctl.py new file mode 100755 index 0000000..b8df556 --- /dev/null +++ b/tools/rpcctl/rpcctl.py @@ -0,0 +1,255 @@ +#!/usr/bin/python3 +import argparse +import collections +import errno +import os +import pathlib +import socket +import sys + +with open("/proc/mounts", 'r') as f: + mount = [ line.split()[1] for line in f if "sysfs" in line ] + if len(mount) == 0: + print("ERROR: sysfs is not mounted") + sys.exit(1) + +sunrpc = pathlib.Path(mount[0]) / "kernel" / "sunrpc" +if not sunrpc.is_dir(): + print("ERROR: sysfs does not have sunrpc directory") + sys.exit(1) + +def read_addr_file(path): + try: + with open(path, 'r') as f: + return f.readline().strip() + except: + return "(enoent)" + +def write_addr_file(path, newaddr): + with open(path, 'w') as f: + f.write(newaddr) + return read_addr_file(path) + +def read_info_file(path): + res = collections.defaultdict(int) + try: + with open(path) as info: + lines = [ l.split("=", 1) for l in info if "=" in l ] + res.update({ key:int(val.strip()) for (key, val) in lines }) + finally: + return res + + +class Xprt: + def __init__(self, path): + self.path = path + self.name = path.stem.rsplit("-", 1)[0] + self.type = path.stem.split("-")[2] + self.info = read_info_file(path / "xprt_info") + self.dstaddr = read_addr_file(path / "dstaddr") + self.srcaddr = read_addr_file(path / "srcaddr") + self.read_state() + + def __lt__(self, rhs): + return self.name < rhs.name + + def _xprt(self): + main = ", main" if self.info.get("main_xprt") else "" + return f"{self.name}: {self.type}, {self.dstaddr}, " \ + f"port {self.info['dst_port']}, state <{self.state}>{main}" + + def _src_reqs(self): + return f" Source: {self.srcaddr}, port {self.info['src_port']}, " \ + f"Requests: {self.info['num_reqs']}" + + def _cong_slots(self): + return f" Congestion: cur {self.info['cur_cong']}, win {self.info['cong_win']}, " \ + f"Slots: min {self.info['min_num_slots']}, max {self.info['max_num_slots']}" + + def _queues(self): + return f" Queues: binding {self.info['binding_q_len']}, " \ + f"sending {self.info['sending_q_len']}, pending {self.info['pending_q_len']}, " \ + f"backlog {self.info['backlog_q_len']}, tasks {self.info['tasks_queuelen']}" + + def __str__(self): + if not self.path.exists(): + return f"{self.name}: has been removed" + return "\n".join([self._xprt(), self._src_reqs(), + self._cong_slots(), self._queues() ]) + + def read_state(self): + if self.path.exists(): + with open(self.path / "xprt_state") as f: + self.state = ','.join(f.readline().split()[1:]) + + def small_str(self): + main = " [main]" if self.info.get("main_xprt") else "" + return f"{self.name}: {self.type}, {self.dstaddr}{main}" + + def set_dstaddr(self, newaddr): + self.dstaddr = write_addr_file(self.path / "dstaddr", newaddr) + + def set_state(self, state): + with open(self.path / "xprt_state", 'w') as f: + f.write(state) + self.read_state() + + def add_command(subparser): + parser = subparser.add_parser("xprt", help="Commands for individual xprts") + parser.set_defaults(func=Xprt.show, xprt=None) + subparser = parser.add_subparsers() + + remove = subparser.add_parser("remove", help="Remove an xprt") + remove.add_argument("xprt", metavar="XPRT", nargs=1, + help="Name of the xprt to remove") + remove.set_defaults(func=Xprt.set_property, property="remove") + + show = subparser.add_parser("show", help="Show xprts") + show.add_argument("xprt", metavar="XPRT", nargs='?', + help="Name of a specific xprt to show") + show.set_defaults(func=Xprt.show) + + set = subparser.add_parser("set", help="Change an xprt property") + set.add_argument("xprt", metavar="XPRT", nargs=1, + help="Name of a specific xprt to modify") + subparser = set.add_subparsers(required=True) + online = subparser.add_parser("online", help="Set an xprt online") + online.set_defaults(func=Xprt.set_property, property="online") + offline = subparser.add_parser("offline", help="Set an xprt offline") + offline.set_defaults(func=Xprt.set_property, property="offline") + dstaddr = subparser.add_parser("dstaddr", help="Change an xprt's dstaddr") + dstaddr.add_argument("newaddr", metavar="NEWADDR", nargs=1, + help="The new address for the xprt") + dstaddr.set_defaults(func=Xprt.set_property, property="dstaddr") + + def get_by_name(name): + glob = f"**/{name}-*" if name else "**/xprt-*" + res = [ Xprt(x) for x in (sunrpc / "xprt-switches").glob(glob) ] + if name and len(res) == 0: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), + f"{sunrpc / 'xprt-switches' / glob}") + return sorted(res) + + def show(args): + for xprt in Xprt.get_by_name(args.xprt): + print(xprt) + + def set_property(args): + for xprt in Xprt.get_by_name(args.xprt[0]): + if args.property == "dstaddr": + xprt.set_dstaddr(socket.gethostbyname(args.newaddr[0])) + elif args.property == "remove": + xprt.set_state("offline") + xprt.set_state("remove") + else: + args.set_state(args.property) + print(xprt) + + +class XprtSwitch: + def __init__(self, path, sep=":"): + self.path = path + self.name = path.stem + self.info = read_info_file(path / "xprt_switch_info") + self.xprts = sorted([ Xprt(p) for p in self.path.iterdir() if p.is_dir() ]) + self.sep = sep + + def __lt__(self, rhs): + return self.name < rhs.name + + def __str__(self): + switch = f"{self.name}{self.sep} " \ + f"xprts {self.info['num_xprts']}, " \ + f"active {self.info['num_active']}, " \ + f"queue {self.info['queue_len']}" + xprts = [ f" {x.small_str()}" for x in self.xprts ] + return "\n".join([ switch ] + xprts) + + def add_command(subparser): + parser = subparser.add_parser("switch", help="Commands for xprt switches") + parser.set_defaults(func=XprtSwitch.show, switch=None) + subparser = parser.add_subparsers() + + show = subparser.add_parser("show", help="Show xprt switches") + show.add_argument("switch", metavar="SWITCH", nargs='?', + help="Name of a specific switch to show") + show.set_defaults(func=XprtSwitch.show) + + set = subparser.add_parser("set", help="Change an xprt switch property") + set.add_argument("switch", metavar="SWITCH", nargs=1, + help="Name of a specific xprt switch to modify") + subparser = set.add_subparsers(required=True) + dstaddr = subparser.add_parser("dstaddr", help="Change an xprt switch's dstaddr") + dstaddr.add_argument("newaddr", metavar="NEWADDR", nargs=1, + help="The new address for the xprt switch") + dstaddr.set_defaults(func=XprtSwitch.set_property, property="dstaddr") + + def get_by_name(name): + xprt_switches = sunrpc / "xprt-switches" + if name: + return [ XprtSwitch(xprt_switches / name) ] + return [ XprtSwitch(f) for f in sorted(xprt_switches.iterdir()) ] + + def show(args): + for switch in XprtSwitch.get_by_name(args.switch): + print(switch) + + def set_property(args): + for switch in XprtSwitch.get_by_name(args.switch[0]): + resolved = socket.gethostbyname(args.newaddr[0]) + for xprt in switch.xprts: + xprt.set_dstaddr(resolved) + print(switch) + + +class RpcClient: + def __init__(self, path): + self.path = path + self.name = path.stem + self.switch = XprtSwitch(path / (path / "switch").readlink(), sep=",") + + def __lt__(self, rhs): + return self.name < rhs.name + + def __str__(self): + return f"{self.name}: {self.switch}" + + def add_command(subparser): + parser = subparser.add_parser("client", help="Commands for rpc clients") + parser.set_defaults(func=RpcClient.show, client=None) + subparser = parser.add_subparsers() + + show = subparser.add_parser("show", help="Show rpc clients") + show.add_argument("client", metavar="CLIENT", nargs='?', + help="Name of a specific rpc client to show") + parser.set_defaults(func=RpcClient.show) + + def get_by_name(name): + rpc_clients = sunrpc / "rpc-clients" + if name: + return [ RpcClient(rpc_clients / name) ] + return [ RpcClient(f) for f in sorted(rpc_clients.iterdir()) ] + + def show(args): + for client in RpcClient.get_by_name(args.client): + print(client) + + +parser = argparse.ArgumentParser() + +def show_small_help(args): + parser.print_usage() + print("sunrpc dir:", sunrpc) +parser.set_defaults(func=show_small_help) + +subparser = parser.add_subparsers(title="commands") +RpcClient.add_command(subparser) +XprtSwitch.add_command(subparser) +Xprt.add_command(subparser) + +args = parser.parse_args() +try: + args.func(args) +except Exception as e: + print(str(e)) + sys.exit(1) diff --git a/utils/idmapd/idmapd.c b/utils/idmapd/idmapd.c index e2c160e..e79c124 100644 --- a/utils/idmapd/idmapd.c +++ b/utils/idmapd/idmapd.c @@ -474,7 +474,7 @@ main(int argc, char **argv) event_free(svrdirev); event_base_free(evbase); - return 1; + return 0; } static void diff --git a/utils/idmapd/idmapd.man b/utils/idmapd/idmapd.man index 5f34d2b..8215d25 100644 --- a/utils/idmapd/idmapd.man +++ b/utils/idmapd/idmapd.man @@ -24,13 +24,13 @@ the NFSv4 kernel client and server, to which it communicates via upcalls, by translating user and group IDs to names, and vice versa. .Pp The system derives the -.I user +.Em user part of the string by performing a password or group lookup. The lookup mechanism is configured in .Pa /etc/idmapd.conf .Pp By default, the -.I domain +.Em domain part of the string is the system's DNS domain name. It can also be specified in .Pa /etc/idmapd.conf diff --git a/utils/mount/nfsmount.conf.man b/utils/mount/nfsmount.conf.man index 73c3e11..34879c8 100644 --- a/utils/mount/nfsmount.conf.man +++ b/utils/mount/nfsmount.conf.man @@ -1,4 +1,4 @@ -."@(#)nfsmount.conf.5" +.\" @(#)nfsmount.conf.5" .TH NFSMOUNT.CONF 5 "16 December 2020" .SH NAME nfsmount.conf - Configuration file for NFS mounts diff --git a/utils/mount/stropts.c b/utils/mount/stropts.c index 3c4e218..dbdd11e 100644 --- a/utils/mount/stropts.c +++ b/utils/mount/stropts.c @@ -973,7 +973,9 @@ fall_back: if ((result = nfs_try_mount_v3v2(mi, FALSE))) return result; - errno = olderrno; + if (errno != EBUSY && errno != EACCES) + errno = olderrno; + return result; } diff --git a/utils/mountd/mountd.man b/utils/mountd/mountd.man index 77e6299..a206a3e 100644 --- a/utils/mountd/mountd.man +++ b/utils/mountd/mountd.man @@ -286,10 +286,9 @@ The values recognized in the section include .BR TCP , .BR UDP , -.BR vers2 , .BR vers3 ", and" .B vers4 -which each have same same meaning as given by +which each have the same meaning as given by .BR rpc.nfsd (8). .SH TCP_WRAPPERS SUPPORT diff --git a/utils/nfsd/nfsd.man b/utils/nfsd/nfsd.man index 716f538..634b8a6 100644 --- a/utils/nfsd/nfsd.man +++ b/utils/nfsd/nfsd.man @@ -156,8 +156,6 @@ Enable (with "on" or "yes" etc) or disable ("off", "no") UDP support. .B TCP Enable or disable TCP support. .TP -.B vers2 -.TP .B vers3 .TP .B vers4