summaryrefslogtreecommitdiffstats
path: root/usr.sbin
diff options
context:
space:
mode:
authorrmacklem <rmacklem@FreeBSD.org>2009-05-26 15:19:04 +0000
committerrmacklem <rmacklem@FreeBSD.org>2009-05-26 15:19:04 +0000
commit2864c5d543e6e9084ecdc1a66dab4d64fceed270 (patch)
treebe1fc6c137a6a2e03956eb1e9cf11f4b2f37ded1 /usr.sbin
parent8765020747943ee9a002927002b90186ea1f13c9 (diff)
downloadFreeBSD-src-2864c5d543e6e9084ecdc1a66dab4d64fceed270.zip
FreeBSD-src-2864c5d543e6e9084ecdc1a66dab4d64fceed270.tar.gz
Add two new utilities and two new daemons to /usr/src/usr.sbin that
are specifically used by the experimental nfsv4 subsystem. nfscbd - The NFSv4 client callback daemon. nfsuserd - The NFSv4 daemon that maps between user and group name and their corresponding uid/gid numbers. nfsdumpstate - A utility that dumps out the NFSv4 Open/Lock state. nfsrevoke - Administratively revokes an NFSv4 client, releasing all NFSv4 Open/Lock state it holds on the server. Approved by: kib (mentor)
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/Makefile4
-rw-r--r--usr.sbin/nfscbd/Makefile6
-rw-r--r--usr.sbin/nfscbd/nfscbd.887
-rw-r--r--usr.sbin/nfscbd/nfscbd.c380
-rw-r--r--usr.sbin/nfsdumpstate/Makefile6
-rw-r--r--usr.sbin/nfsdumpstate/nfsdumpstate.871
-rw-r--r--usr.sbin/nfsdumpstate/nfsdumpstate.c280
-rw-r--r--usr.sbin/nfsrevoke/Makefile6
-rw-r--r--usr.sbin/nfsrevoke/nfsrevoke.864
-rw-r--r--usr.sbin/nfsrevoke/nfsrevoke.c124
-rw-r--r--usr.sbin/nfsuserd/Makefile6
-rw-r--r--usr.sbin/nfsuserd/nfsuserd.8114
-rw-r--r--usr.sbin/nfsuserd/nfsuserd.c665
13 files changed, 1813 insertions, 0 deletions
diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile
index eb13bf1..1a3d9f6 100644
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -110,7 +110,11 @@ SUBDIR= ${_ac} \
${_ndiscvt} \
${_ndp} \
newsyslog \
+ nfscbd \
nfsd \
+ nfsdumpstate \
+ nfsrevoke \
+ nfsuserd \
${_ngctl} \
${_nghook} \
nologin \
diff --git a/usr.sbin/nfscbd/Makefile b/usr.sbin/nfscbd/Makefile
new file mode 100644
index 0000000..1e11e3a
--- /dev/null
+++ b/usr.sbin/nfscbd/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfscbd
+MAN= nfscbd.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfscbd/nfscbd.8 b/usr.sbin/nfscbd/nfscbd.8
new file mode 100644
index 0000000..802dff3
--- /dev/null
+++ b/usr.sbin/nfscbd/nfscbd.8
@@ -0,0 +1,87 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 25, 2009
+.Dt NFSCBD 8
+.Os
+.Sh NAME
+.Nm nfscbd
+.Tn NFSv4
+client side callback daemon
+.Sh SYNOPSIS
+.Nm nfscbd
+.Op Fl p Ar port_number
+.Op Fl P Ar client_principal
+.Sh DESCRIPTION
+.Nm
+runs on a client using
+.Tn NFSv4
+to handle callback requests from the NFSv4 server.
+If no
+.Nm
+is running, NFSv4 mounts will still work, but the server will never issue
+Open Delegations to the client.
+.Pp
+One callback server and one master server
+are always started.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl p Ar port_number
+Specifies what port# the callback server should use.
+.It Fl P Ar client_principal
+Specifies the host based principal name to be used as the target for
+callbacks over RPCSEC_GSS. For KerberosV, it must be in the client's
+default keytab file.
+This client_principal should be the same one specified by the
+.Cm gssname
+argument being used by nfsv4 mounts.
+If you do not specify this argument, callbacks will still work over AUTH_SYS,
+which is what many extant servers use even for RPCSEC_GSS mounts, as of 2009.
+.El
+.Pp
+For example,
+.Dq Li "nfscbd -p 7654 -P root"
+starts the daemon to handle callbacks on port# 7654 and is using the host based
+principal root@<client-host>.<dns-domain> as the callback target.
+.Pp
+.Nm
+listens for service requests at the port
+defined by NFSV4_CBPORT in /usr/include/fs/nfs/nfs.h, unless
+.Fl p
+has been specified.
+For more information on what callbacks and Open Delegations do, see
+.%T "Network File System (NFS) Version 4 Protocol" ,
+RFC3530 .
+.Pp
+The
+.Nm
+utility exits 0 on success or >0 if an error occurred.
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr mount_nfs 8
+.Sh HISTORY
+First introduced with the experimental nfs client for NFSv4 support in 2009.
diff --git a/usr.sbin/nfscbd/nfscbd.c b/usr.sbin/nfscbd/nfscbd.c
new file mode 100644
index 0000000..3920360
--- /dev/null
+++ b/usr.sbin/nfscbd/nfscbd.c
@@ -0,0 +1,380 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/stat.h>
+#include <sys/ucred.h>
+#include <sys/uio.h>
+#include <sys/vnode.h>
+#include <sys/wait.h>
+
+#include <nfs/nfssvc.h>
+
+#include <rpc/rpc.h>
+
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+#include <fs/nfs/rpcv2.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/* Global defs */
+#ifdef DEBUG
+#define syslog(e, s) fprintf(stderr,(s))
+int debug = 1;
+#else
+int debug = 0;
+#endif
+
+pid_t children;
+
+void nonfs(int);
+void reapchild(int);
+void usage(void);
+void cleanup(int);
+void child_cleanup(int);
+void nfscbd_exit(int);
+void killchildren(void);
+
+/*
+ * Nfs callback server daemon.
+ *
+ * 1 - do file descriptor and signal cleanup
+ * 2 - fork the nfscbd(s)
+ * 4 - create callback server socket(s)
+ * 5 - set up server socket for rpc
+ *
+ * For connectionless protocols, just pass the socket into the kernel via.
+ * nfssvc().
+ * For connection based sockets, loop doing accepts. When you get a new
+ * socket from accept, pass the msgsock into the kernel via. nfssvc().
+ */
+int
+main(int argc, char *argv[], char **envp)
+{
+ struct group *grp;
+ struct nfscbd_args nfscbdargs;
+ struct nfsd_nfscbd_args nfscbdargs2;
+ struct passwd *pwd;
+ struct ucred *cr;
+ struct sockaddr_in inetaddr, inetpeer;
+ struct timeval ktv;
+ fd_set ready, sockbits;
+ int ch, connect_type_cnt, i, len, maxsock, msgsock, error;
+ int nfssvc_flag, on, sock, tcpsock, ret, mustfreeai = 0;
+ char *cp, **cpp, princname[128];
+ char myname[MAXHOSTNAMELEN], *myfqdnname = NULL;
+ struct addrinfo *aip, hints;
+ pid_t pid;
+ sigset_t signew;
+ short myport = NFSV4_CBPORT;
+
+ if (modfind("nfscl") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfscl") < 0 ||
+ modfind("nfscl") < 0)
+ errx(1, "nfscl is not available");
+ }
+ /*
+ * First, get our fully qualified host name, if possible.
+ */
+ if (gethostname(myname, MAXHOSTNAMELEN) >= 0) {
+ cp = strchr(myname, '.');
+ if (cp != NULL && *(cp + 1) != '\0') {
+ cp = myname;
+ } else {
+ /*
+ * No domain on myname, so try looking it up.
+ */
+ cp = NULL;
+ memset((void *)&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(myname, NULL, &hints, &aip);
+ if (error == 0) {
+ if (aip->ai_canonname != NULL &&
+ (cp = strchr(aip->ai_canonname, '.')) != NULL
+ && *(cp + 1) != '\0') {
+ cp = aip->ai_canonname;
+ mustfreeai = 1;
+ } else {
+ freeaddrinfo(aip);
+ }
+ }
+ }
+ if (cp == NULL)
+ warnx("Can't get fully qualified host name");
+ myfqdnname = cp;
+ }
+
+ princname[0] = '\0';
+#define GETOPT "p:P:"
+#define USAGE "[ -p port_num ] [ -P client_principal ]"
+ while ((ch = getopt(argc, argv, GETOPT)) != -1)
+ switch (ch) {
+ case 'p':
+ myport = atoi(optarg);
+ if (myport < 1) {
+ warnx("port# non-positive, reset to %d",
+ NFSV4_CBPORT);
+ myport = NFSV4_CBPORT;
+ }
+ break;
+ case 'P':
+ cp = optarg;
+ if (cp != NULL && strlen(cp) > 0 &&
+ strlen(cp) < sizeof (princname)) {
+ if (strchr(cp, '@') == NULL &&
+ myfqdnname != NULL)
+ snprintf(princname, sizeof (princname),
+ "%s@%s", cp, myfqdnname);
+ else
+ strlcpy(princname, cp,
+ sizeof (princname));
+ } else {
+ warnx("client princ invalid. ignored\n");
+ }
+ break;
+ default:
+ case '?':
+ usage();
+ };
+ argv += optind;
+ argc -= optind;
+
+ if (argc > 0)
+ usage();
+
+ if (mustfreeai)
+ freeaddrinfo(aip);
+ nfscbdargs2.principal = (const char *)princname;
+ if (debug == 0) {
+ daemon(0, 0);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ }
+ (void)signal(SIGSYS, nonfs);
+ (void)signal(SIGCHLD, reapchild);
+
+ openlog("nfscbd:", LOG_PID, LOG_DAEMON);
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ERR, "fork: %m");
+ nfscbd_exit(1);
+ } else if (pid > 0) {
+ children = pid;
+ } else {
+ (void)signal(SIGUSR1, child_cleanup);
+ setproctitle("server");
+ nfssvc_flag = NFSSVC_NFSCBD;
+ if (nfssvc(nfssvc_flag, &nfscbdargs2) < 0) {
+ syslog(LOG_ERR, "nfssvc: %m");
+ nfscbd_exit(1);
+ }
+ exit(0);
+ }
+ (void)signal(SIGUSR1, cleanup);
+
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ syslog(LOG_ERR, "can't create udp socket");
+ nfscbd_exit(1);
+ }
+ memset(&inetaddr, 0, sizeof inetaddr);
+ inetaddr.sin_family = AF_INET;
+ inetaddr.sin_addr.s_addr = INADDR_ANY;
+ inetaddr.sin_port = htons(myport);
+ inetaddr.sin_len = sizeof(inetaddr);
+ ret = bind(sock, (struct sockaddr *)&inetaddr, sizeof(inetaddr));
+ /* If bind() fails, this is a restart, so just skip UDP. */
+ if (ret == 0) {
+ len = sizeof(inetaddr);
+ if (getsockname(sock, (struct sockaddr *)&inetaddr, &len) < 0){
+ syslog(LOG_ERR, "can't get bound addr");
+ nfscbd_exit(1);
+ }
+ nfscbdargs.port = ntohs(inetaddr.sin_port);
+ if (nfscbdargs.port != myport) {
+ syslog(LOG_ERR, "BAD PORT#");
+ nfscbd_exit(1);
+ }
+ nfscbdargs.sock = sock;
+ nfscbdargs.name = NULL;
+ nfscbdargs.namelen = 0;
+ if (nfssvc(NFSSVC_CBADDSOCK, &nfscbdargs) < 0) {
+ syslog(LOG_ERR, "can't Add UDP socket");
+ nfscbd_exit(1);
+ }
+ }
+ (void)close(sock);
+
+ /* Now set up the master server socket waiting for tcp connections. */
+ on = 1;
+ FD_ZERO(&sockbits);
+ connect_type_cnt = 0;
+ if ((tcpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ syslog(LOG_ERR, "can't create tcp socket");
+ nfscbd_exit(1);
+ }
+ if (setsockopt(tcpsock,
+ SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR, "setsockopt SO_REUSEADDR: %m");
+ /* sin_port is already set */
+ inetaddr.sin_family = AF_INET;
+ inetaddr.sin_addr.s_addr = INADDR_ANY;
+ inetaddr.sin_port = htons(myport);
+ inetaddr.sin_len = sizeof(inetaddr);
+ if (bind(tcpsock,
+ (struct sockaddr *)&inetaddr, sizeof (inetaddr)) < 0) {
+ syslog(LOG_ERR, "can't bind tcp addr");
+ nfscbd_exit(1);
+ }
+ if (listen(tcpsock, 5) < 0) {
+ syslog(LOG_ERR, "listen failed");
+ nfscbd_exit(1);
+ }
+ FD_SET(tcpsock, &sockbits);
+ maxsock = tcpsock;
+ connect_type_cnt++;
+
+ setproctitle("master");
+
+ /*
+ * Loop forever accepting connections and passing the sockets
+ * into the kernel for the mounts.
+ */
+ for (;;) {
+ ready = sockbits;
+ if (connect_type_cnt > 1) {
+ if (select(maxsock + 1,
+ &ready, NULL, NULL, NULL) < 1) {
+ syslog(LOG_ERR, "select failed: %m");
+ nfscbd_exit(1);
+ }
+ }
+ if (FD_ISSET(tcpsock, &ready)) {
+ len = sizeof(inetpeer);
+ if ((msgsock = accept(tcpsock,
+ (struct sockaddr *)&inetpeer, &len)) < 0) {
+ syslog(LOG_ERR, "accept failed: %m");
+ nfscbd_exit(1);
+ }
+ memset(inetpeer.sin_zero, 0,
+ sizeof (inetpeer.sin_zero));
+ if (setsockopt(msgsock, SOL_SOCKET,
+ SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_ERR,
+ "setsockopt SO_KEEPALIVE: %m");
+ nfscbdargs.sock = msgsock;
+ nfscbdargs.name = (caddr_t)&inetpeer;
+ nfscbdargs.namelen = sizeof(inetpeer);
+ nfssvc(NFSSVC_CBADDSOCK, &nfscbdargs);
+ (void)close(msgsock);
+ }
+ }
+}
+
+void
+usage(void)
+{
+
+ errx(1, "usage: nfscbd %s", USAGE);
+}
+
+void
+nonfs(int signo)
+{
+ syslog(LOG_ERR, "missing system call: NFS not available");
+}
+
+void
+reapchild(int signo)
+{
+ pid_t pid;
+ int i;
+
+ while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) {
+ if (pid == children)
+ children = -1;
+ }
+}
+
+void
+killchildren(void)
+{
+ int i;
+
+ if (children > 0)
+ kill(children, SIGKILL);
+}
+
+/*
+ * Cleanup master after SIGUSR1.
+ */
+void
+cleanup(int signo)
+{
+ nfscbd_exit(0);
+}
+
+/*
+ * Cleanup child after SIGUSR1.
+ */
+void
+child_cleanup(int signo)
+{
+ exit(0);
+}
+
+void
+nfscbd_exit(int status)
+{
+ killchildren();
+ exit(status);
+}
diff --git a/usr.sbin/nfsdumpstate/Makefile b/usr.sbin/nfsdumpstate/Makefile
new file mode 100644
index 0000000..938fb07
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfsdumpstate
+MAN= nfsdumpstate.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsdumpstate/nfsdumpstate.8 b/usr.sbin/nfsdumpstate/nfsdumpstate.8
new file mode 100644
index 0000000..ca48c02
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/nfsdumpstate.8
@@ -0,0 +1,71 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 25, 2009
+.Dt NFSDUMPSTATE 8
+.Os
+.Sh NAME
+.Nm nfsdumpstate
+.Nd display
+.Tn NFSv4
+open/lock state
+.Sh SYNOPSIS
+.Nm nfsdumpstate
+.Op Fl o
+.Op Fl l Ar filename
+.Sh DESCRIPTION
+.Nm
+displays open/lock state for the
+.Tn NFSv4
+client and server in the experimental nfs subsystem.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl o
+Displays a summary of Clients for NFSv4. Each line lists a Client with
+the ClientID being the last field of the line.
+.sp
+.nf
+The following are the client flag values displayed:
+NC - Needs Confirmation
+CB - Callbacks are enabled
+GSS - Using RPCSEC_GSS
+REV - Administratively Revoked, via nfsrevoke(8)
+.fi
+.sp
+.It Fl l Ar filename
+Displays a list of all NFSv4 Opens and Locks on the file specified by
+the
+.Ar filename .
+The ClientID is the last field of each line.
+.El
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr nfsrevoke 8
+.Sh HISTORY
+The
+.Nm
+utility was introduced with the NFSv4 experimental subsystem in 2009.
diff --git a/usr.sbin/nfsdumpstate/nfsdumpstate.c b/usr.sbin/nfsdumpstate/nfsdumpstate.c
new file mode 100644
index 0000000..54841dc
--- /dev/null
+++ b/usr.sbin/nfsdumpstate/nfsdumpstate.c
@@ -0,0 +1,280 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <netinet/in.h>
+
+#include <nfs/nfssvc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DUMPSIZE 10000
+
+static void dump_lockstate(char *);
+static void dump_openstate(void);
+static void usage(void);
+static char *open_flags(uint32_t);
+static char *deleg_flags(uint32_t);
+static char *lock_flags(uint32_t);
+static char *client_flags(uint32_t);
+
+static struct nfsd_dumpclients dp[DUMPSIZE];
+static struct nfsd_dumplocks lp[DUMPSIZE];
+static char flag_string[20];
+
+int
+main(int argc, char **argv)
+{
+ int ch, openstate;
+ char *lockfile;
+
+ if (modfind("nfsd") < 0)
+ errx(1, "nfsd not loaded - self terminating");
+ openstate = 0;
+ lockfile = NULL;
+ while ((ch = getopt(argc, argv, "ol")) != -1)
+ switch (ch) {
+ case 'o':
+ openstate = 1;
+ break;
+ case 'l':
+ lockfile = optarg;
+ break;
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (openstate == 0 && lockfile == NULL)
+ openstate = 1;
+ else if (openstate != 0 && lockfile != NULL)
+ errx(1, "-o and -l cannot both be specified");
+
+ /*
+ * For -o, dump all open/lock state.
+ * For -l, dump lock state for that file.
+ */
+ if (openstate != 0)
+ dump_openstate();
+ else
+ dump_lockstate(lockfile);
+ exit(0);
+}
+
+static void
+usage(void)
+{
+
+ errx(1, "usage: nfsdumpstate [-o] [-l]");
+}
+
+/*
+ * Dump all open/lock state.
+ */
+static void
+dump_openstate(void)
+{
+ struct nfsd_dumplist dumplist;
+ int cnt, i;
+
+ dumplist.ndl_size = DUMPSIZE;
+ dumplist.ndl_list = (void *)dp;
+ if (nfssvc(NFSSVC_DUMPCLIENTS, &dumplist) < 0)
+ errx(1, "Can't perform dump clients syscall");
+
+ printf("%-13s %9.9s %9.9s %9.9s %9.9s %9.9s %9.9s %-15s %s\n",
+ "Flags", "OpenOwner", "Open", "LockOwner",
+ "Lock", "Deleg", "OldDeleg", "Clientaddr", "ClientID");
+ /*
+ * Loop through results, printing them out.
+ */
+ cnt = 0;
+ while (dp[cnt].ndcl_clid.nclid_idlen > 0 && cnt < DUMPSIZE) {
+ printf("%-13s ", client_flags(dp[cnt].ndcl_flags));
+ printf("%9d %9d %9d %9d %9d %9d ",
+ dp[cnt].ndcl_nopenowners,
+ dp[cnt].ndcl_nopens,
+ dp[cnt].ndcl_nlockowners,
+ dp[cnt].ndcl_nlocks,
+ dp[cnt].ndcl_ndelegs,
+ dp[cnt].ndcl_nolddelegs);
+ if (dp[cnt].ndcl_addrfam == AF_INET)
+ printf("%-15s ",
+ inet_ntoa(dp[cnt].ndcl_cbaddr.sin_addr));
+ for (i = 0; i < dp[cnt].ndcl_clid.nclid_idlen; i++)
+ printf("%02x", dp[cnt].ndcl_clid.nclid_id[i]);
+ printf("\n");
+ cnt++;
+ }
+}
+
+/*
+ * Dump the lock state for a file.
+ */
+static void
+dump_lockstate(char *fname)
+{
+ struct nfsd_dumplocklist dumplocklist;
+ int cnt, i;
+
+ dumplocklist.ndllck_size = DUMPSIZE;
+ dumplocklist.ndllck_list = (void *)lp;
+ dumplocklist.ndllck_fname = fname;
+ if (nfssvc(NFSSVC_DUMPLOCKS, &dumplocklist) < 0)
+ errx(1, "Can't dump locks for %s\n", fname);
+
+ printf("%-11s %-36s %-15s %s\n",
+ "Open/Lock",
+ " Stateid or Lock Range",
+ "Clientaddr",
+ "Owner and ClientID");
+ /*
+ * Loop through results, printing them out.
+ */
+ cnt = 0;
+ while (lp[cnt].ndlck_clid.nclid_idlen > 0 && cnt < DUMPSIZE) {
+ if (lp[cnt].ndlck_flags & NFSLCK_OPEN)
+ printf("%-11s %9d %08x %08x %08x ",
+ open_flags(lp[cnt].ndlck_flags),
+ lp[cnt].ndlck_stateid.seqid,
+ lp[cnt].ndlck_stateid.other[0],
+ lp[cnt].ndlck_stateid.other[1],
+ lp[cnt].ndlck_stateid.other[2]);
+ else if (lp[cnt].ndlck_flags & (NFSLCK_DELEGREAD |
+ NFSLCK_DELEGWRITE))
+ printf("%-11s %9d %08x %08x %08x ",
+ deleg_flags(lp[cnt].ndlck_flags),
+ lp[cnt].ndlck_stateid.seqid,
+ lp[cnt].ndlck_stateid.other[0],
+ lp[cnt].ndlck_stateid.other[1],
+ lp[cnt].ndlck_stateid.other[2]);
+ else
+ printf("%-11s %17lld %17lld ",
+ lock_flags(lp[cnt].ndlck_flags),
+ lp[cnt].ndlck_first,
+ lp[cnt].ndlck_end);
+ if (lp[cnt].ndlck_addrfam == AF_INET)
+ printf("%-15s ",
+ inet_ntoa(lp[cnt].ndlck_cbaddr.sin_addr));
+ else
+ printf("%-15s ", " ");
+ for (i = 0; i < lp[cnt].ndlck_owner.nclid_idlen; i++)
+ printf("%02x", lp[cnt].ndlck_owner.nclid_id[i]);
+ printf(" ");
+ for (i = 0; i < lp[cnt].ndlck_clid.nclid_idlen; i++)
+ printf("%02x", lp[cnt].ndlck_clid.nclid_id[i]);
+ printf("\n");
+ cnt++;
+ }
+}
+
+/*
+ * Parse the Open/Lock flag bits and create a string to be printed.
+ */
+static char *
+open_flags(uint32_t flags)
+{
+ int i, j;
+
+ strlcpy(flag_string, "Open ", sizeof (flag_string));
+ i = 5;
+ if (flags & NFSLCK_READACCESS)
+ flag_string[i++] = 'R';
+ if (flags & NFSLCK_WRITEACCESS)
+ flag_string[i++] = 'W';
+ flag_string[i++] = ' ';
+ flag_string[i++] = 'D';
+ flag_string[i] = 'N';
+ j = i;
+ if (flags & NFSLCK_READDENY)
+ flag_string[i++] = 'R';
+ if (flags & NFSLCK_WRITEDENY)
+ flag_string[i++] = 'W';
+ if (i == j)
+ i++;
+ flag_string[i] = '\0';
+ return (flag_string);
+}
+
+static char *
+deleg_flags(uint32_t flags)
+{
+
+ if (flags & NFSLCK_DELEGREAD)
+ strlcpy(flag_string, "Deleg R", sizeof (flag_string));
+ else
+ strlcpy(flag_string, "Deleg W", sizeof (flag_string));
+ return (flag_string);
+}
+
+static char *
+lock_flags(uint32_t flags)
+{
+
+ if (flags & NFSLCK_READ)
+ strlcpy(flag_string, "Lock R", sizeof (flag_string));
+ else
+ strlcpy(flag_string, "Lock W", sizeof (flag_string));
+ return (flag_string);
+}
+
+static char *
+client_flags(uint32_t flags)
+{
+
+ flag_string[0] = '\0';
+ if (flags & LCL_NEEDSCONFIRM)
+ strlcat(flag_string, "NC ", sizeof (flag_string));
+ if (flags & LCL_CALLBACKSON)
+ strlcat(flag_string, "CB ", sizeof (flag_string));
+ if (flags & LCL_GSS)
+ strlcat(flag_string, "GSS ", sizeof (flag_string));
+ if (flags & LCL_ADMINREVOKED)
+ strlcat(flag_string, "REV", sizeof (flag_string));
+ return (flag_string);
+}
diff --git a/usr.sbin/nfsrevoke/Makefile b/usr.sbin/nfsrevoke/Makefile
new file mode 100644
index 0000000..ab78aa3
--- /dev/null
+++ b/usr.sbin/nfsrevoke/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfsrevoke
+MAN= nfsrevoke.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsrevoke/nfsrevoke.8 b/usr.sbin/nfsrevoke/nfsrevoke.8
new file mode 100644
index 0000000..00a8915
--- /dev/null
+++ b/usr.sbin/nfsrevoke/nfsrevoke.8
@@ -0,0 +1,64 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 25, 2009
+.Dt NFSREVOKE 8
+.Os
+.Sh NAME
+.Nm nfsrevoke
+.Nd revoke
+.Tn NFS
+V4 client
+.Sh SYNOPSIS
+.Nm nfsrevoke
+.Ar ClientId
+.Sh DESCRIPTION
+.Nm
+This command is used by a system administrator to revoke a client's access
+to the NFS Version 4 server. All Open/Lock state held by the client will
+be released.
+After revocation, the client will no longer be able to use state on the server
+until it does a fresh SetClientID/SetClientIDConfirm operations sequence.
+THIS SHOULD BE DONE AS A LAST RESORT ONLY, when clients are holding state
+that must be released on the server.
+.Pp
+The
+.Ar ClientId
+argument is a hexadecimal string, which is the last field
+of the
+.Xr nfsdumpstate 8
+command's
+.Fl o
+and
+.Fl l
+options output.
+.Sh SEE ALSO
+.Xr nfsv4 4 ,
+.Xr nfsdumpstate 8
+.Sh HISTORY
+The
+.Nm
+command was introduced as a part of the experimental nfs server subsystem.
diff --git a/usr.sbin/nfsrevoke/nfsrevoke.c b/usr.sbin/nfsrevoke/nfsrevoke.c
new file mode 100644
index 0000000..f6ba813
--- /dev/null
+++ b/usr.sbin/nfsrevoke/nfsrevoke.c
@@ -0,0 +1,124 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/vnode.h>
+
+#include <netinet/in.h>
+
+#include <nfs/nfssvc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+void usage(void);
+extern int errno;
+
+int
+main(int argc, char **argv)
+{
+ char *cp;
+ u_char val;
+ int cnt, even;
+ struct nfsd_clid revoke;
+
+ if (modfind("nfsd") < 0)
+ errx(1, "nfsd not loaded - self terminating");
+ if (argc != 2)
+ usage();
+ cnt = 0;
+ cp = argv[1];
+ if (strlen(cp) % 2)
+ even = 0;
+ else
+ even = 1;
+ val = 0;
+ while (*cp) {
+ if (*cp >= '0' & *cp <= '9')
+ val += (u_char)(*cp - '0');
+ else if (*cp >= 'A' && *cp <= 'F')
+ val += ((u_char)(*cp - 'A')) + 0xa;
+ else if (*cp >= 'a' && *cp <= 'f')
+ val += ((u_char)(*cp - 'a')) + 0xa;
+ else
+ errx(1, "Non hexadecimal digit in %s", argv[1]);
+ if (even) {
+ val <<= 4;
+ even = 0;
+ } else {
+ revoke.nclid_id[cnt++] = val;
+ if (cnt > NFSV4_OPAQUELIMIT)
+ errx(1, "Clientid %s, loo long", argv[1]);
+ val = 0;
+ even = 1;
+ }
+ cp++;
+ }
+
+ /*
+ * Do the revocation system call.
+ */
+ revoke.nclid_idlen = cnt;
+#ifdef DEBUG
+ printf("Idlen=%d\n", revoke.nclid_idlen);
+ for (cnt = 0; cnt < revoke.nclid_idlen; cnt++)
+ printf("%02x", revoke.nclid_id[cnt]);
+ printf("\n");
+#else
+ if (nfssvc(NFSSVC_ADMINREVOKE, &revoke) < 0)
+ err(1, "Admin revoke failed");
+#endif
+}
+
+void
+usage(void)
+{
+
+ errx(1, "Usage: nfsrevoke <ClientID>");
+}
diff --git a/usr.sbin/nfsuserd/Makefile b/usr.sbin/nfsuserd/Makefile
new file mode 100644
index 0000000..377b5af
--- /dev/null
+++ b/usr.sbin/nfsuserd/Makefile
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+PROG= nfsuserd
+MAN= nfsuserd.8
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/nfsuserd/nfsuserd.8 b/usr.sbin/nfsuserd/nfsuserd.8
new file mode 100644
index 0000000..b98c34a
--- /dev/null
+++ b/usr.sbin/nfsuserd/nfsuserd.8
@@ -0,0 +1,114 @@
+.\" Copyright (c) 2009 Rick Macklem, University of Guelph
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd April 25, 2009
+.Dt NFSUSERD 8
+.Os
+.Sh NAME
+.Nm nfsuserd
+.Nd load user and group information into the kernel for
+.Tn NFSv4
+services
+.Sh SYNOPSIS
+.Nm nfsuserd
+.Op Fl domain Ar domain_name
+.Op Fl usertimeout Ar minutes
+.Op Fl usermax Ar max_cache_size
+.Op Fl verbose
+.Op Fl force
+.Op Ar num_servers
+.Sh DESCRIPTION
+.Nm
+loads user and group information into the kernel for NFSv4.
+It must be running for NFSv4 to function correctly, either client or server.
+.Pp
+Upon startup, it loads the machines DNS domain name, plus timeout and
+cache size limit into the kernel. It then preloads the cache with group
+and user information, up to the cache size limit and forks off N children
+(default 4), that service requests from the kernel for cache misses. The
+master server is there for the sole purpose of killing off the slaves.
+To stop the nfsuserd, send a SIGUSR1 to the master server.
+.Pp
+The following options are available:
+.Bl -tag -width Ds
+.It Fl domain Ar domain_name
+This option allows you to override the default DNS domain name, which
+is acquired by taking either the suffix on the machine's hostname or,
+if that name is not a fully qualified host name, the cannonical name as
+reported by
+.Xr getaddrinfo 3 .
+.It Fl usertimeout Ar minutes
+Overrides the default timeout for cache entries, in minutes. If the
+timeout is specified as 0, cache entries never time out. The longer the
+time out, the better the performance, but the longer it takes for replaced
+entries to be seen. If your user/group database management system almost
+never re-uses the same names or id numbers, a large timeout is recommended.
+The default is 1 minute.
+.It Fl usermax Ar max_cache_size
+Overrides the default upper bound on the cache size. The larger the cache,
+the more kernel memory is used, but the better the performance. If your
+system can afford the memory use, make this the sum of the number of
+entries in your group and password databases.
+The default is 200 entries.
+.It Fl verbose
+When set, the server logs a bunch of information to syslog.
+.It Fl force
+This flag option must be set to restart the daemon after it has gone away
+abnormally and refuses to start, because it thinks nfsuserd is already
+running.
+.It Ar num_servers
+Specifies how many servers to create (max 20).
+The default of 4 may be sufficient. You should run enough servers, so that
+.Xr ps 1
+shows almost no running time for one or two of the slaves after the system
+has been running for a long period. Running too few will have a major
+performance impact, whereas running too many will only tie up some resources,
+such as a process table entry and swap space.
+.El
+.Sh SEE ALSO
+.Xr getpwent 3 ,
+.Xr getgrent 3 ,
+.Xr nfsv4 4 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr nfsd 8 .
+.Sh HISTORY
+The
+.Nm
+utility was introduced with the NFSv4 experimental subsystem in 2009.
+.Sh BUGS
+The
+.Nm
+use
+.Xr getgrent 3
+and
+.Xr getpwent 3
+library calls to resolve requests and will hang if the servers handling
+those requests fail and the library functions don't return. See
+.Xr group 5
+and
+.Xr passwd 5
+for more information on how the databases are accessed.
diff --git a/usr.sbin/nfsuserd/nfsuserd.c b/usr.sbin/nfsuserd/nfsuserd.c
new file mode 100644
index 0000000..7928a75
--- /dev/null
+++ b/usr.sbin/nfsuserd/nfsuserd.c
@@ -0,0 +1,665 @@
+/*-
+ * Copyright (c) 2009 Rick Macklem, University of Guelph
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/errno.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/time.h>
+#include <sys/ucred.h>
+#include <sys/vnode.h>
+#include <sys/wait.h>
+
+#include <nfs/nfssvc.h>
+
+#include <rpc/rpc.h>
+
+#include <fs/nfs/rpcv2.h>
+#include <fs/nfs/nfsproto.h>
+#include <fs/nfs/nfskpiport.h>
+#include <fs/nfs/nfs.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+/*
+ * This program loads the password and group databases into the kernel
+ * for NFS V4.
+ */
+
+void cleanup_term(int);
+void usage(void);
+void nfsuserdsrv(struct svc_req *, SVCXPRT *);
+bool_t xdr_getid(XDR *, caddr_t);
+bool_t xdr_getname(XDR *, caddr_t);
+bool_t xdr_retval(XDR *, caddr_t);
+
+#define MAXNAME 1024
+#define MAXNFSUSERD 20
+#define DEFNFSUSERD 4
+#define DEFUSERMAX 200
+#define DEFUSERTIMEOUT (1 * 60)
+struct info {
+ long id;
+ long retval;
+ char name[MAXNAME + 1];
+};
+
+u_char *dnsname = "default.domain";
+u_char *defaultuser = "nobody";
+uid_t defaultuid = (uid_t)32767;
+u_char *defaultgroup = "nogroup";
+gid_t defaultgid = (gid_t)32767;
+int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0;
+int defusertimeout = DEFUSERTIMEOUT;
+pid_t slaves[MAXNFSUSERD];
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+ int i;
+ int error, len, mustfreeai = 0;
+ struct nfsd_idargs nid;
+ struct passwd *pwd;
+ struct group *grp;
+ int sock, one = 1;
+ SVCXPRT *udptransp, *tcptransp;
+ struct passwd *pw;
+ u_short portnum;
+ sigset_t signew;
+ char hostname[MAXHOSTNAMELEN + 1], *cp, **aliases;
+ struct addrinfo *aip, hints;
+
+ if (modfind("nfscommon") < 0) {
+ /* Not present in kernel, try loading it */
+ if (kldload("nfscommon") < 0 ||
+ modfind("nfscommon") < 0)
+ errx(1, "Experimental nfs subsystem is not available");
+ }
+
+ /*
+ * First, figure out what our domain name and Kerberos Realm
+ * seem to be. Command line args may override these later.
+ */
+ if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
+ if ((cp = strchr(hostname, '.')) != NULL &&
+ *(cp + 1) != '\0') {
+ dnsname = cp + 1;
+ } else {
+ memset((void *)&hints, 0, sizeof (hints));
+ hints.ai_flags = AI_CANONNAME;
+ error = getaddrinfo(hostname, NULL, &hints, &aip);
+ if (error == 0) {
+ if (aip->ai_canonname != NULL &&
+ (cp = strchr(aip->ai_canonname, '.')) != NULL
+ && *(cp + 1) != '\0') {
+ dnsname = cp + 1;
+ mustfreeai = 1;
+ } else {
+ freeaddrinfo(aip);
+ }
+ }
+ }
+ }
+ nid.nid_usermax = DEFUSERMAX;
+ nid.nid_usertimeout = defusertimeout;
+
+ argc--;
+ argv++;
+ while (argc >= 1) {
+ if (!strcmp(*argv, "-domain")) {
+ if (argc == 1)
+ usage();
+ argc--;
+ argv++;
+ strncpy(hostname, *argv, MAXHOSTNAMELEN);
+ hostname[MAXHOSTNAMELEN] = '\0';
+ dnsname = hostname;
+ } else if (!strcmp(*argv, "-verbose")) {
+ verbose = 1;
+ } else if (!strcmp(*argv, "-force")) {
+ forcestart = 1;
+ } else if (!strcmp(*argv, "-usermax")) {
+ if (argc == 1)
+ usage();
+ argc--;
+ argv++;
+ i = atoi(*argv);
+ if (i < 10 || i > 100000) {
+ fprintf(stderr,
+ "usermax out of range 10<->100000\n", i);
+ usage();
+ }
+ nid.nid_usermax = i;
+ } else if (!strcmp(*argv, "-usertimeout")) {
+ if (argc == 1)
+ usage();
+ argc--;
+ argv++;
+ i = atoi(*argv);
+ if (i < 0 || i > 100000) {
+ fprintf(stderr,
+ "usertimeout out of range 0<->100000\n",
+ i);
+ usage();
+ }
+ nid.nid_usertimeout = defusertimeout = i * 60;
+ } else if (nfsuserdcnt == -1) {
+ nfsuserdcnt = atoi(*argv);
+ if (nfsuserdcnt < 1)
+ usage();
+ if (nfsuserdcnt > MAXNFSUSERD) {
+ warnx("nfsuserd count %d; reset to %d",
+ nfsuserdcnt, DEFNFSUSERD);
+ nfsuserdcnt = DEFNFSUSERD;
+ }
+ } else {
+ usage();
+ }
+ argc--;
+ argv++;
+ }
+ if (nfsuserdcnt < 1)
+ nfsuserdcnt = DEFNFSUSERD;
+
+ /*
+ * Strip off leading and trailing '.'s in domain name and map
+ * alphabetics to lower case.
+ */
+ while (*dnsname == '.')
+ dnsname++;
+ if (*dnsname == '\0')
+ errx(1, "Domain name all '.'");
+ len = strlen(dnsname);
+ cp = dnsname + len - 1;
+ while (*cp == '.') {
+ *cp = '\0';
+ len--;
+ cp--;
+ }
+ for (i = 0; i < len; i++) {
+ if (!isascii(dnsname[i]))
+ errx(1, "Domain name has non-ascii char");
+ if (isupper(dnsname[i]))
+ dnsname[i] = tolower(dnsname[i]);
+ }
+
+ /*
+ * If the nfsuserd died off ungracefully, this is necessary to
+ * get them to start again.
+ */
+ if (forcestart && nfssvc(NFSSVC_NFSUSERDDELPORT, NULL) < 0)
+ errx(1, "Can't do nfssvc() to delete the port");
+
+ if (verbose)
+ fprintf(stderr,
+ "nfsuserd: domain=%s usermax=%d usertimeout=%d\n",
+ dnsname, nid.nid_usermax, nid.nid_usertimeout);
+
+ for (i = 0; i < nfsuserdcnt; i++)
+ slaves[i] = (pid_t)-1;
+
+ /*
+ * Set up the service port to accept requests via UDP from
+ * localhost (127.0.0.1).
+ */
+ if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+ err(1, "cannot create udp socket");
+
+ /*
+ * Not sure what this does, so I'll leave it here for now.
+ */
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+ if ((udptransp = svcudp_create(sock)) == NULL)
+ err(1, "Can't set up socket");
+
+ /*
+ * By not specifying a protocol, it is linked into the
+ * dispatch queue, but not registered with portmapper,
+ * which is just what I want.
+ */
+ if (!svc_register(udptransp, RPCPROG_NFSUSERD, RPCNFSUSERD_VERS,
+ nfsuserdsrv, 0))
+ err(1, "Can't register nfsuserd");
+
+ /*
+ * Tell the kernel what my port# is.
+ */
+ portnum = htons(udptransp->xp_port);
+#ifdef DEBUG
+ printf("portnum=0x%x\n", portnum);
+#else
+ if (nfssvc(NFSSVC_NFSUSERDPORT, (caddr_t)&portnum) < 0) {
+ if (errno == EPERM) {
+ fprintf(stderr,
+ "Can't start nfsuserd when already running");
+ fprintf(stderr,
+ " If not running, use the -force option.\n");
+ } else {
+ fprintf(stderr, "Can't do nfssvc() to add port\n");
+ }
+ exit(1);
+ }
+#endif
+
+ pwd = getpwnam(defaultuser);
+ if (pwd)
+ nid.nid_uid = pwd->pw_uid;
+ else
+ nid.nid_uid = defaultuid;
+ grp = getgrnam(defaultgroup);
+ if (grp)
+ nid.nid_gid = grp->gr_gid;
+ else
+ nid.nid_gid = defaultgid;
+ nid.nid_name = dnsname;
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_flag = NFSID_INITIALIZE;
+#ifdef DEBUG
+ printf("Initialize uid=%d gid=%d dns=%s\n", nid.nid_uid, nid.nid_gid,
+ nid.nid_name);
+#else
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error)
+ errx(1, "Can't initialize nfs user/groups");
+#endif
+
+ i = 0;
+ /*
+ * Loop around adding all groups.
+ */
+ setgrent();
+ while (i < nid.nid_usermax && (grp = getgrent())) {
+ nid.nid_gid = grp->gr_gid;
+ nid.nid_name = grp->gr_name;
+ nid.nid_namelen = strlen(grp->gr_name);
+ nid.nid_flag = NFSID_ADDGID;
+#ifdef DEBUG
+ printf("add gid=%d name=%s\n", nid.nid_gid, nid.nid_name);
+#else
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error)
+ errx(1, "Can't add group %s", grp->gr_name);
+#endif
+ i++;
+ }
+
+ /*
+ * Loop around adding all users.
+ */
+ setpwent();
+ while (i < nid.nid_usermax && (pwd = getpwent())) {
+ nid.nid_uid = pwd->pw_uid;
+ nid.nid_name = pwd->pw_name;
+ nid.nid_namelen = strlen(pwd->pw_name);
+ nid.nid_flag = NFSID_ADDUID;
+#ifdef DEBUG
+ printf("add uid=%d name=%s\n", nid.nid_uid, nid.nid_name);
+#else
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error)
+ errx(1, "Can't add user %s", pwd->pw_name);
+#endif
+ i++;
+ }
+
+ /*
+ * I should feel guilty for not calling this for all the above exit()
+ * upon error cases, but I don't.
+ */
+ if (mustfreeai)
+ freeaddrinfo(aip);
+
+#ifdef DEBUG
+ exit(0);
+#endif
+ /*
+ * Temporarily block SIGUSR1 and SIGCHLD, so slaves[] can't
+ * end up bogus.
+ */
+ sigemptyset(&signew);
+ sigaddset(&signew, SIGUSR1);
+ sigaddset(&signew, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &signew, NULL);
+
+ daemon(0, 0);
+ (void)signal(SIGHUP, SIG_IGN);
+ (void)signal(SIGINT, SIG_IGN);
+ (void)signal(SIGQUIT, SIG_IGN);
+ (void)signal(SIGTERM, SIG_IGN);
+ (void)signal(SIGUSR1, cleanup_term);
+ (void)signal(SIGCHLD, cleanup_term);
+
+ openlog("nfsuserd:", LOG_PID, LOG_DAEMON);
+
+ /*
+ * Fork off the slave daemons that do the work. All the master
+ * does is kill them off and cleanup.
+ */
+ for (i = 0; i < nfsuserdcnt; i++) {
+ slaves[i] = fork();
+ if (slaves[i] == 0) {
+ im_a_slave = 1;
+ setproctitle("slave");
+ sigemptyset(&signew);
+ sigaddset(&signew, SIGUSR1);
+ sigprocmask(SIG_UNBLOCK, &signew, NULL);
+
+ /*
+ * and away we go.
+ */
+ svc_run();
+ syslog(LOG_ERR, "nfsuserd died: %m");
+ exit(1);
+ } else if (slaves[i] < 0) {
+ syslog(LOG_ERR, "fork: %m");
+ }
+ }
+
+ /*
+ * Just wait for SIGUSR1 or a child to die and then...
+ * As the Governor of California would say, "Terminate them".
+ */
+ setproctitle("master");
+ sigemptyset(&signew);
+ while (1)
+ sigsuspend(&signew);
+}
+
+/*
+ * The nfsuserd rpc service
+ */
+void
+nfsuserdsrv(struct svc_req *rqstp, SVCXPRT *transp)
+{
+ int i;
+ char *cp;
+ struct passwd *pwd;
+ struct group *grp;
+ int error;
+ u_short sport;
+ struct info info;
+ struct nfsd_idargs nid;
+ u_int32_t saddr;
+
+ /*
+ * Only handle requests from 127.0.0.1 on a reserved port number.
+ * (Since a reserved port # at localhost implies a client with
+ * local root, there won't be a security breach. This is about
+ * the only case I can think of where a reserved port # means
+ * something.)
+ */
+ sport = ntohs(transp->xp_raddr.sin_port);
+ saddr = ntohl(transp->xp_raddr.sin_addr.s_addr);
+ if ((rqstp->rq_proc != NULLPROC && sport >= IPPORT_RESERVED) ||
+ saddr != 0x7f000001) {
+ syslog(LOG_ERR, "req from ip=0x%x port=%d\n", saddr, sport);
+ svcerr_weakauth(transp);
+ return;
+ }
+ switch (rqstp->rq_proc) {
+ case NULLPROC:
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETUID:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getid,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ pwd = getpwuid((uid_t)info.id);
+ info.retval = 0;
+ if (pwd != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_uid = pwd->pw_uid;
+ nid.nid_name = pwd->pw_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_uid = (uid_t)info.id;
+ nid.nid_name = defaultuser;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_flag = NFSID_ADDUID;
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add user %s\n", pwd->pw_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added uid=%d name=%s\n",
+ nid.nid_uid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETGID:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getid,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ grp = getgrgid((gid_t)info.id);
+ info.retval = 0;
+ if (grp != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_gid = grp->gr_gid;
+ nid.nid_name = grp->gr_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_gid = (gid_t)info.id;
+ nid.nid_name = defaultgroup;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_flag = NFSID_ADDGID;
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add group %s\n",
+ grp->gr_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added gid=%d name=%s\n",
+ nid.nid_gid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETUSER:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getname,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ pwd = getpwnam(info.name);
+ info.retval = 0;
+ if (pwd != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_uid = pwd->pw_uid;
+ nid.nid_name = pwd->pw_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_uid = defaultuid;
+ nid.nid_name = info.name;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_flag = NFSID_ADDUSERNAME;
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add user %s\n", pwd->pw_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added uid=%d name=%s\n",
+ nid.nid_uid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ case RPCNFSUSERD_GETGROUP:
+ if (!svc_getargs(transp, (xdrproc_t)xdr_getname,
+ (caddr_t)&info)) {
+ svcerr_decode(transp);
+ return;
+ }
+ grp = getgrnam(info.name);
+ info.retval = 0;
+ if (grp != NULL) {
+ nid.nid_usertimeout = defusertimeout;
+ nid.nid_gid = grp->gr_gid;
+ nid.nid_name = grp->gr_name;
+ } else {
+ nid.nid_usertimeout = 5;
+ nid.nid_gid = defaultgid;
+ nid.nid_name = info.name;
+ }
+ nid.nid_namelen = strlen(nid.nid_name);
+ nid.nid_flag = NFSID_ADDGROUPNAME;
+ error = nfssvc(NFSSVC_IDNAME, &nid);
+ if (error) {
+ info.retval = error;
+ syslog(LOG_ERR, "Can't add group %s\n",
+ grp->gr_name);
+ } else if (verbose) {
+ syslog(LOG_ERR,"Added gid=%d name=%s\n",
+ nid.nid_gid, nid.nid_name);
+ }
+ if (!svc_sendreply(transp, (xdrproc_t)xdr_retval,
+ (caddr_t)&info))
+ syslog(LOG_ERR, "Can't send reply");
+ return;
+ default:
+ svcerr_noproc(transp);
+ return;
+ };
+}
+
+/*
+ * Xdr routine to get an id number
+ */
+bool_t
+xdr_getid(XDR *xdrsp, caddr_t cp)
+{
+ struct info *ifp = (struct info *)cp;
+
+ return (xdr_long(xdrsp, &ifp->id));
+}
+
+/*
+ * Xdr routine to get a user name
+ */
+bool_t
+xdr_getname(XDR *xdrsp, caddr_t cp)
+{
+ struct info *ifp = (struct info *)cp;
+ long len;
+
+ if (!xdr_long(xdrsp, &len))
+ return (0);
+ if (len > MAXNAME)
+ return (0);
+ if (!xdr_opaque(xdrsp, ifp->name, len))
+ return (0);
+ ifp->name[len] = '\0';
+ return (1);
+}
+
+/*
+ * Xdr routine to return the value.
+ */
+bool_t
+xdr_retval(XDR *xdrsp, caddr_t cp)
+{
+ struct info *ifp = (struct info *)cp;
+ long val;
+
+ val = ifp->retval;
+ return (xdr_long(xdrsp, &val));
+}
+
+/*
+ * cleanup_term() called via SIGUSR1.
+ */
+void
+cleanup_term(int signo)
+{
+ int i, cnt;
+
+ if (im_a_slave)
+ exit(0);
+
+ /*
+ * Ok, so I'm the master.
+ * As the Governor of California might say, "Terminate them".
+ */
+ cnt = 0;
+ for (i = 0; i < nfsuserdcnt; i++) {
+ if (slaves[i] != (pid_t)-1) {
+ cnt++;
+ kill(slaves[i], SIGUSR1);
+ }
+ }
+
+ /*
+ * and wait for them to die
+ */
+ for (i = 0; i < cnt; i++)
+ wait3(NULL, 0, NULL);
+
+ /*
+ * Finally, get rid of the socket
+ */
+ if (nfssvc(NFSSVC_NFSUSERDDELPORT, NULL) < 0) {
+ syslog(LOG_ERR, "Can't do nfssvc() to delete the port\n");
+ exit(1);
+ }
+ exit(0);
+}
+
+void
+usage(void)
+{
+
+ errx(1,
+ "usage: nfsuserd [-usermax cache_size] [-usertimeout minutes] [-verbose] [-domain domain_name] [n]");
+}
OpenPOWER on IntegriCloud