summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorhjl <hjl>1999-10-18 23:21:12 +0000
committerhjl <hjl>1999-10-18 23:21:12 +0000
commit8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9 (patch)
tree0904ef8554ed680fe3244fa618685e1fb7ea148b /utils
downloadnfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.tar.gz
nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.tar.xz
nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.zip
Initial revision
Diffstat (limited to 'utils')
-rw-r--r--utils/Makefile10
-rw-r--r--utils/exportfs/Makefile13
-rw-r--r--utils/exportfs/exportfs.c391
-rw-r--r--utils/exportfs/exportfs.man195
-rw-r--r--utils/exportfs/exports.man306
-rw-r--r--utils/lockd/Makefile12
-rw-r--r--utils/lockd/lockd.c35
-rw-r--r--utils/mountd/Makefile12
-rw-r--r--utils/mountd/auth.c215
-rw-r--r--utils/mountd/mount_dispatch.c70
-rw-r--r--utils/mountd/mount_xdr.c79
-rw-r--r--utils/mountd/mountd.c489
-rw-r--r--utils/mountd/mountd.h54
-rw-r--r--utils/mountd/mountd.man92
-rw-r--r--utils/mountd/rmtab.c173
-rw-r--r--utils/nfsd/Makefile35
-rw-r--r--utils/nfsd/nfsd.c68
-rw-r--r--utils/nfsd/nfsd.man46
-rw-r--r--utils/nfsstat/Makefile32
-rw-r--r--utils/nfsstat/nfsstat.c328
-rw-r--r--utils/nfsstat/nfsstat.man74
-rw-r--r--utils/nhfsstone/DISCLAIMER33
-rw-r--r--utils/nhfsstone/Makefile8
-rw-r--r--utils/nhfsstone/README111
-rw-r--r--utils/nhfsstone/README.linux11
-rwxr-xr-xutils/nhfsstone/nhfsgraph23
-rwxr-xr-xutils/nhfsstone/nhfsnums22
-rwxr-xr-xutils/nhfsstone/nhfsrun59
-rw-r--r--utils/nhfsstone/nhfsstone.1381
-rw-r--r--utils/nhfsstone/nhfsstone.c1798
-rw-r--r--utils/rquotad/Makefile13
-rw-r--r--utils/rquotad/NEW3
-rw-r--r--utils/rquotad/README.okir3
-rw-r--r--utils/rquotad/hasquota.c72
-rw-r--r--utils/rquotad/mntent.h112
-rw-r--r--utils/rquotad/pathnames.h39
-rw-r--r--utils/rquotad/quotactl.c30
-rw-r--r--utils/rquotad/rquota.h64
-rw-r--r--utils/rquotad/rquota.x84
-rw-r--r--utils/rquotad/rquota_server.c246
-rw-r--r--utils/rquotad/rquota_svc.c213
-rw-r--r--utils/rquotad/rquota_xdr.c123
-rw-r--r--utils/rquotad/rquotad.man41
-rw-r--r--utils/showmount/Makefile11
-rw-r--r--utils/showmount/showmount.c287
-rw-r--r--utils/showmount/showmount.man58
-rw-r--r--utils/statd/COPYING340
-rw-r--r--utils/statd/COPYRIGHT25
-rw-r--r--utils/statd/Makefile58
-rw-r--r--utils/statd/TODO13
-rw-r--r--utils/statd/callback.c53
-rw-r--r--utils/statd/log.c108
-rw-r--r--utils/statd/log.h41
-rw-r--r--utils/statd/misc.c72
-rw-r--r--utils/statd/monitor.c287
-rw-r--r--utils/statd/notify.c75
-rw-r--r--utils/statd/notlist.c125
-rw-r--r--utils/statd/notlist.h111
-rw-r--r--utils/statd/rmtcall.c406
-rw-r--r--utils/statd/sim_sm_inter.x32
-rw-r--r--utils/statd/simu.c29
-rw-r--r--utils/statd/simulate.c225
-rw-r--r--utils/statd/sm_inter.x132
-rw-r--r--utils/statd/stat.c37
-rw-r--r--utils/statd/statd.c122
-rw-r--r--utils/statd/statd.h58
-rw-r--r--utils/statd/statd.man53
-rw-r--r--utils/statd/state.c126
-rw-r--r--utils/statd/svc_run.c128
-rw-r--r--utils/statd/system.h18
-rw-r--r--utils/statd/version.h7
71 files changed, 9255 insertions, 0 deletions
diff --git a/utils/Makefile b/utils/Makefile
new file mode 100644
index 0000000..7e58325
--- /dev/null
+++ b/utils/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for linux-nfs/support
+#
+
+SUBDIRS = exportfs mountd nfsd statd nfsstat rquotad showmount \
+ nhfsstone lockd
+
+include $(TOP)rules.mk
+
+# .EXPORT_ALL_VARIABLES:
diff --git a/utils/exportfs/Makefile b/utils/exportfs/Makefile
new file mode 100644
index 0000000..851a294
--- /dev/null
+++ b/utils/exportfs/Makefile
@@ -0,0 +1,13 @@
+#
+# dummy Makefile
+#
+
+PROGRAM = exportfs
+OBJS = exportfs.o
+LIBDEPS = $(TOP)support/lib/libexport.a $(TOP)/support/lib/libnfs.a
+LIBS = -lexport -lnfs
+MAN8 = exportfs
+MAN5 = exports
+
+include $(TOP)rules.mk
+
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
new file mode 100644
index 0000000..44761f8
--- /dev/null
+++ b/utils/exportfs/exportfs.c
@@ -0,0 +1,391 @@
+/*
+ * utils/exportfs/exportfs.c
+ *
+ * Export file systems to knfsd
+ *
+ * Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de>
+ *
+ * Extensive changes, 1999, Neil Brown <neilb@cse.unsw.edu.au>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <getopt.h>
+#include <netdb.h>
+#include <errno.h>
+#include "xmalloc.h"
+#include "misc.h"
+#include "nfslib.h"
+#include "exportfs.h"
+#include "xmalloc.h"
+#include "xlog.h"
+
+static void export_all(int verbose);
+static void unexport_all(int verbose);
+static void exportfs(char *arg, char *options, int verbose);
+static void unexportfs(char *arg, int verbose);
+static void exports_update(int verbose);
+static void dump(int verbose);
+static void error(nfs_export *exp, int err);
+static void usage(void);
+
+
+int
+main(int argc, char **argv)
+{
+ char *options = NULL;
+ int f_export = 1;
+ int f_all = 0;
+ int f_verbose = 0;
+ int f_reexport = 0;
+ int f_ignore = 0;
+ int i, c;
+
+ xlog_open("exportfs");
+
+ while ((c = getopt(argc, argv, "aio:ruv")) != EOF) {
+ switch(c) {
+ case 'a':
+ f_all = 1;
+ break;
+ case 'i':
+ f_ignore = 1;
+ break;
+ case 'o':
+ options = optarg;
+ break;
+ case 'r':
+ f_reexport = 1;
+ f_all = 1;
+ break;
+ case 'u':
+ f_export = 0;
+ break;
+ case 'v':
+ f_verbose = 1;
+ break;
+ default:
+ usage();
+ break;
+ }
+ }
+
+ if (optind != argc && f_all) {
+ fprintf(stderr,"exportfs: extra arguments are not permitted with -a or -r.\n");
+ return 1;
+ }
+ if (f_ignore && (f_all || ! f_export)) {
+ fprintf(stderr,"exportfs: -i not meaningful with -a, -r or -u.\n");
+ return 1;
+ }
+ if (f_reexport && ! f_export) {
+ fprintf(stderr, "exportfs: -r and -u are incompatible.\n");
+ return 1;
+ }
+ if (optind == argc && ! f_all) {
+ xtab_export_read();
+ dump(f_verbose);
+ return 0;
+ }
+
+ if (f_export && ! f_ignore)
+ export_read(_PATH_EXPORTS);
+ if (f_export) {
+ if (f_all)
+ export_all(f_verbose);
+ else
+ for (i = optind; i < argc ; i++)
+ exportfs(argv[i], options, f_verbose);
+ }
+ /* note: xtab_*_read does not update entries if they already exist,
+ * so this will not lose new options
+ */
+ if (!f_reexport)
+ xtab_export_read();
+ if (!f_export) {
+ if (f_all)
+ unexport_all(f_verbose);
+ else
+ for (i = optind ; i < argc ; i++)
+ unexportfs(argv[i], f_verbose);
+ }
+ rmtab_read();
+ xtab_mount_read();
+ exports_update(f_verbose);
+ xtab_export_write();
+ xtab_mount_write();
+
+ return 0;
+}
+
+/* we synchronise intention with reality.
+ * entries with m_mayexport get exported
+ * entries with m_exported but not m_mayexport get unexported
+ * looking at m_client->m_type == MCL_FQDN only
+ */
+static void
+exports_update(int verbose)
+{
+ nfs_export *exp;
+
+ for (exp = exportlist[MCL_FQDN]; exp; exp=exp->m_next) {
+ if (exp->m_mayexport && (!exp->m_exported || exp->m_changed)) {
+ if (verbose)
+ printf("%sexporting %s:%s to kernel\n",
+ exp->m_exported ?"re":"",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ if (!export_export(exp))
+ error(exp, errno);
+ }
+ if (exp->m_exported && ! exp->m_mayexport) {
+ if (verbose)
+ printf("unexporting %s:%s from kernel\n",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ if (!export_unexport(exp))
+ error(exp, errno);
+ }
+ }
+}
+
+/*
+ * export_all finds all entries and
+ * marks them xtabent and mayexport so that they get exported
+ */
+static void
+export_all(int verbose)
+{
+ nfs_export *exp;
+ int i;
+
+ for (i = 0; i < MCL_MAXTYPES; i++) {
+ for (exp = exportlist[i]; exp; exp = exp->m_next) {
+ if (verbose)
+ printf("exporting %s:%s\n",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ exp->m_xtabent = 1;
+ exp->m_mayexport = 1;
+ exp->m_changed = 1;
+ }
+ }
+}
+/*
+ * unexport_all finds all entries that are mayexport, and
+ * marks them not xtabent and not mayexport
+ */
+static void
+unexport_all(int verbose)
+{
+ nfs_export *exp;
+ int i;
+
+ for (i = 0; i < MCL_MAXTYPES; i++) {
+ for (exp = exportlist[i]; exp; exp = exp->m_next)
+ if (exp->m_mayexport) {
+ if (verbose) {
+ if (exp->m_exported) {
+ printf("unexporting %s:%s from kernel\n",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ }
+ else {
+ printf("unexporting %s:%s\n",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ }
+ }
+ if (exp->m_exported && !export_unexport(exp))
+ error(exp, errno);
+ exp->m_xtabent = 0;
+ exp->m_mayexport = 0;
+ }
+ }
+}
+
+
+static void
+exportfs(char *arg, char *options, int verbose)
+{
+ struct exportent *eep;
+ nfs_export *exp;
+ struct hostent *hp = NULL;
+ char *path;
+ char *hname = arg;
+ int htype;
+
+ if ((path = strchr(arg, ':')) != NULL)
+ *path++ = '\0';
+
+ if (!path || *path != '/') {
+ fprintf(stderr, "Invalid exporting option: %s\n", arg);
+ return;
+ }
+
+ if ((htype = client_gettype(hname)) == MCL_FQDN &&
+ (hp = gethostbyname(hname)) != NULL) {
+ hp = hostent_dup (hp);
+ exp = export_find(hp, path);
+ } else {
+ exp = export_lookup(hname, path);
+ }
+
+ if (!exp) {
+ if (!(eep = mkexportent(hname, path, options)) ||
+ !(exp = export_create(eep))) {
+ if (hp) free (hp);
+ return;
+ }
+ } else if (!updateexportent(&exp->m_export, options)) {
+ if (hp) free (hp);
+ return;
+ }
+
+ if (verbose)
+ printf("exporting %s:%s\n", exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ exp->m_xtabent = 1;
+ exp->m_mayexport = 1;
+ exp->m_changed = 1;
+ if (hp) free (hp);
+}
+
+static void
+unexportfs(char *arg, int verbose)
+{
+ nfs_export *exp;
+ struct hostent *hp = NULL;
+ char *path;
+ char *hname = arg;
+ int htype;
+
+ if ((path = strchr(arg, ':')) != NULL)
+ *path++ = '\0';
+
+ if (!path || *path != '/') {
+ fprintf(stderr, "Invalid unexporting option: %s\n",
+ arg);
+ return;
+ }
+
+ if ((htype = client_gettype(hname)) == MCL_FQDN) {
+ if ((hp = gethostbyname(hname)) != 0) {
+ hp = hostent_dup (hp);
+ hname = (char *) hp->h_name;
+ }
+ }
+
+ for (exp = exportlist[htype]; exp; exp = exp->m_next) {
+ if (path && strcmp(path, exp->m_export.e_path))
+ continue;
+ if (htype != exp->m_client->m_type
+ || (htype == MCL_FQDN
+ && !matchhostname(exp->m_export.e_hostname,
+ hname)))
+ continue;
+ if (verbose) {
+ if (exp->m_exported) {
+ printf("unexporting %s:%s from kernel\n",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ }
+ else {
+ printf("unexporting %s:%s\n",
+ exp->m_client->m_hostname,
+ exp->m_export.e_path);
+ }
+ }
+ if (exp->m_exported && !export_unexport(exp))
+ error(exp, errno);
+ exp->m_xtabent = 0;
+ exp->m_mayexport = 0;
+ }
+
+ if (hp) free (hp);
+}
+
+static char
+dumpopt(char c, char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ printf("%c", c);
+ vprintf(fmt, ap);
+ va_end(ap);
+ return ',';
+}
+
+static void
+dump(int verbose)
+{
+ nfs_export *exp;
+ struct exportent *ep;
+ int htype;
+ char *hname, c;
+
+ for (htype = 0; htype < MCL_MAXTYPES; htype++) {
+ for (exp = exportlist[htype]; exp; exp = exp->m_next) {
+ ep = &exp->m_export;
+ if (!exp->m_xtabent)
+ continue; /* neilb */
+ if (htype == MCL_ANONYMOUS)
+ hname = "<world>";
+ else
+ hname = ep->e_hostname;
+ if (strlen(ep->e_path) > 14)
+ printf("%-14s\n\t\t%s", ep->e_path, hname);
+ else
+ printf("%-14s\t%s", ep->e_path, hname);
+ if (!verbose) {
+ printf("\n");
+ continue;
+ }
+ c = '(';
+ if (ep->e_flags & NFSEXP_READONLY)
+ c = dumpopt(c, "ro");
+ else
+ c = dumpopt(c, "rw");
+ if (ep->e_flags & NFSEXP_ASYNC)
+ c = dumpopt(c, "async");
+ if (ep->e_flags & NFSEXP_GATHERED_WRITES)
+ c = dumpopt(c, "wdelay");
+ if (ep->e_flags & NFSEXP_INSECURE_PORT)
+ c = dumpopt(c, "insecure");
+ if (ep->e_flags & NFSEXP_ROOTSQUASH)
+ c = dumpopt(c, "root_squash");
+ else
+ c = dumpopt(c, "no_root_squash");
+ if (ep->e_flags & NFSEXP_ALLSQUASH)
+ c = dumpopt(c, "all_squash");
+ if (ep->e_maptype == CLE_MAP_UGIDD)
+ c = dumpopt(c, "mapping=ugidd");
+ else if (ep->e_maptype == CLE_MAP_FILE)
+ c = dumpopt(c, "mapping=file");
+ if (ep->e_anonuid != -2)
+ c = dumpopt(c, "anonuid=%d", ep->e_anonuid);
+ if (ep->e_anongid != -2)
+ c = dumpopt(c, "anongid=%d", ep->e_anongid);
+
+ printf("%c\n", (c != '(')? ')' : ' ');
+ }
+ }
+}
+
+static void
+error(nfs_export *exp, int err)
+{
+ fprintf(stderr, "%s:%s: %s\n", exp->m_client->m_hostname,
+ exp->m_export.e_path, strerror(err));
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: exportfs [-aruv] [host:/path]\n");
+ exit(1);
+}
diff --git a/utils/exportfs/exportfs.man b/utils/exportfs/exportfs.man
new file mode 100644
index 0000000..0cd5cca
--- /dev/null
+++ b/utils/exportfs/exportfs.man
@@ -0,0 +1,195 @@
+.\"
+.\" exportfs(8)
+.\"
+.\" Copyright (C) 1995 Olaf Kirch <okir@monad.swb.de>
+.\" Modifications 1999 Neil Brown <neilb@cse.unsw.edu.au>
+.TH exportfs 8 "7 Sep 1999"
+.SH NAME
+exportfs \- maintain list of NFS exported file systems
+.SH SYNOPSIS
+.BI "/usr/sbin/exportfs [-avi] [-o " "options,.." "] [" "client:/path" " ..]
+.br
+.BI "/usr/sbin/exportfs -r [-v]"
+.br
+.BI "/usr/sbin/exportfs [-av] -u [" "client:/path" " ..]
+.br
+.BI "/usr/sbin/exportfs [-v]
+.br
+.SH DESCRIPTION
+The
+.B exportfs
+command is used to maintain the current table of exported file systems for
+NFS. This list is kept in a separate file named
+.BR /var/lib/nfs/xtab
+which is read by
+.B mountd
+when a remote host requests access to mount a file tree, and parts of
+the list which are active are kept in the kernel's export table.
+.P
+Normally this
+.B xtab
+file is initialized with the list of all file systems named in
+.B /etc/exports
+by invoking
+.BR "exportfs -a" .
+.P
+However, administrators can choose to add and delete individual file systems
+without modifying
+.B /etc/exports
+using
+.BR exportfs .
+.P
+Any export requests which identify a specific host (rather than a
+subnet or netgroup etc) are entered directly into the kernel's export
+table as well as being written to
+.BR /var/lib/nfs/xtab .
+Further, any mount points listed in
+.B /var/lib/nfs/rmtab
+which match a non host-specific export request will cause an
+appropriate export entry for the host given in
+.B rmtab
+to be entered
+into the kernel's export table.
+.SH OPTIONS
+.TP
+.B -a
+Export or unexport all directories.
+.TP
+.BI "-o " options,...
+Specify a list of export options in the same manner as in
+.BR exports(5) .
+.TP
+.B -i
+Ignore the
+.B /etc/exports
+file, so that only default options and options given on the command
+line are used.
+.TP
+.B -r
+Reexport all directories. It synchronizes /var/lib/nfs/xtab
+with /etc/exports. It removes entries in /var/lib/nfs/xtab
+which are deleted from /etc/exports, and remove any entries from the
+kernel export table which are no longer valid.
+.TP
+.TP
+.B -u
+Unexport one or more directories.
+.TP
+.B -v
+Be verbose. When exporting or unexporting, show what's going on. When
+displaying the current export list, also display the list of export
+options.
+.SH DISCUSSION
+.\" -------------------- Exporting Directories --------------------
+.SS Exporting Directories
+The first synopsis shows how to invoke the command when adding new
+entries to the export table. When using
+.BR "exportfs -a" ,
+all directories in
+.B exports(5)
+are added to
+.B xtab
+and the resulting list is pushed into the kernel.
+.P
+The
+.I host:/path
+argument specifies the directory to export along with the host or hosts to
+export it to. All formats described in
+.B exports(5)
+are supported; to export a directory to the world, simply specify
+.IR :/path .
+.P
+The export options for a particular host/directory pair derive from
+several sources. There is a set of default options which can be overridden by
+entries in
+.B /etc/exports
+(unless the
+.B -i
+option is given).
+In addition, the administrator may overide any options from these sources
+using the
+.B -o
+argument which takes a comma-separated list of options in the same fashion
+as one would specify them in
+.BR exports(5) .
+Thus,
+.B exportfs
+can also be used to modify the export options of an already exported
+directory.
+.P
+Modifications of the kernel export table used by
+.B nfsd(8)
+take place immediately after parsing the command line and updating the
+.B xtab
+file.
+.P
+The default export options are
+.BR async,ro,root_squash,no_delay .
+.\" -------------------- Unexporting Directories ------------------
+.SS Unexporting Directories
+The third synopsis shows how to unexported a currently exported directory.
+When using
+.BR "exportfs -ua" ,
+all entries listed in
+.B xtab
+are removed from the kernel export tables, and the file is cleared. This
+effectively shuts down all NFS activity.
+.P
+To remove individial export entries, one can specify a
+.I host:/path
+pair. This deletes the specified entry from
+.B xtab
+and removes the corresponding kernel entry (if any).
+.P
+.\" -------------------- Dumping the Export Table -----------------
+.SS Dumping the Export Table
+Invoking
+.B exportfs
+without further options shows the current list of exported file systems.
+When giving the
+.B -v
+option, the list of flags pertaining to each export are shown in addition.
+.\" -------------------- EXAMPLES ---------------------------------
+.SH EXAMPLES
+The following adds all directories listed in
+.B /etc/exports to /var/lib/nfs/xtab
+and pushes the resulting export entries into the kernel:
+.P
+.nf
+.B "# exportfs -a
+.fi
+.P
+To export the
+.B /usr/tmp
+directory to host
+.BR djando ,
+allowing asynchronous writes, one would do this:
+.P
+.nf
+.B "# exportfs -o async django:/usr/tmp
+.fi
+.\" -------------------- DEPENDENCIES -----------------------------
+.SH DEPENDENCIES
+Exporting to IP networks, DNS and NIS domains does not enable clients
+from these groups to access NFS immediately; rather, these sorts of
+exports are hints to
+.B mountd(8)
+to grant any mount requests from these clients.
+This is usually not a big problem, because any existing mounts are preserved
+in
+.B rmtab
+across reboots.
+.P
+When unexporting a network or domain entry, any current exports to members
+of this group will be checked against the remaining valid exports and
+if they themselves are nolonger valid they will be removed.
+.P
+.\" -------------------- SEE ALSO --------------------------------
+.SH SEE ALSO
+.BR exports(5) ", " mountd(8)
+.\" -------------------- AUTHOR ----------------------------------
+.SH AUTHORS
+Olaf Kirch, <okir@monad.swb.de>
+.br
+Neil Brown, <neilb@cse.unsw.edu.au>
+
diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man
new file mode 100644
index 0000000..2863fea
--- /dev/null
+++ b/utils/exportfs/exports.man
@@ -0,0 +1,306 @@
+.TH EXPORTS 5 "11 August 1997"
+.UC 5
+.SH NAME
+exports \- NFS file systems being exported
+.SH SYNOPSIS
+.B /etc/exports
+.SH DESCRIPTION
+The file
+.I /etc/exports
+serves as the access control list for file systems which may be
+exported to NFS clients. It it used by both the NFS mount daemon,
+.IR mountd (8)
+and the NFS file server daemon
+.IR nfsd (8).
+.PP
+The file format is similar to the SunOS
+.I exports
+file, except that several additional options are permitted. Each line
+contains a mount point and a list of machine or netgroup names allowed
+to mount the file system at that point. An optional parenthesized list
+of mount parameters may follow each machine name. Blank lines are
+ignored, and a # introduces a comment to the end of the line. Entries may
+be continued across newlines using a backslash.
+.PP
+.SS Machine Name Formats
+NFS clients may be specified in a number of ways:
+.IP "single host
+This is the most common format. You may specify a host either by an
+abbreviated name recognizued be the resolver, the fully qualified domain
+name, or an IP address.
+.IP "netgroups
+NIS netgroups may be given as
+.IR @group .
+Only the host part of all
+netgroup members is extracted and added to the access list. Empty host
+parts or those containing a single dash (\-) are ignored.
+.IP "wildcards
+Machine names may contain the wildcard characters \fI*\fR and \fI?\fR.
+This can be used to make the \fIexports\fR file more compact; for instance,
+\fI*.cs.foo.edu\fR matches all hosts in the domain \fIcs.foo.edu\fR. However,
+these wildcard characters do not match the dots in a domain name, so the
+above pattern does not include hosts such as \fIa.b.cs.foo.edu\fR.
+.IP "IP networks
+You can also export directories to all hosts on an IP (sub-) network
+simultaneously. This is done by specifying an IP address and netmask pair
+as
+.IR address/netmask .
+.TP
+.B =public
+This is a special ``hostname'' that identifies the given directory name
+as the public root directory (see the section on WebNFS in
+.BR nfsd (8)
+for a discussion of WebNFS and the public root handle). When using this
+convention,
+.B =public
+must be the only entry on this line, and must have no export options
+associated with it. Note that this does
+.I not
+actually export the named directory; you still have to set the exports
+options in a separate entry.
+.PP
+The public root path can also be specified by invoking
+.I nfsd
+with the
+.B \-\-public\-root
+option. Multiple specifications of a public root will be ignored.
+.PP
+.SS General Options
+.IR mountd " and " nfsd
+understand the following export options:
+.TP
+.IR secure "\*d
+This option requires that requests originate on an internet port less
+than IPPORT_RESERVED (1024). This option is on by default. To turn it
+off, specify
+.IR insecure .
+.TP
+.IR ro
+Allow only read-only requests on this NFS volume. The default is to
+allow write requests as well, which can also be made explicit by using
+the
+.IR rw " option.
+.TP
+.I noaccess
+This makes everything below the directory inaccessible for the named
+client. This is useful when you want to export a directory hierarchy to
+a client, but exclude certain subdirectories. The client's view of a
+directory flagged with noaccess is very limited; it is allowed to read
+its attributes, and lookup `.' and `..'. These are also the only entries
+returned by a readdir.
+.TP
+.IR link_relative
+Convert absolute symbolic links (where the link contents start with a
+slash) into relative links by prepending the necessary number of ../'s
+to get from the directory containing the link to the root on the
+server. This has subtle, perhaps questionable, semantics when the file
+hierarchy is not mounted at its root.
+.TP
+.IR link_absolute
+Leave all symbolic link as they are. This is the default operation.
+.SS User ID Mapping
+.PP
+.I nfsd
+bases its access control to files on the server machine on the uid and
+gid provided in each NFS RPC request. The normal behavior a user would
+expect is that she can access her files on the server just as she would
+on a normal file system. This requires that the same uids and gids are
+used on the client and the server machine. This is not always true, nor
+is it always desirable.
+.PP
+Very often, it is not desirable that the root user on a client machine
+is also treated as root when accessing files on the NFS server. To this
+end, uid 0 is normally mapped to a different id: the so-called
+anonymous or
+.I nobody
+uid. This mode of operation (called `root squashing') is the default,
+and can be turned off with
+.IR no_root_squash .
+.PP
+By default,
+.I nfsd
+tries to obtain the anonymous uid and gid by looking up user
+.I nobody
+in the password file at startup time. If it isn't found, a uid and gid
+of -2 (i.e. 65534) is used. These values can also be overridden by
+the
+.IR anonuid " and " anongid
+options.
+.PP
+In addition to this,
+.I nfsd
+lets you specify arbitrary uids and gids that should be mapped to user
+nobody as well. Finally, you can map all user requests to the
+anonymous uid by specifying the
+.IR all_squash " option.
+.PP
+For the benefit of installations where uids differ between different
+machines,
+.I nfsd
+provides several mechanism to dynamically map server uids to client
+uids and vice versa: static mapping files, NIS-based mapping, and
+.IR ugidd -based
+mapping.
+.PP
+.IR ugidd -based
+mapping is enabled with the
+.I map_daemon
+option, and uses the UGID RPC protocol. For this to work, you have to run
+the
+.IR ugidd (8)
+mapping daemon on the client host. It is the least secure of the three methods,
+because by running
+.IR ugidd ,
+everybody can query the client host for a list of valid user names. You
+can protect yourself by restricting access to
+.I ugidd
+to valid hosts only. This can be done by entering the list of valid
+hosts into the
+.I hosts.allow
+or
+.I hosts.deny
+file. The service name is
+.IR ugidd .
+For a description of the file's syntax, please read
+.IR hosts_access (5).
+.PP
+Static mapping is enabled by using the
+.I map_static
+option, which takes a file name as an argument that describes the mapping.
+NIS-based mapping queries the client's NIS server to obtain a mapping from
+user and group names on the server host to user and group names on the
+client.
+.PP
+Here's the complete list of mapping options:
+.TP
+.IR root_squash
+Map requests from uid/gid 0 to the anonymous uid/gid. Note that this does
+not apply to any other uids that might be equally sensitive, such as user
+.IR bin .
+.TP
+.IR no_root_squash
+Turn off root squashing. This option is mainly useful for diskless clients.
+.TP
+.IR squash_uids " and " squash_gids
+This option specifies a list of uids or gids that should be subject to
+anonymous mapping. A valid list of ids looks like this:
+.IP
+.IR squash_uids=0-15,20,25-50
+.IP
+Usually, your squash lists will look a lot simpler.
+.TP
+.IR all_squash
+Map all uids and gids to the anonymous user. Useful for NFS-exported
+public FTP directories, news spool directories, etc. The opposite option
+is
+.IR no_all_squash ,
+which is the default setting.
+.TP
+.IR map_daemon
+This option turns on dynamic uid/gid mapping. Each uid in an NFS request
+will be translated to the equivalent server uid, and each uid in an
+NFS reply will be mapped the other way round. This option requires that
+.IR rpc.ugidd (8)
+runs on the client host. The default setting is
+.IR map_identity ,
+which leaves all uids untouched. The normal squash options apply regardless
+of whether dynamic mapping is requested or not.
+.TP
+.IR map_static
+This option enables static mapping. It specifies the name of the file
+that describes the uid/gid mapping, e.g.
+.IP
+.IR map_static=/etc/nfs/foobar.map
+.IP
+The file's format looks like this
+.IP
+.nf
+.ta +3i
+# Mapping for client foobar:
+# remote local
+uid 0-99 - # squash these
+uid 100-500 1000 # map 100-500 to 1000-1500
+gid 0-49 - # squash these
+gid 50-100 700 # map 50-100 to 700-750
+.fi
+.TP
+.IR map_nis
+This option enables NIS-based uid/gid mapping. For instance, when
+the server encounters the uid 123 on the server, it will obtain the
+login name associated with it, and contact the NFS client's NIS server
+to obtain the uid the client associates with the name.
+.IP
+In order to do this, the NFS server must know the client's NIS domain.
+This is specified as an argument to the
+.I map_nis
+options, e.g.
+.IP
+.I map_nis=foo.com
+.IP
+Note that it may not be sufficient to simply specify the NIS domain
+here; you may have to take additional actions before
+.I nfsd
+is actually able to contact the server. If your distribution uses
+the NYS library, you can specify one or more NIS servers for the
+client's domain in
+.IR /etc/yp.conf .
+If you are using a different NIS library, you may have to obtain a
+special
+.IR ypbind (8)
+daemon that can be configured via
+.IR yp.conf .
+.TP
+.IR anonuid " and " anongid
+These options explicitly set the uid and gid of the anonymous account.
+This option is primarily useful for PC/NFS clients, where you might want
+all requests appear to be from one user. As an example, consider the
+export entry for
+.B /home/joe
+in the example section below, which maps all requests to uid 150 (which
+is supposedly that of user joe).
+.IP
+.SH EXAMPLE
+.PP
+.nf
+.ta +3i
+# sample /etc/exports file
+/ master(rw) trusty(rw,no_root_squash)
+/projects proj*.local.domain(rw)
+/usr *.local.domain(ro) @trusted(rw)
+/home/joe pc001(rw,all_squash,anonuid=150,anongid=100)
+/pub (ro,insecure,all_squash)
+/pub/private (noaccess)
+.fi
+.PP
+The first line exports the entire filesystem to machines master and trusty.
+In addition to write access, all uid squashing is turned off for host
+trusty. The second and third entry show examples for wildcard hostnames
+and netgroups (this is the entry `@trusted'). The fourth line shows the
+entry for the PC/NFS client discussed above. Line 5 exports the
+public FTP directory to every host in the world, executing all requests
+under the nobody account. The
+.I insecure
+option in this entry also allows clients with NFS implementations that
+don't use a reserved port for NFS. The last line denies all NFS clients
+access to the private directory.
+.SH CAVEATS
+Unlike other NFS server implementations, this
+.I nfsd
+allows you to export both a directory and a subdirectory thereof to
+the same host, for instance
+.IR /usr " and " /usr/X11R6 .
+In this case, the mount options of the most specific entry apply. For
+instance, when a user on the client host accesses a file in
+.IR /usr/X11R6 ,
+the mount options given in the
+.I /usr/X11R6
+entry apply. This is also true when the latter is a wildcard or netgroup
+entry.
+.SH FILES
+/etc/exports
+.SH DIAGNOSTICS
+An error parsing the file is reported using syslogd(8) as level NOTICE from
+a DAEMON whenever nfsd(8) or mountd(8) is started up. Any unknown
+host is reported at that time, but often not all hosts are not yet known
+to named(8) at boot time, thus as hosts are found they are reported
+with the same syslogd(8) parameters.
diff --git a/utils/lockd/Makefile b/utils/lockd/Makefile
new file mode 100644
index 0000000..557eebe
--- /dev/null
+++ b/utils/lockd/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for lockd
+#
+
+PROGRAM = lockd
+PREFIX = rpc.
+OBJS = lockd.o
+DEPLIBS = $(TOP)support/lib/libfs.a
+LIBS = -lnfs
+#MAN8 = lockd
+
+include $(TOP)rules.mk
diff --git a/utils/lockd/lockd.c b/utils/lockd/lockd.c
new file mode 100644
index 0000000..05bc999
--- /dev/null
+++ b/utils/lockd/lockd.c
@@ -0,0 +1,35 @@
+/*
+ * lockd
+ *
+ * This is the user level part of lockd. This is very primitive, because
+ * all the work is now done in the kernel module.
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include "nfslib.h"
+
+static void usage(const char *);
+
+int
+main(int argc, char **argv)
+{
+ int error;
+
+ if (argc > 1)
+ usage (argv [0]);
+
+ if ((error = lockdsvc()) < 0)
+ perror("lockdsvc");
+
+ return (error != 0);
+}
+
+static void
+usage(const char *prog)
+{
+ fprintf(stderr, "usage:\n%s\n", prog);
+ exit(2);
+}
diff --git a/utils/mountd/Makefile b/utils/mountd/Makefile
new file mode 100644
index 0000000..93529a0
--- /dev/null
+++ b/utils/mountd/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for rpc.mountd
+#
+
+PROGRAM = mountd
+PREFIX = rpc.
+OBJS = mountd.o mount_dispatch.o auth.o rmtab.o
+LIBDEPS = $(TOP)support/lib/libexport.a $(TOP)/support/lib/libnfs.a
+LIBS = -lexport -lnfs
+MAN8 = mountd
+
+include $(TOP)rules.mk
diff --git a/utils/mountd/auth.c b/utils/mountd/auth.c
new file mode 100644
index 0000000..9f63120
--- /dev/null
+++ b/utils/mountd/auth.c
@@ -0,0 +1,215 @@
+/*
+ * utils/mountd/auth.c
+ *
+ * Authentication procedures for mountd.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include "config.h"
+
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include "misc.h"
+#include "nfslib.h"
+#include "exportfs.h"
+#include "mountd.h"
+
+enum auth_error
+{
+ bad_path,
+ unknown_host,
+ no_entry,
+ not_exported,
+ illegal_port,
+ faked_hostent,
+ success
+};
+
+static void auth_fixpath(char *path);
+static nfs_export* auth_authenticate_internal
+ (char *what, struct sockaddr_in *caller, char *path,
+ struct hostent **hpp, enum auth_error *error);
+static char *export_file = NULL;
+
+void
+auth_init(char *exports)
+{
+
+ export_file = exports;
+ auth_reload();
+ xtab_mount_write();
+}
+
+int
+auth_reload()
+{
+ struct stat stb;
+ static time_t last_modified = 0;
+
+ if (stat(_PATH_ETAB, &stb) < 0)
+ xlog(L_FATAL, "couldn't stat %s", _PATH_ETAB);
+ if (stb.st_mtime == last_modified)
+ return 0;
+ last_modified = stb.st_mtime;
+
+ export_freeall();
+ // export_read(export_file);
+ xtab_export_read();
+
+ return 1;
+}
+
+static nfs_export *
+auth_authenticate_internal(char *what, struct sockaddr_in *caller,
+ char *path, struct hostent **hpp,
+ enum auth_error *error)
+{
+ struct in_addr addr = caller->sin_addr;
+ nfs_export *exp;
+
+ if (path[0] != '/') {
+ *error = bad_path;
+ return NULL;
+ }
+ auth_fixpath(path);
+
+ if (!(*hpp = gethostbyaddr((const char *)&addr, sizeof(addr), AF_INET)))
+ *hpp = get_hostent((const char *)&addr, sizeof(addr),
+ AF_INET);
+ else {
+ /* must make sure the hostent is authorative. */
+ char *name = strdup((*hpp)->h_name);
+ char **sp;
+ *hpp = gethostbyname(name);
+ /* now make sure the "addr" is in the list */
+ for (sp = (*hpp)->h_addr_list ; *sp ; sp++) {
+ if (memcmp(*sp, &addr, (*hpp)->h_length)==0)
+ break;
+ }
+
+ if (!*sp) {
+ free(name);
+ /* it was a FAKE */
+ *error = faked_hostent;
+ *hpp = NULL;
+ return NULL;
+ }
+ *hpp = hostent_dup (*hpp);
+ free(name);
+ }
+
+ if (!(exp = export_find(*hpp, path))) {
+ *error = no_entry;
+ return NULL;
+ }
+ if (!exp->m_mayexport) {
+ *error = not_exported;
+ return NULL;
+ }
+
+ if (!(exp->m_export.e_flags & NFSEXP_INSECURE_PORT) &&
+ (ntohs(caller->sin_port) < IPPORT_RESERVED/2 ||
+ ntohs(caller->sin_port) >= IPPORT_RESERVED)) {
+ *error = illegal_port;
+ return NULL;
+ }
+
+ *error = success;
+
+ return exp;
+}
+
+nfs_export *
+auth_authenticate(char *what, struct sockaddr_in *caller, char *path)
+{
+ nfs_export *exp = NULL;
+ char epath[MAXPATHLEN+1];
+ char *p = NULL;
+ struct hostent *hp = NULL;
+ struct in_addr addr = caller->sin_addr;
+ enum auth_error error;
+
+ if (path [0] != '/') return exp;
+
+ strncpy(epath, path, sizeof (epath) - 1);
+ epath[sizeof (epath) - 1] = '\0';
+
+ /* Try the longest matching exported pathname. */
+ while (1) {
+ if (hp) {
+ free (hp);
+ hp = NULL;
+ }
+ exp = auth_authenticate_internal(what, caller, epath,
+ &hp, &error);
+ if (exp || (error != not_exported && error != no_entry))
+ break;
+ /* We have to treat the root, "/", specially. */
+ if (p == &epath[1]) break;
+ p = strrchr(epath, '/');
+ if (p == epath) p++;
+ *p = '\0';
+ }
+
+ switch (error) {
+ case bad_path:
+ xlog(L_WARNING, "bad path in %s request from %s: \"%s\"",
+ what, inet_ntoa(addr), path);
+ break;
+
+ case unknown_host:
+ xlog(L_WARNING, "%s request from unknown host %s for %s (%s)",
+ what, inet_ntoa(addr), path, epath);
+ break;
+
+ case no_entry:
+ xlog(L_WARNING, "refused %s request from %s for %s (%s): no export entry",
+ what, hp->h_name, path, epath);
+ break;
+
+ case not_exported:
+ xlog(L_WARNING, "refused %s request from %s for %s (%s): not exported",
+ what, hp->h_name, path, epath);
+ break;
+
+ case illegal_port:
+ xlog(L_WARNING, "refused %s request from %s for %s (%s): illegal port %d",
+ what, hp->h_name, path, epath, ntohs(caller->sin_port));
+ break;
+
+ case faked_hostent:
+ xlog(L_WARNING, "refused %s request from %s for %s (%s): faked hostent",
+ what, inet_ntoa(addr), path, epath);
+ break;
+
+ case success:
+ xlog(L_NOTICE, "authenticated %s request from %s:%d for %s (%s)",
+ what, hp->h_name, ntohs(caller->sin_port), path, epath);
+ break;
+ default:
+ xlog(L_NOTICE, "%s request from %s:%d for %s (%s) gave %d",
+ what, hp->h_name, ntohs(caller->sin_port), path, epath, error);
+ }
+
+ if (hp)
+ free (hp);
+
+ return exp;
+}
+
+static void
+auth_fixpath(char *path)
+{
+ char *sp, *cp;
+
+ for (sp = cp = path; *sp; sp++) {
+ if (*sp != '/' || sp[1] != '/')
+ *cp++ = *sp;
+ }
+ while (cp > path+1 && cp[-1] == '/')
+ cp--;
+ *cp = '\0';
+}
diff --git a/utils/mountd/mount_dispatch.c b/utils/mountd/mount_dispatch.c
new file mode 100644
index 0000000..cee1981
--- /dev/null
+++ b/utils/mountd/mount_dispatch.c
@@ -0,0 +1,70 @@
+/*
+ * mount_dispatch This file contains the function dispatch table.
+ *
+ * Copyright (C) 1995 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include "config.h"
+
+#include "mountd.h"
+#include "rpcmisc.h"
+
+/*
+ * Procedures for MNTv1
+ */
+static struct rpc_dentry mnt_1_dtable[] = {
+ dtable_ent(mount_null,1,void,void), /* NULL */
+ dtable_ent(mount_mnt,1,dirpath,fhstatus), /* MNT */
+ dtable_ent(mount_dump,1,void,mountlist), /* DUMP */
+ dtable_ent(mount_umnt,1,dirpath,void), /* UMNT */
+ dtable_ent(mount_umntall,1,void,void), /* UMNTALL */
+ dtable_ent(mount_export,1,void,exports), /* EXPORT */
+ dtable_ent(mount_exportall,1,void,exports), /* EXPORTALL */
+};
+
+/*
+ * Procedures for MNTv2
+ */
+static struct rpc_dentry mnt_2_dtable[] = {
+ dtable_ent(mount_null,1,void,void), /* NULL */
+ dtable_ent(mount_mnt,1,dirpath,fhstatus), /* MNT */
+ dtable_ent(mount_dump,1,void,mountlist), /* DUMP */
+ dtable_ent(mount_umnt,1,dirpath,void), /* UMNT */
+ dtable_ent(mount_umntall,1,void,void), /* UMNTALL */
+ dtable_ent(mount_export,1,void,exports), /* EXPORT */
+ dtable_ent(mount_exportall,1,void,exports), /* EXPORTALL */
+ dtable_ent(mount_pathconf,2,dirpath,ppathcnf), /* PATHCONF */
+};
+
+/*
+ * Procedures for MNTv3
+ */
+static struct rpc_dentry mnt_3_dtable[] = {
+ dtable_ent(mount_null,1,void,void), /* NULL */
+ dtable_ent(mount_mnt,3,dirpath,mountres3), /* MNT */
+ dtable_ent(mount_dump,1,void,mountlist), /* DUMP */
+ dtable_ent(mount_umnt,1,dirpath,void), /* UMNT */
+ dtable_ent(mount_umntall,1,void,void), /* UMNTALL */
+ dtable_ent(mount_export,1,void,exports), /* EXPORT */
+};
+
+#define number_of(x) (sizeof(x)/sizeof(x[0]))
+
+static struct rpc_dtable dtable[] = {
+ { mnt_1_dtable, number_of(mnt_1_dtable) },
+ { mnt_2_dtable, number_of(mnt_2_dtable) },
+ { mnt_3_dtable, number_of(mnt_3_dtable) },
+};
+
+/*
+ * The main dispatch routine.
+ */
+void
+mount_dispatch(struct svc_req *rqstp, SVCXPRT *transp)
+{
+ union mountd_arguments argument;
+ union mountd_results result;
+
+ rpc_dispatch(rqstp, transp, dtable, number_of(dtable),
+ &argument, &result);
+}
diff --git a/utils/mountd/mount_xdr.c b/utils/mountd/mount_xdr.c
new file mode 100644
index 0000000..87adfa6
--- /dev/null
+++ b/utils/mountd/mount_xdr.c
@@ -0,0 +1,79 @@
+/*
+ * mount_xdr XDR procedures for mountd.
+ *
+ * Originally generated by rpcgen; edited to get rid of warnings.
+ */
+
+#include "config.h"
+
+#include "mount.h"
+
+inline bool_t
+xdr_fhandle(XDR *xdrs, fhandle objp)
+{
+ return xdr_opaque(xdrs, objp, FHSIZE);
+}
+
+bool_t
+xdr_fhstatus(XDR *xdrs, fhstatus *objp)
+{
+ return xdr_u_int(xdrs, &objp->fhs_status) &&
+ (objp->fhs_status != 0 ||
+ xdr_fhandle(xdrs, objp->fhstatus_u.fhs_fhandle));
+}
+
+bool_t
+xdr_dirpath(XDR *xdrs, dirpath *objp)
+{
+ return xdr_string(xdrs, objp, MNTPATHLEN);
+}
+
+inline bool_t
+xdr_name(XDR *xdrs, name *objp)
+{
+ return xdr_string(xdrs, objp, MNTPATHLEN);
+}
+
+bool_t
+xdr_mountlist(XDR *xdrs, mountlist *objp)
+{
+ return xdr_pointer(xdrs, (char **)objp, sizeof(struct mountbody),
+ (xdrproc_t)xdr_mountbody);
+}
+
+bool_t
+xdr_mountbody(XDR *xdrs, mountbody *objp)
+{
+ return xdr_name(xdrs, &objp->ml_hostname) &&
+ xdr_dirpath(xdrs, &objp->ml_directory) &&
+ xdr_mountlist(xdrs, &objp->ml_next);
+}
+
+bool_t
+xdr_groups(XDR *xdrs, groups *objp)
+{
+ return xdr_pointer(xdrs, (char **)objp, sizeof(struct groupnode),
+ (xdrproc_t)xdr_groupnode);
+}
+
+bool_t
+xdr_groupnode(XDR *xdrs, groupnode *objp)
+{
+ return xdr_name(xdrs, &objp->gr_name) &&
+ xdr_groups(xdrs, &objp->gr_next);
+}
+
+bool_t
+xdr_exports(XDR *xdrs, exports *objp)
+{
+ return xdr_pointer(xdrs, (char **)objp, sizeof(struct exportnode),
+ (xdrproc_t)xdr_exportnode);
+}
+
+bool_t
+xdr_exportnode(XDR *xdrs, exportnode *objp)
+{
+ return xdr_dirpath(xdrs, &objp->ex_dir) &&
+ xdr_groups(xdrs, &objp->ex_groups) &&
+ xdr_exports(xdrs, &objp->ex_next);
+}
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
new file mode 100644
index 0000000..9f467d1
--- /dev/null
+++ b/utils/mountd/mountd.c
@@ -0,0 +1,489 @@
+/*
+ * utils/mountd/mountd.c
+ *
+ * Authenticate mount requests and retrieve file handle.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include "config.h"
+
+#include <signal.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "xmalloc.h"
+#include "misc.h"
+#include "mountd.h"
+#include "rpcmisc.h"
+
+static void usage(const char *, int exitcode);
+static exports get_exportlist(void);
+static struct knfs_fh * get_rootfh(struct svc_req *, dirpath *, int *);
+
+static struct option longopts[] =
+{
+ { "foreground", 0, 0, 'F' },
+ { "debug", 1, 0, 'd' },
+ { "help", 0, 0, 'h' },
+ { "exports-file", 1, 0, 'f' },
+ { "nfs-version", 1, 0, 'V' },
+ { "no-nfs-version", 1, 0, 'N' },
+ { "version", 0, 0, 'v' },
+ { "port", 1, 0, 'p' },
+ { NULL, 0, 0, 0 }
+};
+
+static int nfs_version = -1;
+
+/*
+ * Signal handler.
+ */
+static void
+killer (int sig)
+{
+ if (nfs_version & 0x1)
+ pmap_unset (MOUNTPROG, MOUNTVERS);
+ if (nfs_version & (0x1 << 1))
+ pmap_unset (MOUNTPROG, MOUNTVERS_POSIX);
+ if (nfs_version & (0x1 << 2))
+ pmap_unset (MOUNTPROG, MOUNTVERS_NFSV3);
+ xlog (L_FATAL, "Caught signal %d, un-registering and exiting.", sig);
+}
+
+bool_t
+mount_null_1_svc(struct svc_req *rqstp, void *argp, void *resp)
+{
+ return 1;
+}
+
+bool_t
+mount_mnt_1_svc(struct svc_req *rqstp, dirpath *path, fhstatus *res)
+{
+ struct knfs_fh *fh;
+
+ xlog(D_CALL, "MNT1(%s) called", *path);
+ if ((fh = get_rootfh(rqstp, path, &res->fhs_status)) != NULL)
+ memcpy(&res->fhstatus_u.fhs_fhandle, fh, 32);
+ return 1;
+}
+
+bool_t
+mount_dump_1_svc(struct svc_req *rqstp, void *argp, mountlist *res)
+{
+ xlog(L_NOTICE, "dump request from %s",
+ inet_ntoa(svc_getcaller(rqstp->rq_xprt)->sin_addr));
+
+ *res = mountlist_list();
+ return 1;
+}
+
+bool_t
+mount_umnt_1_svc(struct svc_req *rqstp, dirpath *argp, void *resp)
+{
+ struct sockaddr_in *sin = svc_getcaller(rqstp->rq_xprt);
+ nfs_export *exp;
+ char *p = *argp;
+ char rpath[MAXPATHLEN+1];
+
+ if (*p == '\0')
+ p = "/";
+
+ if (realpath(p, rpath) != NULL) {
+ rpath[sizeof (rpath) - 1] = '\0';
+ p = rpath;
+ }
+
+ if (!(exp = auth_authenticate("unmount", sin, p))) {
+ return 1;
+ }
+ mountlist_del(exp, p);
+ export_reset (exp);
+ return 1;
+}
+
+bool_t
+mount_umntall_1_svc(struct svc_req *rqstp, void *argp, void *resp)
+{
+ /* Reload /etc/xtab if necessary */
+ auth_reload();
+
+ mountlist_del_all(svc_getcaller(rqstp->rq_xprt));
+ return 1;
+}
+
+bool_t
+mount_export_1_svc(struct svc_req *rqstp, void *argp, exports *resp)
+{
+ xlog(L_NOTICE, "export request from %s",
+ inet_ntoa(svc_getcaller(rqstp->rq_xprt)->sin_addr));
+ *resp = get_exportlist();
+ return 1;
+}
+
+bool_t
+mount_exportall_1_svc(struct svc_req *rqstp, void *argp, exports *resp)
+{
+ xlog(L_NOTICE, "exportall request from %s",
+ inet_ntoa(svc_getcaller(rqstp->rq_xprt)->sin_addr));
+ *resp = get_exportlist();
+ return 1;
+}
+
+/*
+ * MNTv2 pathconf procedure
+ *
+ * The protocol doesn't include a status field, so Sun apparently considers
+ * it good practice to let anyone snoop on your system, even if it's
+ * pretty harmless data such as pathconf. We don't.
+ *
+ * Besides, many of the pathconf values don't make much sense on NFS volumes.
+ * FIFOs and tty device files represent devices on the *client*, so there's
+ * no point in getting the server's buffer sizes etc.
+ */
+bool_t
+mount_pathconf_2_svc(struct svc_req *rqstp, dirpath *path, ppathcnf *res)
+{
+ struct sockaddr_in *sin = svc_getcaller(rqstp->rq_xprt);
+ struct stat stb;
+ nfs_export *exp;
+ char rpath[MAXPATHLEN+1];
+ char *p = *path;
+
+ memset(res, 0, sizeof(*res));
+
+ if (*p == '\0')
+ p = "/";
+
+ /* Reload /etc/xtab if necessary */
+ auth_reload();
+
+ /* Resolve symlinks */
+ if (realpath(p, rpath) != NULL) {
+ rpath[sizeof (rpath) - 1] = '\0';
+ p = rpath;
+ }
+
+ /* Now authenticate the intruder... */
+ if (!(exp = auth_authenticate("mount", sin, p))) {
+ return 1;
+ } else if (stat(p, &stb) < 0) {
+ xlog(L_WARNING, "can't stat exported dir %s: %s",
+ p, strerror(errno));
+ export_reset (exp);
+ return 1;
+ }
+
+ res->pc_link_max = pathconf(p, _PC_LINK_MAX);
+ res->pc_max_canon = pathconf(p, _PC_MAX_CANON);
+ res->pc_max_input = pathconf(p, _PC_MAX_INPUT);
+ res->pc_name_max = pathconf(p, _PC_NAME_MAX);
+ res->pc_path_max = pathconf(p, _PC_PATH_MAX);
+ res->pc_pipe_buf = pathconf(p, _PC_PIPE_BUF);
+ res->pc_vdisable = pathconf(p, _PC_VDISABLE);
+
+ /* Can't figure out what to do with pc_mask */
+ res->pc_mask[0] = 0;
+ res->pc_mask[1] = 0;
+
+ export_reset (exp);
+
+ return 1;
+}
+
+/*
+ * NFSv3 MOUNT procedure
+ */
+bool_t
+mount_mnt_3_svc(struct svc_req *rqstp, dirpath *path, mountres3 *res)
+{
+ static int flavors[] = { AUTH_NULL, AUTH_UNIX };
+ struct knfs_fh *fh;
+
+ xlog(D_CALL, "MNT3(%s) called", *path);
+ if ((fh = get_rootfh(rqstp, path, (int *) &res->fhs_status)) != NULL) {
+ struct mountres3_ok *ok = &res->mountres3_u.mountinfo;
+
+ ok->fhandle.fhandle3_len = 32;
+ ok->fhandle.fhandle3_val = (char *) fh;
+ ok->auth_flavors.auth_flavors_len = 2;
+ ok->auth_flavors.auth_flavors_val = flavors;
+ }
+ return 1;
+}
+
+static struct knfs_fh *
+get_rootfh(struct svc_req *rqstp, dirpath *path, int *error)
+{
+ struct sockaddr_in *sin = svc_getcaller(rqstp->rq_xprt);
+ struct stat stb;
+ nfs_export *exp;
+ char rpath[MAXPATHLEN+1];
+ char *p = *path;
+
+ if (*p == '\0')
+ p = "/";
+
+ /* Reload /var/lib/nfs/etab if necessary */
+ auth_reload();
+
+ /* Resolve symlinks */
+ if (realpath(p, rpath) != NULL) {
+ rpath[sizeof (rpath) - 1] = '\0';
+ p = rpath;
+ }
+
+ /* Now authenticate the intruder... */
+ if (!(exp = auth_authenticate("mount", sin, p))) {
+ *error = NFSERR_ACCES;
+ } else if (stat(p, &stb) < 0) {
+ xlog(L_WARNING, "can't stat exported dir %s: %s",
+ p, strerror(errno));
+ if (errno == ENOENT)
+ *error = NFSERR_NOENT;
+ else
+ *error = NFSERR_ACCES;
+ } else if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
+ xlog(L_WARNING, "%s is not a directory or regular file", p);
+ *error = NFSERR_NOTDIR;
+ } else {
+ struct knfs_fh *fh;
+
+ if (!exp->m_exported)
+ export_export(exp);
+ if (!exp->m_xtabent)
+ xtab_append(exp);
+
+ /* We first try the new nfs syscall. */
+ fh = getfh ((struct sockaddr *) sin, p);
+ if (fh == NULL && errno == EINVAL)
+ /* Let's try the old one. */
+ fh = getfh_old ((struct sockaddr *) sin,
+ stb.st_dev, stb.st_ino);
+ if (fh != NULL) {
+ mountlist_add(exp, p);
+ *error = NFS_OK;
+ export_reset (exp);
+ return fh;
+ }
+ xlog(L_WARNING, "getfh failed: %s", strerror(errno));
+ *error = NFSERR_ACCES;
+ }
+ export_reset (exp);
+ return NULL;
+}
+
+static exports
+get_exportlist(void)
+{
+ static exports elist = NULL;
+ struct exportnode *e, *ne;
+ struct groupnode *g, *ng, *c, **cp;
+ nfs_export *exp;
+ int i;
+
+ if (!auth_reload() && elist)
+ return elist;
+
+ for (e = elist; e != NULL; e = ne) {
+ ne = e->ex_next;
+ for (g = e->ex_groups; g != NULL; g = ng) {
+ ng = g->gr_next;
+ xfree(g->gr_name);
+ xfree(g);
+ }
+ xfree(e->ex_dir);
+ xfree(e);
+ }
+ elist = NULL;
+
+ for (i = 0; i < MCL_MAXTYPES; i++) {
+ for (exp = exportlist[i]; exp; exp = exp->m_next) {
+ for (e = elist; e != NULL; e = e->ex_next) {
+ if (!strcmp(exp->m_export.m_path, e->ex_dir))
+ break;
+ }
+ if (!e) {
+ e = (struct exportnode *) xmalloc(sizeof(*e));
+ e->ex_next = elist;
+ e->ex_groups = NULL;
+ e->ex_dir = strdup(exp->m_export.m_path);
+ elist = e;
+ }
+
+ /* We need to check if we should remove
+ previous ones. */
+ if (i == MCL_ANONYMOUS && e->ex_groups) {
+ for (g = e->ex_groups; g; g = ng) {
+ ng = g->gr_next;
+ xfree(g->gr_name);
+ xfree(g);
+ }
+ e->ex_groups = NULL;
+ continue;
+ }
+
+ if (i != MCL_FQDN && e->ex_groups) {
+ struct hostent *hp;
+
+ cp = &e->ex_groups;
+ while ((c = *cp) != NULL) {
+ if (client_gettype (c->gr_name) == MCL_FQDN
+ && (hp = gethostbyname(c->gr_name))) {
+ hp = hostent_dup (hp);
+ if (client_check(exp->m_client, hp)) {
+ *cp = c->gr_next;
+ xfree(c->gr_name);
+ xfree(c);
+ xfree (hp);
+ if ((c = *cp) == NULL)
+ break;
+ }
+ else
+ xfree (hp);
+ }
+ cp = &(c->gr_next);
+ }
+ }
+
+ if (exp->m_export.e_hostname [0] != '\0') {
+ for (g = e->ex_groups; g; g = g->gr_next)
+ if (strcmp (exp->m_export.e_hostname,
+ g->gr_name) == 0)
+ break;
+ if (g)
+ continue;
+ g = (struct groupnode *) xmalloc(sizeof(*g));
+ g->gr_name = xstrdup(exp->m_export.e_hostname);
+ g->gr_next = e->ex_groups;
+ e->ex_groups = g;
+ }
+ }
+ }
+
+ return elist;
+}
+
+int
+main(int argc, char **argv)
+{
+ char *export_file = _PATH_EXPORTS;
+ int foreground = 0;
+ int port = 0;
+ int c;
+ struct sigaction sa;
+
+ /* Parse the command line options and arguments. */
+ opterr = 0;
+ while ((c = getopt_long(argc, argv, "Fd:f:p:P:hN:V:v", longopts, NULL)) != EOF)
+ switch (c) {
+ case 'F':
+ foreground = 1;
+ break;
+ case 'd':
+ xlog_sconfig(optarg, 1);
+ break;
+ case 'f':
+ export_file = optarg;
+ break;
+ case 'h':
+ usage(argv [0], 0);
+ break;
+ case 'P': /* XXX for nfs-server compatibility */
+ case 'p':
+ port = atoi(optarg);
+ if (port <= 0 || port > 65535) {
+ fprintf(stderr, "%s: bad port number: %s\n",
+ argv [0], optarg);
+ usage(argv [0], 1);
+ }
+ break;
+ case 'N':
+ nfs_version &= ~(1 << (atoi (optarg) - 1));
+ break;
+ case 'V':
+ nfs_version |= 1 << (atoi (optarg) - 1);
+ break;
+ case 'v':
+ printf("kmountd %s\n", VERSION);
+ exit(0);
+ case 0:
+ break;
+ case '?':
+ default:
+ usage(argv [0], 1);
+ }
+
+ /* No more arguments allowed. */
+ if (optind != argc || !(nfs_version & 0x7))
+ usage(argv [0], 1);
+
+ /* Initialize logging. */
+ xlog_open("mountd");
+
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ if (nfs_version & 0x1)
+ rpc_init("mountd", MOUNTPROG, MOUNTVERS,
+ mount_dispatch, port, 0);
+ if (nfs_version & (0x1 << 1))
+ rpc_init("mountd", MOUNTPROG, MOUNTVERS_POSIX,
+ mount_dispatch, port, 0);
+ if (nfs_version & (0x1 << 2))
+ rpc_init("mountd", MOUNTPROG, MOUNTVERS_NFSV3,
+ mount_dispatch, port, 0);
+
+ sa.sa_handler = killer;
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ auth_init(export_file);
+
+ if (!foreground) {
+ /* We first fork off a child. */
+ if ((c = fork()) > 0)
+ exit(0);
+ if (c < 0) {
+ xlog(L_FATAL, "mountd: cannot fork: %s\n",
+ strerror(errno));
+ }
+ /* Now we remove ourselves from the foreground.
+ Redirect stdin/stdout/stderr first. */
+ {
+ int fd = open("/dev/null", O_RDWR);
+ (void) dup2(fd, 0);
+ (void) dup2(fd, 1);
+ (void) dup2(fd, 2);
+ if (fd > 2) (void) close(fd);
+ }
+ setsid();
+ xlog_background();
+ }
+
+ svc_run();
+
+ xlog(L_ERROR, "Ack! Gack! svc_run returned!\n");
+ exit(1);
+}
+
+static void
+usage(const char *prog, int n)
+{
+ fprintf(stderr,
+"Usage: %s [-Fhnv] [-d kind] [-f exports-file] [-V version]\n"
+" [-N version] [--debug kind] [-p|--port port] [--help] [--version]\n"
+" [--exports-file=file] [--nfs-version version]\n"
+" [--no-nfs-version version]\n", prog);
+ exit(n);
+}
diff --git a/utils/mountd/mountd.h b/utils/mountd/mountd.h
new file mode 100644
index 0000000..9f9bc1f
--- /dev/null
+++ b/utils/mountd/mountd.h
@@ -0,0 +1,54 @@
+/*
+ * utils/mountd/mountd.h
+ *
+ * Declarations for mountd.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#ifndef MOUNTD_H
+#define MOUNTD_H
+
+#include <rpc/rpc.h>
+#include <rpc/svc.h>
+#include "nfslib.h"
+#include "exportfs.h"
+#include "mount.h"
+
+union mountd_arguments {
+ dirpath dirpath;
+};
+
+union mountd_results {
+ fhstatus fstatus;
+ mountlist mountlist;
+ exports exports;
+};
+
+/*
+ * Global Function prototypes.
+ */
+bool_t mount_null_1_svc(struct svc_req *, void *, void *);
+bool_t mount_mnt_1_svc(struct svc_req *, dirpath *, fhstatus *);
+bool_t mount_dump_1_svc(struct svc_req *, void *, mountlist *);
+bool_t mount_umnt_1_svc(struct svc_req *, dirpath *, void *);
+bool_t mount_umntall_1_svc(struct svc_req *, void *, void *);
+bool_t mount_export_1_svc(struct svc_req *, void *, exports *);
+bool_t mount_exportall_1_svc(struct svc_req *, void *, exports *);
+bool_t mount_pathconf_2_svc(struct svc_req *, dirpath *, ppathcnf *);
+bool_t mount_mnt_3_svc(struct svc_req *, dirpath *, mountres3 *);
+
+void mount_dispatch(struct svc_req *, SVCXPRT *);
+void auth_init(char *export_file);
+int auth_reload(void);
+nfs_export * auth_authenticate(char *what, struct sockaddr_in *sin,
+ char *path);
+void auth_export(nfs_export *exp);
+
+void mountlist_add(nfs_export *exp, const char *path);
+void mountlist_del(nfs_export *exp, const char *path);
+void mountlist_del_all(struct sockaddr_in *sin);
+mountlist mountlist_list(void);
+
+
+#endif /* MOUNTD_H */
diff --git a/utils/mountd/mountd.man b/utils/mountd/mountd.man
new file mode 100644
index 0000000..593037b
--- /dev/null
+++ b/utils/mountd/mountd.man
@@ -0,0 +1,92 @@
+.\"
+.\" mountd(8)
+.\"
+.\" Copyright (C) 1999 Olaf Kirch <okir@monad.swb.de>
+.TH rpc.mountd 8 "31 May 1999"
+.SH NAME
+rpc.mountd \- NFS mount daemon
+.SH SYNOPSIS
+.BI "/usr/sbin/rpc.mountd [" options "]"
+.SH DESCRIPTION
+The
+.B rpc.mountd
+program implements the NFS mount protocol. When receiving a MOUNT
+request from an NFS client, it checks the request against the list of
+currently exported file systems. If the client is permitted to mount
+the file system,
+.B rpc.mountd
+obtains a file handle for requested directory and returns it to
+the client.
+.SS Exporting NFS File Systems
+Making file systems available to NFS clients is called
+.IR exporting .
+.P
+Usually, a file system and the hosts it should be made available to
+are listed in the
+.B /etc/exports
+file, and invoking
+.B exportfs -a
+whenever the system is booted. The
+.BR exportfs (8)
+command makes export information available to both the kernel NFS
+server module and the
+.B rpc.mountd
+daemon.
+.P
+Alternatively, you can export individual directories temporarily
+using
+.BR exportfs 's
+.IB host : /directory
+syntax.
+.SS The rmtab File
+For every mount request received from an NFS client,
+.B rpc.mountd
+adds an entry to the
+.B /var/state/nfs/rmtab
+file. When receiving an unmount request, that entry is removed.
+user level part of the NFS service.
+.P
+However, this file is mostly ornamental. One, the client can continue
+to use the file handle even after calling
+.BR rpc.mountd 's
+UMOUNT procedure. And two, if a client reboots without notifying
+.BR rpc.mountd ,
+a stale entry will remain in
+.BR rmtab .
+.SH OPTIONS
+.TP
+.\" This file isn't touched by mountd at all--even though it
+.\" accepts the option.
+.\" .BR \-f " or " \-\-exports-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
+.\" .BR exports (5)).
+.\" By default, export information is read from
+.\" .IR /etc/exports .
+.TP
+.BR \-N " or " \-\-no-nfs-version
+This option can be used to request that
+.B rpc.mountd
+does not offer certain versions of NFS. The current version of
+.B rpc.mountd
+can support both NFS version 2 and the newer version 3. If the
+NFS kernel module was compiled without support for NFSv3,
+.B rpc.mountd
+must be invoked with the option
+.BR "\-\-no-nfs-version 3" .
+.TP
+.BR \-v " or " \-\-version
+Print the version of
+.B rpc.mountd
+and exit.
+.SH SEE ALSO
+.BR rpc.nfsd (8),
+.BR exportfs (8),
+.BR exports (5),
+.BR rpc.rquotad (8).
+.SH FILES
+.BR /etc/exports ,
+.BR /var/state/nfs/xtab .
+.SH AUTHOR
+Olaf Kirch, H. J. Lu, G. Allan Morris III, and a host of others.
diff --git a/utils/mountd/rmtab.c b/utils/mountd/rmtab.c
new file mode 100644
index 0000000..289a42e
--- /dev/null
+++ b/utils/mountd/rmtab.c
@@ -0,0 +1,173 @@
+/*
+ * utils/mountd/rmtab.c
+ *
+ * Manage the rmtab file for mountd.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include "config.h"
+
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include "xmalloc.h"
+#include "misc.h"
+#include "exportfs.h"
+#include "xio.h"
+#include "mountd.h"
+
+void
+mountlist_add(nfs_export *exp, const char *path)
+{
+ struct rmtabent xe;
+ struct rmtabent *rep;
+ int lockid;
+
+ if ((lockid = xflock(_PATH_RMTAB, "a")) < 0)
+ return;
+ setrmtabent("r");
+ while ((rep = getrmtabent(1)) != NULL) {
+ if (strcmp (rep->r_client,
+ exp->m_client->m_hostname) == 0
+ && strcmp(rep->r_path, path) == 0) {
+ endrmtabent();
+ xfunlock(lockid);
+ return;
+ }
+ }
+ endrmtabent();
+ strncpy(xe.r_client, exp->m_client->m_hostname,
+ sizeof (xe.r_client) - 1);
+ xe.r_client [sizeof (xe.r_client) - 1] = '\0';
+ strncpy(xe.r_path, path, sizeof (xe.r_path) - 1);
+ xe.r_path [sizeof (xe.r_path) - 1] = '\0';
+ if (setrmtabent("a")) {
+ putrmtabent(&xe);
+ endrmtabent();
+ }
+ xfunlock(lockid);
+}
+
+void
+mountlist_del(nfs_export *exp, const char *path)
+{
+ struct rmtabent *rep;
+ FILE *fp;
+ char *hname = exp->m_client->m_hostname;
+ int lockid;
+
+ if ((lockid = xflock(_PATH_RMTAB, "w")) < 0)
+ return;
+ if (!setrmtabent("r")) {
+ xfunlock(lockid);
+ return;
+ }
+ if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) {
+ endrmtabent();
+ xfunlock(lockid);
+ return;
+ }
+ while ((rep = getrmtabent(1)) != NULL) {
+ if (strcmp (rep->r_client, hname)
+ || strcmp(rep->r_path, path))
+ fputrmtabent(fp, rep);
+ }
+ if (rename(_PATH_RMTABTMP, _PATH_RMTAB) < 0) {
+ xlog(L_ERROR, "couldn't rename %s to %s",
+ _PATH_RMTABTMP, _PATH_RMTAB);
+ }
+ endrmtabent(); /* close & unlink */
+ fendrmtabent(fp);
+ xfunlock(lockid);
+}
+
+void
+mountlist_del_all(struct sockaddr_in *sin)
+{
+ struct in_addr addr = sin->sin_addr;
+ struct hostent *hp;
+ struct rmtabent *rep;
+ nfs_export *exp;
+ FILE *fp;
+ int lockid;
+
+ if ((lockid = xflock(_PATH_RMTAB, "w")) < 0)
+ return;
+ if (!(hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET))) {
+ xlog(L_ERROR, "can't get hostname of %s", inet_ntoa(addr));
+ xfunlock(lockid);
+ return;
+ }
+ else
+ hp = hostent_dup (hp);
+
+ if (!setrmtabent("r")) {
+ xfunlock(lockid);
+ free (hp);
+ return;
+ }
+ if (!(fp = fsetrmtabent(_PATH_RMTABTMP, "w"))) {
+ endrmtabent();
+ xfunlock(lockid);
+ free (hp);
+ return;
+ }
+ while ((rep = getrmtabent(1)) != NULL) {
+ if (strcmp(rep->r_client, hp->h_name) == 0 &&
+ (exp = auth_authenticate("umountall", sin, rep->r_path))) {
+ export_reset(exp);
+ continue;
+ }
+ fputrmtabent(fp, rep);
+ }
+ if (rename(_PATH_RMTABTMP, _PATH_RMTAB) < 0) {
+ xlog(L_ERROR, "couldn't rename %s to %s",
+ _PATH_RMTABTMP, _PATH_RMTAB);
+ }
+ endrmtabent(); /* close & unlink */
+ fendrmtabent(fp);
+ xfunlock(lockid);
+ free (hp);
+}
+
+mountlist
+mountlist_list(void)
+{
+ static mountlist mlist = NULL;
+ static time_t last_mtime = 0;
+ mountlist m;
+ struct rmtabent *rep;
+ struct stat stb;
+ int lockid;
+
+ if ((lockid = xflock(_PATH_RMTAB, "r")) < 0)
+ return NULL;
+ if (stat(_PATH_RMTAB, &stb) < 0) {
+ xlog(L_ERROR, "can't stat %s", _PATH_RMTAB);
+ return NULL;
+ }
+ if (stb.st_mtime != last_mtime) {
+ while (mlist) {
+ mlist = (m = mlist)->ml_next;
+ xfree(m->ml_hostname);
+ xfree(m->ml_directory);
+ xfree(m);
+ }
+ last_mtime = stb.st_mtime;
+
+ setrmtabent("r");
+ while ((rep = getrmtabent(1)) != NULL) {
+ m = (mountlist) xmalloc(sizeof(*m));
+ m->ml_hostname = xstrdup(rep->r_client);
+ m->ml_directory = xstrdup(rep->r_path);
+ m->ml_next = mlist;
+ mlist = m;
+ }
+ endrmtabent();
+ }
+ xfunlock(lockid);
+
+ return mlist;
+}
diff --git a/utils/nfsd/Makefile b/utils/nfsd/Makefile
new file mode 100644
index 0000000..4cc6f8a
--- /dev/null
+++ b/utils/nfsd/Makefile
@@ -0,0 +1,35 @@
+#
+# Makefile for knfsd
+#
+
+PROGRAM = nfsd
+PREFIX = rpc.
+OBJS = nfsd.o
+DEPLIBS = $(TOP)support/lib/libfs.a
+LIBS = -lnfs
+MAN8 = nfsd
+
+include $(TOP)rules.mk
+
+#
+# all:: nfsd
+# @echo "Done."
+#
+# nfsd: $(OBJS)
+# $(CC) $(LDFLAGS) -o $@ $(OBJS) -lnfs
+#
+# clean distclean::
+# rm -f *.o
+#
+# distclean::
+# rm -f nfsd .depend
+#
+# install::
+# install -o root -g root -m 755 nfsd /usr/sbin/rpc.$knfsd
+#
+# dep::
+# $(CC) $(CFLAGS) -M $(OBJS:.o=.c) > .depend
+#
+# ifeq (.depend,$(wildcard .depend))
+# include .depend
+# endif
diff --git a/utils/nfsd/nfsd.c b/utils/nfsd/nfsd.c
new file mode 100644
index 0000000..3a22370
--- /dev/null
+++ b/utils/nfsd/nfsd.c
@@ -0,0 +1,68 @@
+/*
+ * nfsd
+ *
+ * This is the user level part of nfsd. This is very primitive, because
+ * all the work is now done in the kernel module.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include "nfslib.h"
+
+static void usage(const char *);
+
+int
+main(int argc, char **argv)
+{
+ int count = 1, c, error, port;
+
+ port = 2049;
+
+ /* FIXME: Check for nfs in /etc/services */
+
+ while ((c = getopt(argc, argv, "hp:P:")) != EOF) {
+ switch(c) {
+ case 'P': /* XXX for nfs-server compatibility */
+ case 'p':
+ port = atoi(optarg);
+ if (port <= 0 || port > 65535) {
+ fprintf(stderr, "%s: bad port number: %s\n",
+ argv[0], optarg);
+ usage(argv [0]);
+ }
+ break;
+ break;
+ case 'h':
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (optind < argc) {
+ if ((count = atoi(argv[optind])) < 0) {
+ /* insane # of servers */
+ fprintf(stderr,
+ "%s: invalid server count (%d), using 1\n",
+ argv[0], count);
+ count = 1;
+ }
+ }
+
+ if ((error = nfssvc(port, count)) < 0)
+ perror("nfssvc");
+
+ return (error != 0);
+}
+
+static void
+usage(const char *prog)
+{
+ fprintf(stderr, "usage:\n"
+ "%s nrservs\n", prog);
+ exit(2);
+}
diff --git a/utils/nfsd/nfsd.man b/utils/nfsd/nfsd.man
new file mode 100644
index 0000000..f415cfd
--- /dev/null
+++ b/utils/nfsd/nfsd.man
@@ -0,0 +1,46 @@
+.\"
+.\" nfsd(8)
+.\"
+.\" Copyright (C) 1999 Olaf Kirch <okir@monad.swb.de>
+.TH rpc.nfsd 8 "31 May 1999"
+.SH NAME
+rpc.nfsd \- NFS server process
+.SH SYNOPSIS
+.BI "/usr/sbin/rpc.nfsd [-p " port "] " nproc
+.SH DESCRIPTION
+The
+.B rpc.nfsd
+program implements the user level part of the NFS service. The
+main functionality is handled by the
+.B nfsd.o
+kernel module; the user space program merely starts the specified
+number of kernel threads.
+.P
+The
+.B rpc.mountd
+server provides an ancially service needed to satisfy mount requests
+by NFS clients.
+.SH OPTIONS
+.TP
+.BI \-p " port"
+specify a diferent port to listen on for NFS requests. By default,
+.B rpc.nfsd
+will listen on port 2049.
+.TP
+.I nproc
+specify the number of NFS server threads. By default, just one
+thread is started. However, for optimum performance several threads
+should be used. The actual figure depends on the number of and the work
+load created by the NFS clients, but a useful starting point is
+8 threads. Effects of modifying that number can be checked using
+the
+.BR nfsstat (8)
+program.
+.SH SEE ALSO
+.BR rpc.mountd (8),
+.BR exportfs (8),
+.BR rpc.rquotad (8),
+.BR nfsstat (8).
+.SH AUTHOR
+Olaf Kirch, Bill Hawes, H. J. Lu, G. Allan Morris III,
+and a host of others.
diff --git a/utils/nfsstat/Makefile b/utils/nfsstat/Makefile
new file mode 100644
index 0000000..e3a9428
--- /dev/null
+++ b/utils/nfsstat/Makefile
@@ -0,0 +1,32 @@
+#
+# dummy Makefile
+#
+
+PROGRAM = nfsstat
+OBJS = nfsstat.o
+MAN8 = nfsstat
+
+include $(TOP)rules.mk
+
+#
+# all:: nfsstat
+# @echo "Done."
+#
+# nfsstat: $(OBJS)
+# $(CC) $(LDFLAGS) -o $@ $(OBJS)
+#
+# clean distclean::
+# rm -f *.o core
+#
+# distclean::
+# rm -f nfsstat .depend
+#
+# install:
+# install -o root -g root -m 755 nfsstat /usr/sbin/$knfsstat
+#
+# dep::
+# $(CC) $(CFLAGS) -M $(OBJS:.o=.c) > .depend
+#
+# ifeq (.depend,$(wildcard .depend))
+# include .depend
+# endif
diff --git a/utils/nfsstat/nfsstat.c b/utils/nfsstat/nfsstat.c
new file mode 100644
index 0000000..55b5cd0
--- /dev/null
+++ b/utils/nfsstat/nfsstat.c
@@ -0,0 +1,328 @@
+/*
+ * nfsstat.c Output NFS statistics
+ *
+ * Copyright (C) 1995, 1996, 1999 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include "config.h"
+
+#define NFSSVCSTAT "/proc/net/rpc/nfsd"
+#define NFSCLTSTAT "/proc/net/rpc/nfs"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#define MAXNRVALS 32
+
+static unsigned int svcv2info[19]; /* NFSv2 call counts ([0] == 18) */
+static unsigned int cltv2info[19]; /* NFSv2 call counts ([0] == 18) */
+static unsigned int svcv3info[22]; /* NFSv3 call counts ([0] == 22) */
+static unsigned int cltv3info[22]; /* NFSv3 call counts ([0] == 22) */
+static unsigned int svcnetinfo[4]; /* 0 # of received packets
+ * 1 UDP packets
+ * 2 TCP packets
+ * 3 TCP connections
+ */
+static unsigned int cltnetinfo[4]; /* 0 # of received packets
+ * 1 UDP packets
+ * 2 TCP packets
+ * 3 TCP connections
+ */
+
+static unsigned int svcrpcinfo[5]; /* 0 total # of RPC calls
+ * 1 total # of bad calls
+ * 2 bad format
+ * 3 authentication failed
+ * 4 unknown client
+ */
+static unsigned int cltrpcinfo[3]; /* 0 total # of RPC calls
+ * 1 retransmitted calls
+ * 2 cred refreshs
+ */
+
+static unsigned int svcrcinfo[8]; /* 0 repcache hits
+ * 1 repcache hits
+ * 2 uncached reqs
+ *
+ * including fh info:
+ * 3 cached fh's
+ * 4 valid fh's
+ * 5 fixup required
+ * 6 lookup (?)
+ * 7 stale
+ */
+
+static const char * nfsv2name[18] = {
+ "null", "getattr", "setattr", "root", "lookup", "readlink",
+ "read", "wrcache", "write", "create", "remove", "rename",
+ "link", "symlink", "mkdir", "rmdir", "readdir", "fsstat"
+};
+
+static const char * nfsv3name[22] = {
+ "null", "getattr", "setattr", "lookup", "access", "readlink",
+ "read", "write", "create", "mkdir", "symlink", "mknod",
+ "remove", "rmdir", "rename", "link", "readdir", "readdirplus",
+ "fsstat", "fsinfo", "pathconf", "commit"
+};
+
+typedef struct statinfo {
+ char *tag;
+ int nrvals;
+ unsigned int * valptr;
+
+ /* Filled in by parse_statfile */
+ int * foundp;
+} statinfo;
+
+static statinfo svcinfo[] = {
+ { "net", 4, svcnetinfo },
+ { "rpc", 5, svcrpcinfo },
+ { "rc", 8, svcrcinfo }, /* including fh_* */
+ { "proc2", 19, svcv2info },
+ { "proc3", 23, svcv3info },
+ { NULL, 0, 0 }
+};
+
+static statinfo cltinfo[] = {
+ { "net", 4, cltnetinfo },
+ { "rpc", 3, cltrpcinfo },
+ { "proc2", 19, cltv2info },
+ { "proc3", 23, cltv3info },
+ { NULL, 0, 0 }
+};
+
+static void print_numbers(const char *, unsigned int *,
+ unsigned int);
+static void print_callstats(const char *, const char **,
+ unsigned int *, unsigned int);
+static int parse_statfile(const char *, struct statinfo *);
+
+#define PRNT_CALLS 0x0001
+#define PRNT_RPC 0x0002
+#define PRNT_NET 0x0004
+#define PRNT_FH 0x0008
+#define PRNT_RC 0x0010
+#define PRNT_ALL 0xffff
+
+int
+main(int argc, char **argv)
+{
+ int opt_all = 0,
+ opt_srv = 0,
+ opt_clt = 0,
+ opt_prt = 0;
+ int c;
+
+ while ((c = getopt(argc, argv, "acno:rsz")) != -1) {
+ switch (c) {
+ case 'a':
+ opt_all = 1;
+ break;
+ case 'c':
+ opt_clt = 1;
+ break;
+ case 'n':
+ opt_prt |= PRNT_CALLS;
+ break;
+ case 'o':
+ if (!strcmp(optarg, "nfs"))
+ opt_prt |= PRNT_CALLS;
+ else if (!strcmp(optarg, "rpc"))
+ opt_prt |= PRNT_RPC;
+ else if (!strcmp(optarg, "net"))
+ opt_prt |= PRNT_NET;
+ else if (!strcmp(optarg, "rc"))
+ opt_prt |= PRNT_RC;
+ else if (!strcmp(optarg, "fh"))
+ opt_prt |= PRNT_FH;
+ else {
+ fprintf(stderr, "nfsstat: unknown category: "
+ "%s\n", optarg);
+ return 2;
+ }
+ break;
+ case 'r':
+ opt_prt |= PRNT_RPC;
+ break;
+ case 's':
+ opt_srv = 1;
+ break;
+ case 'z':
+ fprintf(stderr, "nfsstat: zeroing of nfs statistics "
+ "not yet supported\n");
+ return 2;
+ }
+ }
+
+ if (opt_all) {
+ opt_srv = opt_clt = 1;
+ opt_prt = PRNT_ALL;
+ }
+ if (!(opt_srv + opt_clt))
+ opt_srv = opt_clt = 1;
+ if (!opt_prt)
+ opt_prt = PRNT_CALLS + PRNT_RPC;
+ if ((opt_prt & (PRNT_FH|PRNT_RC)) && !opt_srv) {
+ fprintf(stderr,
+ "You requested file handle or request cache "
+ "statistics while using the -c option.\n"
+ "This information is available only for the NFS "
+ "server.\n");
+ }
+
+ if ((opt_srv && !parse_statfile(NFSSVCSTAT, svcinfo))
+ || (opt_clt && !parse_statfile(NFSCLTSTAT, cltinfo)))
+ return 2;
+
+ if (opt_srv) {
+ if (opt_prt & PRNT_NET) {
+ print_numbers(
+ "Server packet stats:\n"
+ "packets udp tcp tcpconn\n",
+ svcnetinfo, 4
+ );
+ }
+ if (opt_prt & PRNT_RPC) {
+ print_numbers(
+ "Server rpc stats:\n"
+ "calls badcalls badauth badclnt xdrcall\n",
+ svcrpcinfo, 5
+ );
+ }
+ if (opt_prt & PRNT_RC) {
+ print_numbers(
+ "Server reply cache:\n"
+ "hits misses nocache\n",
+ svcrcinfo, 3
+ );
+ }
+ if (opt_prt & PRNT_FH) {
+ print_numbers(
+ "Server file handle cache:\n"
+ "cached valid fixup lookup stale\n",
+ svcrcinfo + 3, 5);
+ }
+ if (opt_prt & PRNT_CALLS) {
+ print_callstats(
+ "Server nfs v2:\n",
+ nfsv2name, svcv2info + 1, 18
+ );
+ if (svcv3info[0])
+ print_callstats(
+ "Server nfs v3:\n",
+ nfsv3name, svcv3info + 1, 22
+ );
+ }
+ }
+
+ if (opt_clt) {
+ if (opt_prt & PRNT_NET) {
+ print_numbers(
+ "Client packet stats:\n"
+ "packets udp tcp tcpconn\n",
+ cltnetinfo, 4
+ );
+ }
+ if (opt_prt & PRNT_RPC) {
+ print_numbers(
+ "Client rpc stats:\n"
+ "calls retrans authrefrsh\n",
+ cltrpcinfo, 3
+ );
+ }
+ if (opt_prt & PRNT_CALLS) {
+ print_callstats(
+ "Client nfs v2:\n",
+ nfsv2name, cltv2info + 1, 18
+ );
+ if (cltv3info[0])
+ print_callstats(
+ "Client nfs v3:\n",
+ nfsv3name, cltv3info + 1, 22
+ );
+ }
+ }
+
+ return 0;
+}
+
+static void
+print_numbers(const char *hdr, unsigned int *info, unsigned int nr)
+{
+ unsigned int i;
+
+ fputs(hdr, stdout);
+ for (i = 0; i < nr; i++)
+ printf("%s%-8d", i? " " : "", info[i]);
+ printf("\n");
+}
+
+static void
+print_callstats(const char *hdr, const char **names,
+ unsigned int *info, unsigned int nr)
+{
+ unsigned int total;
+ int i, j;
+
+ fputs(hdr, stdout);
+ for (i = 0, total = 0; i < nr; i++)
+ total += info[i];
+ if (!total)
+ total = 1;
+ for (i = 0; i < nr; i += 6) {
+ for (j = 0; j < 6 && i + j < nr; j++)
+ printf("%-11s", names[i+j]);
+ printf("\n");
+ for (j = 0; j < 6 && i + j < nr; j++)
+ printf("%-6d %2d%% ",
+ info[i+j], 100 * info[i+j] / total);
+ printf("\n");
+ }
+ printf("\n");
+}
+
+static int
+parse_statfile(const char *name, struct statinfo *statp)
+{
+ char buffer[4096], *next;
+ FILE *fp;
+
+ /* Being unable to read e.g. the nfsd stats file shouldn't
+ * be a fatal error -- it usually means the module isn't loaded.
+ */
+ if ((fp = fopen(name, "r")) == NULL) {
+ fprintf(stderr, "Warning: %s: %m\n", name);
+ return 1;
+ }
+
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ struct statinfo *ip;
+ char *sp, *line = buffer;
+ int i, cnt;
+
+ if ((next = strchr(line, '\n')) != NULL)
+ *next++ = '\0';
+ if (!(sp = strtok(line, " \t")))
+ continue;
+ for (ip = statp; ip->tag; ip++) {
+ if (!strcmp(sp, ip->tag))
+ break;
+ }
+ if (!ip->tag)
+ continue;
+ cnt = ip->nrvals;
+
+ for (i = 0; i < cnt; i++) {
+ if (!(sp = strtok(NULL, " \t")))
+ break;
+ ip->valptr[i] = atoi(sp);
+ }
+ }
+
+ fclose(fp);
+ return 1;
+}
diff --git a/utils/nfsstat/nfsstat.man b/utils/nfsstat/nfsstat.man
new file mode 100644
index 0000000..72c8051
--- /dev/null
+++ b/utils/nfsstat/nfsstat.man
@@ -0,0 +1,74 @@
+.\"
+.\" nfsstat(8)
+.\"
+.\" Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de>
+.TH nfsstat 8 "8 May 1996"
+.SH NAME
+nfsstat \- print NFS statistics
+.SH SYNOPSIS
+.BI "/usr/sbin/nfsstat [-anrcsz] [-o " "facility" "] ...
+.SH DESCRIPTION
+The
+.B nfsstat
+command retrieves and pretty-prints NFS kernel statistics. Currently, only
+server-side statistics are supported, because the NFS client does not yet
+collect any data.
+.SH OPTIONS
+.TP
+.B -s
+Print only server-side statistics. The default is to print both server and
+client statistics.
+.TP
+.B -c
+Print only client-side statistics.
+.TP
+.B -n
+Print only NFS statistics. The default is to print both NFS and RPC
+information.
+.TP
+.B -r
+Print only RPC statistics.
+.TP
+.B -z
+Zero the kernel statistics counters.
+This option is not currently supported.
+.TP
+.BI -o " facility
+Display statistics for the specified facility, which must be one of:
+.RS
+.TP
+.B nfs
+NFS protocol information, split up by RPC call.
+.TP
+.B rpc
+General RPC information.
+.TP
+.B net
+Network layer statistics, such as the number of received packets, number
+of TCP connections, etc.
+.TP
+.B fh
+Usage information on the server's file handle cache, including the
+total number of lookups, and the number of hits and misses.
+.TP
+.B rc
+Usage information on the server's request reply cache, including the
+total number of lookups, and the number of hits and misses.
+.RE
+.SH EXAMPLES
+.\" --------------------- FILES ----------------------------------
+.SH FILES
+.TP
+.B /proc/net/rpc/nfsd
+.BR procfs -based
+interface to kernel NFS server statistics.
+.TP
+.B /proc/net/rpc/nfs
+.BR procfs -based
+interface to kernel NFS client statistics.
+.\" -------------------- SEE ALSO --------------------------------
+.SH SEE ALSO
+.BR rpc.nfsd (8).
+.\" -------------------- AUTHOR ----------------------------------
+.SH AUTHOR
+Olaf Kirch, <okir@monad.swb.de>
diff --git a/utils/nhfsstone/DISCLAIMER b/utils/nhfsstone/DISCLAIMER
new file mode 100644
index 0000000..afde6a3
--- /dev/null
+++ b/utils/nhfsstone/DISCLAIMER
@@ -0,0 +1,33 @@
+@(#)DISCLAIMER 1.4 89/07/07 Legato Systems, Inc.
+
+ IMPORTANT. READ BEFORE USING. USE OF THE PROGRAM WILL
+ CONSTITUTE ACCEPTANCE OF THE FOLLOWING LICENSE TERMS.
+
+Legato nhfsstone source code is a copyrighted product of Legato
+Systems, Inc. and is provided for unrestricted use and distribution of
+the binary program derived from it.
+
+You may copy Legato nhfsstone source, object code and related
+documentation as necessary, but are not authorized to license it to
+anyone else. Legato nhfsstone may be modified only for the purpose of
+porting. If the basic algorithms are changed the resulting program may
+not be called nhfsstone.
+
+Legato nhfsstone is provided with no support and without any obligation
+on the part of Legato Systems, Inc. to assist in its use, correction,
+modification or enhancement.
+
+LEGATO NHFSSTONE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND
+INCLUDING THE WARRANTIES OF DESIGN, MERCHANTIBILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NONINFRINGEMENT, OR ARISING FROM A COURSE OF
+DEALING, USAGE OR TRADE PRACTICE.
+
+LEGATO SYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY LEGATO
+NHFSSTONE, ANY PART THEREOF OR THE USE THEREOF.
+
+IN NO EVENT WILL LEGATO SYSTEMS, INC. BE LIABLE UNDER ANY CONTRACT,
+NEGLIGENCE, STRICT LIABILITY OR OTHER THEORY FOR ANY LOST REVENUE OR
+PROFITS OR OTHER SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR COST OF
+PROCUREMENT OF SUBSTITUTE GOODS OR TECHNOLOGY, EVEN IF LEGATO HAS BEEN
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
diff --git a/utils/nhfsstone/Makefile b/utils/nhfsstone/Makefile
new file mode 100644
index 0000000..d73d85a
--- /dev/null
+++ b/utils/nhfsstone/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for nhfsstone
+#
+
+PROGRAM = nhfsstone
+OBJS = nhfsstone.o
+
+include $(TOP)rules.mk
diff --git a/utils/nhfsstone/README b/utils/nhfsstone/README
new file mode 100644
index 0000000..f13dde5
--- /dev/null
+++ b/utils/nhfsstone/README
@@ -0,0 +1,111 @@
+@(#)README 1.6 89/07/07 Legato Systems, Inc.
+
+This directory contains the source for the nhfsstone (pronounced
+n-f-s-stone, the "h" is silent) NFS load generating program. This
+version of the program can only be compiled on 4.x BSD based UNIX
+systems.
+
+nhfsstone is used on an NFS client to generate an artificial load
+with a particular mix of NFS operations. It reports the average
+response time of the server in milliseconds per call and the load in
+calls per second. The program adjusts its calling patterns based on
+the client's kernel NFS statistics and the elapsed time. Load can be
+generated over a given time or number of NFS calls. See the "nhfsstone.1"
+manual page for more details.
+
+The files in this directory are:
+
+ DISCLAIMER legal requirements
+ Makefile Makefile used to build nhfsstone
+ README This file
+ nhfsstone.c source file
+ nhfsstone.1 manual page
+ nhfsrun shell script to run nhfsstone over multiple loads
+ nhfsnums shell script to convert nhfsrun output to plot(5)
+ nhfsgraph shell script to create a graph from nhfsnums output
+
+The file "nhfsstone.1" is a manual page that describes how to use the
+nhfsstone program. To look at it type "nroff -man nhfsstone.1".
+
+To build an executable type "make nhfsstone". To install it, become
+super-user and then type "make install". This will strip the
+executable, set the group to "kmem" and set the setgid bit. If your
+site requires different installation of programs that read /dev/kmem
+you may have to use different ownership or permissions. Make install
+will also set the execute bits on the shell scripts nhfsrun, nhfsnums
+and nhfsgraph.
+
+To run an nhfsstone test, create a parent test directory on a filesystem
+that is NFS mounted, cd to that directory and type "nhfsstone". This will
+do a run with the default settings, load = 30 calls/sec, 5000 calls,
+and 7 sub-processes.
+
+If you want to spread the load across several server disks, first
+figure out on the server which disk partitions are exported as which
+filesystems. If you don't already have more than one of these
+filesystems mounted on your test client you can mount them in temporary
+locations, like /mnt. Create test directories on these filesystems so
+that the load will be distributed according to the simulation that you
+want to run (for example, you might put 4 test directories on the
+filesystem where the diskless client's root and swap live, and 2 on the
+home directories filesystem, and one on the executables filesystem).
+Now create a parent test directory cd to it, and make symbolic links
+with the names testdir0, testdir1, ... testdir6, that point to the
+real test directories. Finally, run nhfsstone from the parent test
+directory.
+
+If you are doing the test from a diskless machine, putting half of the
+test directories in /tmp or /usr/tmp and running the test from your
+home directory will simulate real diskless load patterns fairly well.
+
+To do a run over multiple load levels, edit the shell script "nhfsrun" and
+set the shell variables "START", "END", and "INCR" to be the correct
+starting and ending loads, and load increment. The script will iterate
+from START to END with an increment of INCR, run nhfsstone at each
+load level, and put the output in the file "run.out". The output file
+name can be changed by editing the nhfsrun script and changing the
+"OUTFILE" variable or by passing a file name suffix on the command line:
+
+ nhfsrun xysd
+
+This produces the output file "run.xysd".
+
+The script "nhfsnums" takes the output from nhfsrun and converts it
+into plot(5) format so that it can be graphed using graph(1) and other
+tools. It takes its input either from files given on the command line
+or from standard in:
+
+ nhfsnums [numsfile] ...
+
+If file names are given, the suffix of each name (the part after the
+".") is used as the line label for the set of numbers produced (see
+plot(5)).
+
+"nhfsgraph" takes the output from nhfsnums and passes it to graph(1)
+with the right arguments to produce PostScript output for a labeled
+graph. The nhfsgraph script can be used as a filter:
+
+ nhfsnums run.* | nhfsgraph | lpr
+
+
+
+
+This program is provided free of charge to anyone who wants it provided
+certain conditions are met (see DISCLAIMER file for more details).
+
+If you would like to receive regular information and bug fixes please
+send your name, and both your Email and U.S. mail addresses to:
+
+ Legato Systems, Inc.
+ Nhfsstone
+ 260 Sheridan Avenue
+ Palo Alto, California 94306
+
+ nhfsstone-request@legato.com or uunet!legato.com!nhfsstone-request
+
+and we will add your name to the nhfsstone mailing list. Comments and bug
+reports should be sent to:
+
+ nhfsstone@legato.com or uunet!legato.com!nhfsstone
+
+
diff --git a/utils/nhfsstone/README.linux b/utils/nhfsstone/README.linux
new file mode 100644
index 0000000..e9b7899
--- /dev/null
+++ b/utils/nhfsstone/README.linux
@@ -0,0 +1,11 @@
+
+
+ This is my port of nhfsstone to Linux. As a benchmark, it has been
+ superseded by LADDIS (but unfortunately, LADDIS comes with a 1200 buck
+ price tag), but it's quite good at catching NFS bugs :-)
+
+ Of course, this port does not work with the old NFS client code, as
+ it does not collect RPC stats.
+
+ Olaf
+
diff --git a/utils/nhfsstone/nhfsgraph b/utils/nhfsstone/nhfsgraph
new file mode 100755
index 0000000..56e2c77
--- /dev/null
+++ b/utils/nhfsstone/nhfsgraph
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# @(#)nhfsgraph.sh 1.3 89/07/07 Copyright (c) 1989, Legato Systems, Inc.
+#
+# See DISCLAIMER file for restrictions
+#
+
+#
+# Usage: nhfsgraph <graphfile> ...
+#
+# Produce a PostScript graph of nhfsstone numbers.
+# Graphfile is a file with number pairs in plot(5) format derived
+# from runs of nhfsstone at different loads (see "nhfsrun" and "nhfsnums"
+# scripts.
+#
+# If you want something other than PostScript output replace "psplot"
+# with "plot". See plot(1) for more details.
+#
+
+LABEL="x=Load (calls/sec) y=Response (msec/call)"
+
+cat $* \
+ | graph -b -u .1 -h 1.2 -g 2 -l "$LABEL" -x 10 80 10 | psplot
diff --git a/utils/nhfsstone/nhfsnums b/utils/nhfsstone/nhfsnums
new file mode 100755
index 0000000..aae625d
--- /dev/null
+++ b/utils/nhfsstone/nhfsnums
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# @(#)nhfsnums.sh 1.3 89/07/07 Copyright (c) 1989, Legato Systems, Inc.
+#
+# See DISCLAIMER file for restrictions
+#
+
+#
+# Usage: nhfsnums <numsfile> ...
+#
+# Collect raw numbers from nhfsstone output and print in plot(5) format.
+# The nums files should be named "run.xxx" where xxx is a name related
+# to the numbers gathered. Each file will produce one line with a label
+# that is the file suffix (the part following the dot.)
+#
+
+for i in $*; do
+ RUNNAME=`echo $i | sed -e 's/.*\\.//'`
+ awk '{ print $5 " " $7 }' $i \
+ | sort -n\
+ | sed -e "\$s/\$/ \"$RUNNAME\"/"
+done
diff --git a/utils/nhfsstone/nhfsrun b/utils/nhfsstone/nhfsrun
new file mode 100755
index 0000000..dfc24eb
--- /dev/null
+++ b/utils/nhfsstone/nhfsrun
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# @(#)nhfsrun.sh 1.3 89/07/07 Copyright (c) 1989, Legato Systems, Inc.
+#
+# See DISCLAIMER file for restrictions
+#
+
+#
+# Usage: nhfsrun [suffix]
+#
+# Run nhfsstone with a range of different loads and put
+# results in a file called run.<suffix>
+#
+
+if [ $# -gt 1 ]; then
+ echo "usage: $0 [suffix]"
+ exit 1
+fi
+
+#
+# Output file
+#
+if [ $# -eq 1 ]; then
+ OUTFILE=run.$1
+else
+ OUTFILE=run.out
+fi
+
+#
+# Starting load
+#
+START=10
+
+#
+# Ending load
+#
+END=80
+
+#
+# Load increment
+#
+INCR=10
+
+#
+# Catch SIGUSR1 and ignore it.
+# SIGUSR1 is used by nhfsstone to synchronize child processes.
+#
+nothing() { echo -n ""; }
+trap nothing 30
+
+rm -f $OUTFILE
+
+LOAD=$START
+while [ $LOAD -le $END ]; do
+ echo nhfsstone -l $LOAD
+ nhfsstone -l $LOAD >> $OUTFILE
+ tail -1 $OUTFILE
+ LOAD=`expr $LOAD + $INCR`
+done
diff --git a/utils/nhfsstone/nhfsstone.1 b/utils/nhfsstone/nhfsstone.1
new file mode 100644
index 0000000..e56eb9e
--- /dev/null
+++ b/utils/nhfsstone/nhfsstone.1
@@ -0,0 +1,381 @@
+.\" @(#)nhfsstone.1 1.13 89/10/05 Copyright (c) 1989, Legato Systems Inc
+.\" See DISCLAIMER file for restrictions
+.TH NHFSSTONE 1 "4 October 1989"
+.SH NAME
+nhfsstone \- Network File System benchmark program
+.SH SYNOPSIS
+.B nhfsstone
+[
+.B \-v
+] [[
+.B \-t secs
+] | [
+.B -c calls
+]] [
+.B \-l load
+] [
+.B \-p nprocs
+] [
+.B \-m mixfile
+] [
+.B dir
+]...
+.SH DESCRIPTION
+.B nhfsstone
+(pronounced n\-f\-s\-stone, the "h" is silent)
+is used on a
+.SM NFS
+client to generate an artificial load with a particular mix of
+.SM NFS
+operations. It reports the average response time of the server in
+milliseconds per call and the load in calls per second.
+The program adjusts its calling patterns based on the client's kernel
+.SM NFS
+statistics and the elapsed time.
+Load can be generated over a given time or number of
+.SM NFS
+calls.
+.LP
+Because it uses the kernel
+.SM NFS
+statistics to monitor its progress,
+.B nhfsstone
+cannot be used to measure the performance of non\-NFS filesystems.
+.LP
+The
+.B nhfsstone
+program uses file and directory manipulation in an attempt to generate
+particular
+.SM NFS
+operations in response to particular system calls.
+To do this it uses several tricks
+that are based on a knowledge of the implementation of the
+.SM NFS
+client side reference port.
+For example, it uses long file names to circumvent the kernel name lookup
+cache so that a
+.BR stat (2)
+system call generates an
+.SM NFS
+lookup operation.
+.LP
+The mix of
+.SM NFS
+operations can be set with a mix file, which is the output of the
+.BR nfsstat (8C)
+command (see the "\-m" option below).
+The percentages taken from
+the mix file are calculated based on the number of
+.SM NFS
+calls, not on the percentages printed by nfsstat. Operations with
+0% in the mix will never get called by
+.BR nhfsstone .
+In a real server load mix, even though the percentage of call for
+a particular
+.SM NFS
+operation may be zero, the number of calls is often nonzero.
+.B Nhfsstone
+makes the assumption that the number of calls to these 0 percent
+operations will have an insignificant effect on server response.
+.LP
+Normally
+.B nhfsstone
+should be given a list of two or more test directories to use
+(default is to use the current directory).
+The test directories used should be located on different disks and
+partitions on the server to realistically simulate typical server loads.
+Each
+.B nhfsstone
+process looks for a directory
+.B <dir>/testdir<n>
+(where <n> is a number from 0 to
+.B nprocs
+\- 1).
+If a process directory name already exists,
+it is checked for the correct set of test files.
+Otherwise the directory is created and populated.
+.SH OPTIONS
+.TP 12
+.B \-v
+Verbose output.
+.TP
+.B \-t secs
+Sets
+.B calls
+based on the given running time (in seconds) and the load.
+.TP
+.B \-c calls
+Total number of
+.SM NFS
+calls to generate (default is 5000).
+.TP
+.B \-l load
+Load to generate in
+.SM NFS
+calls per second (default is 30).
+.TP
+.B \-p nprocs
+Number of load generating sub\-processes to fork (default is 7).
+This can be used to maximize the amount of load a single machine can generate.
+On a small client machine (slow CPU or small amount of memory)
+fewer processes might be used to avoid swapping.
+.TP
+.B \-m mixfile
+Mix of
+.SM NFS
+operations to generate.
+The format of
+.B mixfile
+is the same as the output of the
+.BR nfsstat (8C)
+program.
+A mix file can be created on a server by typing "nfsstat \-s > mixfile".
+The default mix of operations is: null 0%, getattr 13%, setattr 1%,
+root 0%, lookup 34%, readlink 8%, read 22%, wrcache 0%, write 15%, create 2%,
+remove 1%, rename 0%, link 0%, symlink 0%, mkdir 0%, rmdir 0%, readdir 3%,
+fsstat 1%.
+.SH USING NHFSSTONE
+As with all benchmarks,
+.B nhfsstone
+can only provide numbers that are useful if experiments that use it are
+set up carefully.
+Since it is measuring servers, it should be run on a client
+that will not limit the generation of
+.SM NFS
+requests.
+This means it should have a fast CPU,
+a good ethernet interface and the machine
+should not be used for anything else during testing.
+A Sun\-3/50 can generate about 60
+.SM NFS
+calls per second before it runs out of CPU.
+.LP
+.B Nhfsstone
+assumes that all
+.SM NFS
+calls generated on the client are going to a single server, and that
+all of the
+.SM NFS
+load on that server is due to this client.
+To make this assumption hold,
+both the client and server should be as quiescent as possible during tests.
+.LP
+If the network is heavily utilized the delays due to collisions
+may hide any changes in server performance.
+High error rates on either the client or server can also
+cause delays due to retransmissions of lost or damaged packets.
+.BR netstat (8C)
+.B \-i
+can be used to measure the error and collision rates on the client and server.
+.LP
+To best simulate the effects of
+.SM NFS
+clients on the server, the test
+directories should be set up so that they are on at least two of the
+disk partitions that the server exports and the partitions should be
+as far apart as possible. The
+.BR dkinfo (8)
+command can be used to find the physical geometry of disk on BSD based systems.
+.SM NFS
+operations tend to randomize
+access the whole disk so putting all of the
+.B nhfsstone
+test directories on a single partition or on
+two partitions that are close together will not show realistic results.
+.LP
+On all tests it is a good idea to run the tests repeatedly and compare results.
+The number of calls can be increased
+(with the
+.B \-c
+option) until the variance in milliseconds per call is acceptably small.
+If increasing the number of calls does not help there may be something
+wrong with the experimental setup.
+One common problem is too much memory on the client
+test machine. With too much memory,
+.B nhfsstone
+is not able to defeat the client caches and the
+.SM NFS
+operations do not end up going to the server at all. If you suspect that
+there is a caching problem you can use the
+.B -p
+option to increase the number of processes.
+.LP
+The numbers generated by
+.B nhfsstone
+are most useful for comparison if the test setup on the client machine
+is the same between different server configurations.
+Changing
+.B nhfsstone
+parameters between runs will produce numbers that can not be
+meaningfully compared.
+For example, changing the number of generator processes
+may affect the measured response
+time due to context switching or other delays on the client machine, while
+changing the mix of
+.SM NFS
+operations will change the whole nature of the experiment.
+Other changes to the client configuration may also effect the comparability
+of results.
+While
+.B nhfsstone
+tries to compensate for differences in client configurations
+by sampling the actual
+.SM NFS
+statistics and adjusting both the load and mix of operations, some changes
+are not reflected in either the load or the mix. For example, installing
+a faster CPU or mounting different
+.SM NFS
+filesystems may effect the response time without changing either the
+load or the mix.
+.LP
+To do a comparison of different server configurations, first set up the
+client test directories and do
+.B nhfsstone
+runs at different loads to be sure that the variability is
+reasonably low. Second, run
+.B nhfsstone
+at different loads of interest and
+save the results. Third, change the server configuration (for example,
+add more memory, replace a disk controller, etc.). Finally, run the same
+.B nhfsstone
+loads again and compare the results.
+.SH SEE ALSO
+.LP
+The
+.B nhfsstone.c
+source file has comments that describe in detail the operation of
+of the program.
+.SH ERROR MESSAGES
+.TP
+.B "illegal calls value"
+The
+.B calls
+argument following the
+.B \-c
+flag on the command line is not a positive number.
+.TP
+.B "illegal load value"
+The
+.B load
+argument following the
+.B \-l
+flag on the command line is not a positive number.
+.TP
+.B "illegal time value"
+The
+.B time
+argument following the
+.B \-t
+flag on the command line is not a positive number.
+.TP
+.B "bad mix file"
+The
+.B mixfile
+file argument following the
+.B \-m
+flag on the command line could not be accessed.
+.TP
+.B "can't find current directory"
+The parent process couldn't find the pathname of the current directory.
+This usually indicates a permission problem.
+.TP
+.B "can't fork"
+The parent couldn't fork the child processes. This usually results from
+lack of resources, such as memory or swap space.
+.TP
+.PD 0
+.B "can't open log file"
+.TP
+.B "can't stat log"
+.TP
+.B "can't truncate log"
+.TP
+.B "can't write sync file"
+.TP
+.B "can't write log"
+.TP
+.B "can't read log"
+.PD
+A problem occurred during the creation, truncation, reading or writing of the
+synchronization log file. The parent process creates the
+log file in /tmp and uses it to synchronize and communicate with its children.
+.TP
+.PD 0
+.B "can't open test directory"
+.TP
+.B "can't create test directory"
+.TP
+.B "can't cd to test directory"
+.TP
+.B "wrong permissions on test dir"
+.TP
+.B "can't stat testfile"
+.TP
+.B "wrong permissions on testfile"
+.TP
+.B "can't create rename file"
+.TP
+.B "can't create subdir"
+.PD
+A child process had problems creating or checking the contents of its
+test directory. This is usually due to a permission problem (for example
+the test directory was created by a different user) or a full filesystem.
+.TP
+.PD 0
+.B "bad mix format: unexpected EOF after 'nfs:'"
+.TP
+.B "bad mix format: can't find 'calls' value"
+.TP
+.B "bad mix format: unexpected EOF after 'calls'"
+.TP
+.B "bad mix format: can't find %d op values"
+.TP
+.B "bad mix format: unexpected EOF"
+.PD
+A problem occurred while parsing the
+.B mix
+file. The expected format of the file is the same as the output of
+the
+.BR nfsstat (8C)
+command when run with the "\-s" option.
+.TP
+.B "op failed: "
+One of the internal pseudo\-NFS operations failed. The name of the operation,
+e.g. read, write, lookup, will be printed along with an indication of the
+nature of the failure.
+.TP
+.B "select failed"
+The select system call returned an unexpected error.
+.SH BUGS
+.LP
+Running
+.B nhfsstone
+on a non\-NFS filesystem can cause the program to run forever because it
+uses the kernel NFS statistics to determine when enough calls have been made.
+.LP
+.B Nhfsstone
+uses many file descriptors. The kernel on the client may
+have to be reconfigured to increase the number of available file table entries.
+.LP
+Shell scripts that used
+.B nhfsstone
+will have to catch and ignore SIGUSR1 (see
+.BR signal (3)).
+This signal is
+used to synchronize the test processes. If the signal is not caught
+the shell that is running the script will be killed.
+.SH FILES
+.PD 0
+.TP 20
+.B /vmunix
+system namelist
+.TP
+.B /dev/kmem
+kernel virtual memory
+.TP
+.B ./testdir*
+per process test directory
+.TP
+.B /tmp/nhfsstone%d
+process synchronization log file
+.PD
diff --git a/utils/nhfsstone/nhfsstone.c b/utils/nhfsstone/nhfsstone.c
new file mode 100644
index 0000000..034ba79
--- /dev/null
+++ b/utils/nhfsstone/nhfsstone.c
@@ -0,0 +1,1798 @@
+#ifndef lint
+static char sccsid[] = "@(#)nhfsstone.c 1.22 90/05/08 Copyright (c) 1990, Legato Systems Inc";
+#endif
+
+/*
+ * Copyright (c) 1990 Legato Systems Inc.
+ *
+ * See DISCLAIMER file for restrictions
+ *
+ * Ported to Linux by Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/vfs.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#ifdef BSD
+#include <sys/dir.h>
+#define dirent direct
+#else
+#include <dirent.h>
+#endif
+#include <signal.h>
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+/*
+ * Usage: nhfsstone [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs]
+ * [-m mixfile] [dir]...
+ *
+ * Generates an artifical NFS client load based on a given mix of
+ * operations.
+ *
+ * Strategy: loop for some number of NFS calls doing a random sleep
+ * followed by a call to one of the op generator routines. The routines
+ * are called based on a weighting factor determined by the difference
+ * between the current ops percentages (derived from kernel NFS stats)
+ * and a set of default percentages or a mix supplied by the caller.
+ *
+ * The generator routines try very hard to guess how many NFS operations
+ * they are generating so that the calling routine can keep a running
+ * estimate of the number of calls and the mix to avoid having to get
+ * the NFS statistics from the kernel too often.
+ *
+ * The operations are done in a directory that has a set of file names
+ * that are long enough that they won't be cached by the name cache
+ * in the kernel. The "lookup" operation steps through the names and
+ * creates a file if that name does not exist, or closes and reopens it
+ * if it does. This generates a table of open file descriptors. Most of the
+ * other operations are done on random descriptors in the table. The "getattr"
+ * operation tries to avoid the kernel attribute cache by doing "fstat"
+ * system calls on random descriptors in the table. There must be enough
+ * files in the directory so that, on average, the getattr operation hits
+ * any file less often than once each 6 seconds (the default timeout for
+ * the attributes cache).
+ *
+ * The parent process starts children to do the real work of generating load.
+ * The parent coordinates them so that they all start at the same time, and
+ * collects statistics from them when they are done. To coordinate the
+ * start up, the parent waits for each child to write one byte into
+ * a common log file (opened in append mode to avoid overwriting).
+ * After they write a byte the children pause, and the parent send SIGUSR1
+ * when it has heard from all of the kids. The children write their statistics
+ * into the same common log file and the parent reads and accumulates the
+ * statics and prints them out.
+ *
+ * This code will only compile and run on 4.X BSD based systems.
+ */
+
+#define DEFAULT_LOAD 30 /* default calls per sec */
+#define DEFAULT_CALLS 5000 /* default number of calls */
+#define NFILES 40 /* number of test files/dir */
+#define BUFSIZE 8192 /* block size for read and write */
+#define MAXFILESIZE 32 /* size, in blocks, of large file */
+#define SAMPLETIME 5 /* secs between samples of NFS stats */
+#define NPROCS 7 /* number of children to run */
+
+
+/*
+ * The names of NFS operations
+ */
+char *Opnames[] = {
+ "null", "getattr", "setattr", "root", "lookup", "readlink", "read",
+ "wrcache", "write", "create", "remove", "rename", "link", "symlink",
+ "mkdir", "rmdir", "readdir", "fsstat",
+};
+
+/*
+ * NFS operation numbers
+ *
+ * Used to index the Opnames, Mix and statistics arrays.
+ */
+#define NOPS 18 /* number of NFS ops */
+#define NULLCALL 0
+#define GETATTR 1
+#define SETATTR 2
+#define ROOT 3
+#define LOOKUP 4
+#define READLINK 5
+#define READ 6
+#define WRCACHE 7
+#define WRITE 8
+#define CREATE 9
+#define REMOVE 10
+#define RENAME 11
+#define LINK 12
+#define SYMLINK 13
+#define MKDIR 14
+#define RMDIR 15
+#define READDIR 16
+#define FSSTAT 17
+
+/*
+ * Operations counts
+ */
+struct count {
+ int total;
+ int calls[NOPS];
+};
+
+/*
+ * Software development mix for server with about 50/50 mix of
+ * diskless and diskful clients running SunOS 4.0.
+ */
+int Mix[NOPS] = {
+ 0, /* null */
+ 13, /* getattr */
+ 1, /* setattr */
+ 0, /* root */
+ 34, /* lookup */
+ 8, /* readlink */
+ 22, /* read */
+ 0, /* wrcache */
+ 15, /* write */
+ 2, /* create */
+ 1, /* remove */
+ 0, /* rename */
+ 0, /* link */
+ 0, /* symlink */
+ 0, /* mkdir */
+ 0, /* rmdir */
+ 3, /* readdir */
+ 1, /* fsstat */
+};
+
+/* Prototype decls */
+int setmix(FILE *fp);
+void usage(void);
+void init_logfile(void);
+void init_counters(void);
+void get_delta(struct count *start, struct count *cur);
+void init_testdir(int dirnum, char *parentdir);
+void do_op(int rpct);
+void op(int opnum);
+void nextfile(void);
+int createfile(void);
+int openfile(void);
+int writefile(void);
+void collect_counters(void);
+int check_counters(void);
+void print(void);
+void msec_sleep(int msecs);
+void get_opct(struct count *count);
+int substr(char *sp, char *subsp);
+int check_access(struct stat statb);
+void error(char *str);
+
+/*
+ * NFS operations generator routines
+ */
+int op_null();
+int op_getattr();
+int op_setattr();
+int op_root();
+int op_lookup();
+int op_readlink();
+int op_read();
+int op_wrcache();
+int op_write();
+int op_create();
+int op_remove();
+int op_rename();
+int op_link();
+int op_symlink();
+int op_mkdir();
+int op_rmdir();
+int op_readdir();
+int op_fsstat();
+
+/*
+ * Operations generator vector
+ */
+struct op_vect {
+ int (*funct)(); /* op */
+} Op_vect[NOPS] = {
+ { op_null },
+ { op_getattr },
+ { op_setattr },
+ { op_root },
+ { op_lookup },
+ { op_readlink },
+ { op_read },
+ { op_wrcache },
+ { op_write },
+ { op_create },
+ { op_remove },
+ { op_rename },
+ { op_link },
+ { op_symlink },
+ { op_mkdir },
+ { op_rmdir },
+ { op_readdir },
+ { op_fsstat },
+};
+
+/*
+ * Name sub-strings
+ */
+#define DIRSTR "dir" /* directory */
+#define SYMSTR "sym" /* symbolic link */
+#define LINSTR "lin" /* hard link */
+
+struct timeval Optime[NOPS+1]; /* cumulative running time for ops */
+struct count Curct; /* total number ops called */
+int Openfd[NFILES]; /* open file descriptors */
+int Curnum; /* current file number */
+int Symnum; /* current symlink file number */
+int Linknum; /* current link file number */
+int Dirnum; /* current directory number */
+DIR *Testdir; /* my test directory */
+char Testdirname[MAXNAMLEN*2]; /* my test directory name */
+char Curname[MAXNAMLEN]; /* current file name */
+char Dirname[MAXNAMLEN]; /* current directory name */
+char Symname[MAXNAMLEN]; /* symlink file name */
+char Linkname[MAXNAMLEN]; /* link file name */
+char *Otherspec = "%s/%03d"; /* sprintf spec for other names */
+char *Rename1 = "rename1"; /* first name of rename pair */
+char *Rename2 = "rename2"; /* second name of rename pair */
+char *Symlinkpath = "./symlinknamelongstuff";
+ /* symlink file data */
+char *Myname; /* name program invoked under */
+char Namebuf[MAXNAMLEN]; /* unique name for this program */
+int Log; /* synchronization log */
+char Logname[MAXNAMLEN]; /* synchronization log name */
+int Kmem; /* /dev/kmem file descriptor */
+off_t Statoffset; /* offset to op count in NFS stats */
+int Nprocs; /* sub-processes started */
+int Verbose; /* print more info */
+int Testop = -1; /* operation to test */
+int Saveerrno; /* place to save errno */
+
+#define subtime(t1, t2) {if ((t1.tv_usec -= t2.tv_usec) >= 1000000) {\
+ t1.tv_sec += (t1.tv_usec / 1000000); \
+ t1.tv_usec %= 1000000; \
+ } else if (t1.tv_usec < 0) { \
+ t1.tv_usec += 1000000; \
+ t1.tv_sec--; \
+ } \
+ t1.tv_sec -= t2.tv_sec; \
+ }
+
+#define addtime(t1, t2) {if ((t1.tv_usec += t2.tv_usec) >= 1000000) {\
+ t1.tv_sec += (t1.tv_usec / 1000000); \
+ t1.tv_usec %= 1000000; \
+ } else if (t1.tv_usec < 0) { \
+ t1.tv_usec += 1000000; \
+ t1.tv_sec--; \
+ } \
+ t1.tv_sec += t2.tv_sec; \
+ }
+
+/*
+ * Used to catch the parent's "start" signal
+ */
+void
+startup()
+{
+
+ return;
+}
+
+/*
+ * Clean up and exit
+ */
+void
+cleanup()
+{
+
+ (void) unlink(Logname);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int runtime; /* length of run, in seconds */
+ int load; /* load factor, in client loads */
+ int ncalls; /* total number of calls to make */
+ int avgmspc; /* average millisec per call */
+ int mspc; /* millisec per call */
+ int wantcalls; /* ncalls that should have happend by now */
+ int pid; /* process id */
+ int delay; /* msecs since last checked current time */
+ int randnum; /* a random number */
+ int oldmask; /* saved signal mask */
+ int sampletime; /* secs between reading kernel stats */
+ char *opts; /* option parsing */
+ int pct;
+ int procnum;
+ FILE *fp;
+ struct timeval curtime;
+ struct timeval starttime;
+ struct count startct;
+ struct stat statb;
+ char workdir[MAXPATHLEN];
+ char *getwd();
+
+ Myname = argv[0];
+
+ argc--;
+ argv++;
+
+ load = DEFAULT_LOAD;
+ ncalls = 0;
+ runtime = 0;
+ Nprocs = NPROCS;
+ pid = 0;
+
+ (void) umask(0);
+
+ /*
+ * Parse options
+ */
+ while (argc && **argv == '-') {
+ opts = &argv[0][1];
+ while (*opts) {
+ switch (*opts) {
+
+ case 'c':
+ /*
+ * Set number of calls
+ */
+ if (!isdigit(argv[1][0])) {
+ (void) fprintf(stderr,
+ "%s: illegal calls value %s\n",
+ Myname, argv[1]);
+ exit(1);
+ }
+ ncalls = atoi(argv[1]);
+ argv++;
+ argc--;
+ break;
+
+ case 'l':
+ /*
+ * Set load
+ */
+ if (!isdigit(argv[1][0])) {
+ (void) fprintf(stderr,
+ "%s: illegal load value %s\n",
+ Myname, argv[1]);
+ exit(1);
+ }
+ load = atoi(argv[1]);
+ argv++;
+ argc--;
+ break;
+
+ case 'm':
+ /*
+ * Set mix from a file
+ */
+ if ((fp = fopen(argv[1], "r")) == NULL) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: bad mix file", Myname);
+ errno = Saveerrno;
+ perror("");
+ exit(1);
+ }
+ if (setmix(fp) < 0) {
+ exit(1);
+ }
+ (void) fclose(fp);
+ argv++;
+ argc--;
+ break;
+
+ case 'p':
+ /*
+ * Set number of child processes
+ */
+ if (!isdigit(argv[1][0])) {
+ (void) fprintf(stderr,
+ "%s: illegal procs value %s\n",
+ Myname, argv[1]);
+ exit(1);
+ }
+ Nprocs = atoi(argv[1]);
+ argv++;
+ argc--;
+ break;
+
+ case 'T':
+ /*
+ * Set test mode, number following is opnum
+ */
+ if (!isdigit(argv[1][0])) {
+ (void) fprintf(stderr,
+ "%s: illegal test value %s\n",
+ Myname, argv[1]);
+ exit(1);
+ }
+ Testop = atoi(argv[1]);
+ if (Testop >= NOPS) {
+ (void) fprintf(stderr,
+ "%s: illegal test value %d\n",
+ Myname, Testop);
+ exit(1);
+ }
+ argv++;
+ argc--;
+ break;
+
+ case 't':
+ /*
+ * Set running time
+ */
+ if (!isdigit(argv[1][0])) {
+ (void) fprintf(stderr,
+ "%s: illegal time value %s\n",
+ Myname, argv[1]);
+ exit(1);
+ }
+ runtime = atoi(argv[1]);
+ argv++;
+ argc--;
+ break;
+
+ case 'v':
+ /*
+ * Set verbose mode
+ */
+ Verbose++;
+ break;
+
+ default:
+ usage();
+ exit(1);
+
+ }
+ opts++;
+ }
+ argv++;
+ argc--;
+ }
+
+ init_logfile(); /* Set up synchronizatin log file */
+
+ if (getcwd(workdir, sizeof(workdir)) == (char *) 0) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't find current directory ", Myname);
+ errno = Saveerrno;
+ perror("");
+ exit(1);
+ }
+
+ (void) signal(SIGINT, cleanup);
+ (void) signal(SIGUSR1, startup);
+ oldmask = sigblock(sigmask(SIGUSR1));
+
+ if (ncalls == 0) {
+ if (runtime == 0) {
+ ncalls = DEFAULT_CALLS;
+ } else {
+ ncalls = runtime * load;
+ }
+ }
+ avgmspc = Nprocs * 1000 / load;
+
+ /*
+ * Fork kids
+ */
+ for (procnum = 0; procnum < Nprocs; procnum++) {
+ if ((pid = fork()) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr, "%s: can't fork ", Myname);
+ errno = Saveerrno;
+ perror("");
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ /*
+ * Kids go initialize
+ */
+ if (pid == 0) {
+ break;
+ }
+ }
+
+ /*
+ * Parent: wait for kids to get ready, start them, wait for them to
+ * finish, read and accumulate results.
+ */
+ if (pid != 0) {
+ /*
+ * wait for kids to initialize
+ */
+ do {
+ sleep(1);
+ if (fstat(Log, &statb) == -1) {
+ (void) fprintf(stderr, "%s: can't stat log %s",
+ Myname, Logname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ } while (statb.st_size != Nprocs);
+
+ if (ftruncate(Log, 0L) == -1) {
+ (void) fprintf(stderr, "%s: can't truncate log %s",
+ Myname, Logname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ sync();
+ sleep(3);
+
+ /*
+ * Be sure there isn't something else going on
+ */
+ get_opct(&startct);
+ msec_sleep(2000);
+ get_delta(&startct, &Curct);
+ if (Curct.total > 20) {
+ (void) fprintf(stderr,
+ "%s: too much background activity (%d calls/sec)\n",
+ Myname, Curct.total);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ /*
+ * get starting stats
+ */
+ get_opct(&startct);
+
+ /*
+ * Start kids
+ */
+ (void) kill(0, SIGUSR1);
+
+ /*
+ * Kids started, wait for first one to finish, signal the
+ * rest and wait for them to finish.
+ */
+ if (wait((union wait *) 0) != -1) {
+ (void) kill(0, SIGUSR1);
+ while (wait((union wait *) 0) != -1)
+ /* nothing */;
+ }
+
+ /*
+ * Initialize and sum up counters
+ */
+ init_counters();
+ get_delta(&startct, &Curct);
+ collect_counters();
+ if (check_counters() == -1) {
+ Verbose = 1;
+ }
+ print();
+
+ (void) close(Log);
+ (void) unlink(Logname);
+
+ exit(0);
+ }
+
+ /*
+ * Children: initialize, then notify parent through log file,
+ * wait to get signal, beat the snot out of the server, write
+ * stats to the log file, and exit.
+ */
+
+ /*
+ * Change my name for error logging
+ */
+ (void) sprintf(Namebuf, "%s%d", Myname, procnum);
+ Myname = Namebuf;
+
+ /*
+ * Initialize and cd to test directory
+ */
+ if (argc != 0) {
+ init_testdir(procnum, argv[procnum % argc]);
+ } else {
+ init_testdir(procnum, ".");
+ }
+ if ((Testdir = opendir(".")) == NULL) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't open test directory ", Myname);
+ errno = Saveerrno;
+ perror(Testdirname);
+ exit(1);
+ }
+
+ init_counters();
+ srandom(procnum+1);
+
+ /*
+ * Tell parent I'm ready then wait for go ahead
+ */
+ if (write(Log, " ", 1) != 1) {
+ (void) fprintf(stderr, "%s: can't write sync file %s",
+ Myname, Logname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ sigpause(oldmask);
+
+ /*
+ * Initialize counters
+ */
+ get_opct(&startct);
+ (void) gettimeofday(&starttime, (struct timezone *)NULL);
+ sampletime = starttime.tv_sec + ((int) random()) % (2 * SAMPLETIME);
+ curtime = starttime;
+
+ /*
+ * Do pseudo NFS operations and adapt to dynamic changes in load
+ * by adjusting the sleep time between operations based on the
+ * number of calls that should have occured since starttime and
+ * the number that have actually occured. A delay is used to avoid
+ * doing gettimeofday calls too often, and a sampletime is
+ * used to avoid reading kernel NFS stats too often.
+ * If parent interrupts, get out and clean up.
+ */
+ delay = 0;
+ mspc = avgmspc;
+ for (;;) {
+ randnum = (int) random();
+ if (mspc > 0) {
+ msec_sleep(randnum % (mspc << 1));
+ }
+
+ /*
+ * Do the NFS operation
+ * We use a random number from 0-199 to avoid starvation
+ * of the operations at the end of the mix.
+ */
+ do_op(randnum % 200);
+
+ /*
+ * Do a gettimeofday call only once per second
+ */
+ delay += mspc;
+ if (delay > 1000 || Curct.total >= ncalls) {
+ delay = 0;
+ (void) gettimeofday(&curtime, (struct timezone *)NULL);
+
+ /*
+ * If sample time is up, check the kernel stats
+ * and adjust our parameters to either catch up or
+ * slow down.
+ */
+ if (curtime.tv_sec > sampletime ||
+ Curct.total >= ncalls) {
+ sampletime = curtime.tv_sec + SAMPLETIME;
+ get_delta(&startct, &Curct);
+ if (Curct.total >= ncalls) {
+ break;
+ }
+ wantcalls =
+ ((curtime.tv_sec - starttime.tv_sec) * 1000
+ +(curtime.tv_usec-starttime.tv_usec) / 1000)
+ * Nprocs / avgmspc;
+ pct = 1000 * (Curct.total - wantcalls) / ncalls;
+ mspc = avgmspc + avgmspc * pct / 20;
+ if (mspc <= 0) {
+ /*
+ * mspc must be positive or we will
+ * never advance time.
+ */
+ mspc = 10;
+ }
+ }
+ }
+ }
+
+ /*
+ * Store total time in last slot of counts array
+ */
+ Optime[NOPS].tv_sec = curtime.tv_sec - starttime.tv_sec;
+ Optime[NOPS].tv_usec = curtime.tv_usec - starttime.tv_usec;
+
+ /*
+ * write stats to log file (append mode)
+ */
+ if (write(Log, (char *)Optime, sizeof (Optime)) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr, "%s: can't write log ", Myname);
+ errno = Saveerrno;
+ perror("");
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ (void) close(Log);
+
+ exit(0);
+}
+
+/*
+ * Initialize test directory
+ *
+ * If the directory already exists, check to see that all of the
+ * files exist and we can write them. If directory doesn't exist
+ * create it and fill it using the LOOKUP and WRITE ops.
+ * Chdir to the directory.
+ */
+void
+init_testdir(int dirnum, char *parentdir)
+{
+ int i;
+ int fd;
+ char cmd[256];
+ struct stat statb;
+
+ (void) sprintf(Testdirname, "%s/testdir%d", parentdir, dirnum);
+ if (stat(Testdirname, &statb) == -1) {
+ if (mkdir(Testdirname, 0777) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't create test directory ", Myname);
+ errno = Saveerrno;
+ perror(Testdirname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ if (chdir(Testdirname) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't cd to test directory ", Myname);
+ errno = Saveerrno;
+ perror(Testdirname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ /*
+ * create some files with long names and average size
+ */
+ for (i = 0; i < NFILES; i++) {
+ nextfile();
+ (void) createfile();
+ if (Openfd[Curnum] == 0 || writefile() == 0) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't create test file '%s'\n",
+ Myname, Curname);
+ errno = Saveerrno;
+ perror(Testdirname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ }
+ } else {
+ if (chdir(Testdirname) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't cd to test directory ", Myname);
+ errno = Saveerrno;
+ perror(Testdirname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ /*
+ * Verify that we can read and write the test dir
+ */
+ if (check_access(statb) == -1) {
+ (void) fprintf(stderr,
+ "%s: wrong permissions on test dir %s\n",
+ Myname, Testdirname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ /*
+ * Verify that we can read and write all the files
+ */
+ for (i = 0; i < NFILES; i++) {
+ nextfile();
+ if (stat(Curname, &statb) == -1 || statb.st_size == 0) {
+ /*
+ * File doesn't exist or is 0 size
+ */
+ (void) createfile();
+ if (Openfd[Curnum] == 0 || writefile() == 0) {
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ } else if (check_access(statb) == -1) {
+ /*
+ * should try to remove and recreate it
+ */
+ (void) fprintf(stderr,
+ "%s: wrong permissions on testfile %s\n",
+ Myname, Curname);
+ (void) kill(0, SIGINT);
+ exit(1);
+ } else if (Openfd[Curnum] == 0) {
+ (void) openfile();
+ if (Openfd[Curnum] == 0) {
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ }
+ }
+ }
+
+ /*
+ * Start with Rename1 and no Rename2 so the
+ * rename op can ping pong back and forth.
+ */
+ (void) unlink(Rename2);
+ if ((fd = open(Rename1, O_CREAT|O_TRUNC|O_RDWR, 0666)) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr, "%s: can't create rename file ", Myname);
+ errno = Saveerrno;
+ perror(Rename1);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ /*
+ * Remove and recreate the test sub-directories
+ * for mkdir symlink and hard link.
+ */
+ (void) sprintf(cmd, "rm -rf %s %s %s", DIRSTR, SYMSTR, LINSTR);
+ if (system(cmd) != 0) {
+ (void) fprintf(stderr, "%s: can't %s\n", Myname, cmd);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ if (mkdir(DIRSTR, 0777) == -1) {
+ (void) fprintf(stderr,
+ "%s: can't create subdir %s\n", Myname, DIRSTR);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ if (mkdir(SYMSTR, 0777) == -1) {
+ (void) fprintf(stderr,
+ "%s: can't create subdir %s\n", Myname, SYMSTR);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+ op(SYMLINK);
+
+ if (mkdir(LINSTR, 0777) == -1) {
+ (void) fprintf(stderr, "%s: can't create subdir %s\n", Myname,
+ LINSTR);
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ (void) close(fd);
+}
+
+/*
+ * The routines below attempt to do over-the-wire operations.
+ * Each op tries to cause one or more of a particular
+ * NFS operation to go over the wire. OPs return the number
+ * of OTW calls they think they have generated.
+ *
+ * An array of open file descriptors is kept for the files in each
+ * test directory. The open fd's are used to get access to the files
+ * without generating lookups. An fd value of 0 mean the corresponding
+ * file name is closed. Ops that need a name use Curname.
+ */
+
+/*
+ * Call an op based on a random number and the current
+ * op calling weights. Op weights are derived from the
+ * mix percentage and the current NFS stats mix percentage.
+ */
+void
+do_op(int rpct)
+{
+ int opnum;
+ int weight;
+ int oppct;
+
+ if (Testop != -1) {
+ nextfile();
+ op(Testop);
+ return;
+ }
+ for (opnum = rpct % NOPS; rpct >= 0; opnum = (opnum + 1) % NOPS) {
+ if (Curct.total) {
+ oppct = (Curct.calls[opnum] * 100) / Curct.total;
+ } else {
+ oppct = 0;
+ }
+ /*
+ * Weight is mix percent - (how far off we are * fudge)
+ * fudge factor is required because some ops (read, write)
+ * generate many NFS calls for a single op call
+ */
+ weight = Mix[opnum] - ((oppct - Mix[opnum]) << 4);
+ if (weight <= 0) {
+ continue;
+ }
+ rpct -= weight;
+ if (rpct < 0) {
+ if (opnum == RMDIR && Dirnum == 0) {
+ op(MKDIR);
+ } else if (opnum != CREATE && opnum != LOOKUP &&
+ opnum != REMOVE) {
+ nextfile();
+ }
+ op(opnum);
+ if (Openfd[Curnum] == 0) {
+ op(CREATE);
+#ifdef XXX
+ op(WRITE);
+#endif /* XXX */
+ }
+ return;
+ }
+ }
+}
+
+/*
+ * Call an op generator and keep track of its running time
+ */
+void
+op(int opnum)
+{
+ struct timeval start;
+ struct timeval stop;
+ int nops;
+
+ (void) gettimeofday(&start, (struct timezone *)NULL);
+ nops = (*Op_vect[opnum].funct)();
+ (void) gettimeofday(&stop, (struct timezone *)NULL);
+ stop.tv_sec -= start.tv_sec;
+ stop.tv_usec -= start.tv_usec;
+
+#ifdef SUNOS4
+ /*
+ * SunOS 4.0 does a lookup and a getattr on each open
+ * so we have to account for that in the getattr op
+ */
+ if (opnum == GETATTR && nops == 2) {
+ nops = 1;
+ stop.tv_sec /= 2;
+ stop.tv_usec /= 2;
+ Curct.total += Nprocs;
+ Curct.calls[LOOKUP] += Nprocs;
+ addtime(Optime[LOOKUP], stop);
+ }
+#endif
+
+ nops *= Nprocs;
+ Curct.total += nops;
+ Curct.calls[opnum] += nops;
+ addtime(Optime[opnum], stop);
+}
+
+/*
+ * Advance file number (Curnum) and name (Curname)
+ */
+void
+nextfile(void)
+{
+ static char *numpart = NULL;
+ int num;
+
+ Curnum = (Curnum + 1) % NFILES;
+ if (numpart == NULL) {
+ (void) sprintf(Curname, "%03dabcdefghijklmn", Curnum);
+ numpart = Curname;
+ } else {
+ num = Curnum;
+ numpart[0] = '0' + num / 100;
+ num %= 100;
+ numpart[1] = '0' + num / 10;
+ num %= 10;
+ numpart[2] = '0' + num;
+ }
+}
+
+int
+createfile(void)
+{
+ int ret;
+ int fd;
+
+ ret = 0;
+ fd = Openfd[Curnum];
+
+ if ((fd && close(fd) == -1) ||
+ (fd = open(Curname, O_CREAT|O_RDWR|O_TRUNC, 0666)) == -1) {
+ fd = 0;
+ ret = -1;
+ error("create");
+ }
+ Openfd[Curnum] = fd;
+ return (ret);
+}
+
+int
+openfile(void)
+{
+ int ret;
+ int fd;
+
+ ret = 0;
+ fd = Openfd[Curnum];
+ if (fd == 0 && (fd = open(Curname, O_RDWR, 0666)) == -1) {
+ fd = 0;
+ ret = -1;
+ error("open");
+ }
+ Openfd[Curnum] = fd;
+ return (ret);
+}
+
+int
+writefile(void)
+{
+ int fd;
+ int wrote;
+ int bufs;
+ int size;
+ int randnum;
+ char buf[BUFSIZE];
+
+ fd = Openfd[Curnum];
+
+ if (lseek(fd, 0L, 0) == (off_t) -1) {
+ error("write: lseek");
+ return (-1);
+ }
+
+ randnum = (int) random();
+ bufs = randnum % 100; /* using this for distribution desired */
+ /*
+ * Attempt to create a distribution of file sizes
+ * to reflect reality. Most files are small,
+ * but there are a few files that are very large.
+ *
+ * The sprite paper (USENIX 198?) claims :
+ * 50% of all files are < 2.5K
+ * 80% of all file accesses are to files < 10K
+ * 40% of all file I/O is to files > 25K
+ *
+ * static examination of the files in our file system
+ * seems to support the claim that 50% of all files are
+ * smaller than 2.5K
+ */
+ if (bufs < 50) {
+ bufs = (randnum % 3) + 1;
+ size = 1024;
+ } else if (bufs < 97) {
+ bufs = (randnum % 6) + 1;
+ size = BUFSIZE;
+ } else {
+ bufs = MAXFILESIZE;
+ size = BUFSIZE;
+ }
+
+ for (wrote = 0; wrote < bufs; wrote++) {
+ if (write(fd, buf, size) == -1) {
+ error("write");
+ break;
+ }
+ }
+
+ return (wrote);
+}
+
+int
+op_null(void)
+{
+
+ return (1);
+}
+
+
+/*
+ * Generate a getattr call by fstat'ing the current file
+ * or by closing and re-opening it. This helps to keep the
+ * attribute cache cold.
+ */
+int
+op_getattr(void)
+{
+ struct stat statb;
+
+ if ((random() % 2) == 0) {
+ (void) close(Openfd[Curnum]);
+ Openfd[Curnum] = 0;
+ if (openfile() == -1) {
+ return (0);
+ }
+ return (2);
+ }
+ if (fstat(Openfd[Curnum], &statb) == -1) {
+ error("getattr");
+ }
+ return (1);
+}
+
+
+int op_setattr(void)
+{
+
+ if (fchmod(Openfd[Curnum], 0666) == -1) {
+ error("setattr");
+ }
+ return (1);
+}
+
+
+int op_root(void)
+{
+
+ error("root");
+ return (0);
+}
+
+
+/*
+ * Generate a lookup by stat'ing the current name.
+ */
+int op_lookup(void)
+{
+ struct stat statb;
+
+ if (stat(Curname, &statb) == -1) {
+ error("lookup");
+ }
+ return (1);
+}
+
+
+int op_read(void)
+{
+ int got;
+ int bufs;
+ int fd;
+ char buf[BUFSIZE];
+
+ bufs = 0;
+ fd = Openfd[Curnum];
+
+ if (lseek(fd, 0L, 0) == (off_t) -1) {
+ error("read: lseek");
+ return (0);
+ }
+
+ while ((got = read(fd, buf, sizeof (buf))) > 0) {
+ bufs++;
+ }
+
+ if (got == -1) {
+ error("read");
+ } else {
+ bufs++; /* did one extra read to find EOF */
+ }
+ return (bufs);
+}
+
+
+int op_wrcache(void)
+{
+ error("wrcache");
+ return 0;
+}
+
+
+int op_write(void)
+{
+ int bufs;
+
+ bufs = writefile();
+ if (bufs == 0) {
+ return (0);
+ }
+ (void) fsync(Openfd[Curnum]);
+
+ return (bufs + 2);
+}
+
+
+int op_create(void)
+{
+
+ if (createfile() == -1) {
+ return (0);
+ }
+ return (1);
+}
+
+
+int op_remove(void)
+{
+ int fd;
+ int got;
+
+ if (Linknum > 0) {
+ got = unlink(Linkname);
+ Linknum--;
+ (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
+ } else if (Symnum > 1) {
+ got = unlink(Symname);
+ Symnum--;
+ (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
+ } else {
+ fd = Openfd[Curnum];
+
+ if (fd && (close(fd) == -1)) {
+ error("remove: close");
+ }
+ Openfd[Curnum] = 0;
+ got = unlink(Curname);
+ }
+ if (got == -1) {
+ error("remove");
+ }
+ return (1);
+}
+
+
+int toggle = 0;
+
+int op_rename(void)
+{
+ int got;
+
+ if (toggle++ & 01) {
+ got = rename(Rename2, Rename1);
+ } else {
+ got = rename(Rename1, Rename2);
+ }
+ if (got == -1) {
+ error("rename");
+ }
+ return (1);
+}
+
+
+int op_link(void)
+{
+
+ Linknum++;
+ (void) sprintf(Linkname, Otherspec, LINSTR, Linknum);
+ if (link(Curname, Linkname) == -1) {
+ error("link");
+ }
+ return (1);
+}
+
+
+int op_readlink(void)
+{
+ char buf[MAXPATHLEN];
+
+ if (Symnum == 0) {
+ error("readlink");
+ return (0);
+ }
+ if (readlink(Symname, buf, sizeof (buf)) == -1) {
+ error("readlink");
+ }
+ return (1);
+}
+
+
+int op_symlink(void)
+{
+
+ Symnum++;
+ (void) sprintf(Symname, Otherspec, SYMSTR, Symnum);
+ if (symlink(Symlinkpath, Symname) == -1) {
+ error("symlink");
+ }
+ return (1);
+}
+
+
+int op_mkdir(void)
+{
+
+ Dirnum++;
+ (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
+ if (mkdir(Dirname, 0777) == -1) {
+ error("mkdir");
+ }
+ return (1);
+}
+
+
+int op_rmdir(void)
+{
+
+ if (Dirnum == 0) {
+ error("rmdir");
+ return (0);
+ }
+ if (rmdir(Dirname) == -1) {
+ error("rmdir");
+ }
+ Dirnum--;
+ (void) sprintf(Dirname, Otherspec, DIRSTR, Dirnum);
+ return (1);
+}
+
+
+int op_readdir(void)
+{
+
+ rewinddir(Testdir);
+ while (readdir(Testdir) != (struct dirent *)NULL)
+ /* nothing */;
+ return (1);
+}
+
+
+int op_fsstat(void)
+{
+ struct statfs statfsb;
+
+ if (statfs(".", &statfsb) == -1) {
+ error("statfs");
+ }
+ return (1);
+}
+
+
+/*
+ * Utility routines
+ */
+
+/*
+ * Read counter arrays out of log file and accumulate them in "Optime"
+ */
+void
+collect_counters(void)
+{
+ int i;
+ int j;
+
+ (void) lseek(Log, 0L, 0);
+
+ for (i = 0; i < Nprocs; i++) {
+ struct timeval buf[NOPS+1];
+
+ if (read(Log, (char *)buf, sizeof (buf)) == -1) {
+ Saveerrno = errno;
+ (void) fprintf(stderr, "%s: can't read log ", Myname);
+ errno = Saveerrno;
+ perror("");
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+
+ for (j = 0; j < NOPS+1; j++) {
+ addtime(Optime[j], buf[j]);
+ }
+ }
+}
+
+/*
+ * Check consistance of results
+ */
+int
+check_counters(void)
+{
+ int i;
+ int mixdiff;
+ int got;
+ int want;
+
+ mixdiff = 0;
+ for (i = 0; i < NOPS; i++) {
+ got = Curct.calls[i] * 10000 / Curct.total;
+ want = Mix[i] * 100;
+ if (got > want) {
+ mixdiff += got - want;
+ } else {
+ mixdiff += want - got;
+ }
+ }
+ if (mixdiff > 1000) {
+ (void) fprintf(stdout,
+ "%s: INVALID RUN, mix generated is off by %d.%02d%%\n",
+ Myname, mixdiff / 100, mixdiff % 100);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Print results
+ */
+void
+print(void)
+{
+ int totalmsec;
+ int runtime;
+ int msec;
+ int i;
+
+ totalmsec = 0;
+ for (i = 0; i < NOPS; i++) {
+ totalmsec += Optime[i].tv_sec * 1000;
+ totalmsec += Optime[i].tv_usec / 1000;
+ }
+
+ if (Verbose) {
+ const char *format = sizeof (Optime[0].tv_sec) == sizeof (long)
+ ? "%-10s%3d%% %2d.%02d%% %6d %4ld.%02ld %4d.%02d %2d.%02d%%\n"
+ : "%-10s%3d%% %2d.%02d%% %6d %4d.%02d %4d.%02d %2d.%02d%%\n";
+ (void) fprintf(stdout,
+"op want got calls secs msec/call time %%\n");
+ for (i = 0; i < NOPS; i++) {
+ msec = Optime[i].tv_sec * 1000
+ + Optime[i].tv_usec / 1000;
+ (void) fprintf(stdout, format,
+ Opnames[i], Mix[i],
+ Curct.calls[i] * 100 / Curct.total,
+ (Curct.calls[i] * 100 % Curct.total)
+ * 100 / Curct.total,
+ Curct.calls[i],
+ Optime[i].tv_sec, Optime[i].tv_usec / 10000,
+ Curct.calls[i]
+ ? msec / Curct.calls[i]
+ : 0,
+ Curct.calls[i]
+ ? (msec % Curct.calls[i]) * 100 / Curct.calls[i]
+ : 0,
+ msec * 100 / totalmsec,
+ (msec * 100 % totalmsec) * 100 / totalmsec);
+ }
+ }
+
+ runtime = Optime[NOPS].tv_sec / Nprocs;
+ (void) fprintf(stdout,
+ "%d sec %d calls %d.%02d calls/sec %d.%02d msec/call\n",
+ runtime, Curct.total,
+ Curct.total / runtime,
+ ((Curct.total % runtime) * 100) / runtime,
+ totalmsec / Curct.total,
+ ((totalmsec % Curct.total) * 100) / Curct.total);
+}
+
+/*
+ * Use select to sleep for some number of milliseconds
+ * granularity is 20 msec
+ */
+void
+msec_sleep(int msecs)
+{
+ struct timeval sleeptime;
+
+ if (msecs < 20) {
+ return;
+ }
+ sleeptime.tv_sec = msecs / 1000;
+ sleeptime.tv_usec = (msecs % 1000) * 1000;
+
+ if (select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &sleeptime) == -1){
+ Saveerrno = errno;
+ (void) fprintf(stderr, "%s: select failed ", Myname);
+ errno = Saveerrno;
+ perror("");
+ (void) kill(0, SIGINT);
+ exit(1);
+ }
+}
+
+/*
+ * Open the synchronization file with append mode
+ */
+void
+init_logfile(void)
+{
+
+ (void) sprintf(Logname, "/tmp/nhfsstone%d", getpid());
+ if ((Log = open(Logname, O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0666)) == -1){
+ Saveerrno = errno;
+ (void) fprintf(stderr,
+ "%s: can't open log file %s ", Myname, Logname);
+ errno = Saveerrno;
+ perror("");
+ exit(1);
+ }
+}
+
+/*
+ * Zero counters
+ */
+void
+init_counters(void)
+{
+ int i;
+
+ Curct.total = 0;
+ for (i = 0; i < NOPS; i++) {
+ Curct.calls[i] = 0;
+ Optime[i].tv_sec = 0;
+ Optime[i].tv_usec = 0;
+ }
+ Optime[NOPS].tv_sec = 0;
+ Optime[NOPS].tv_usec = 0;
+}
+
+/*
+ * Set cur = cur - start
+ */
+void
+get_delta(struct count *start, struct count *cur)
+{
+ int i;
+
+ get_opct(cur);
+ cur->total -= start->total;
+ for (i = 0; i < NOPS; i++) {
+ cur->calls[i] -= start->calls[i];
+ }
+}
+
+/*
+ * Read kernel stats
+ */
+void
+get_opct(struct count *count)
+{
+ static FILE *fp = NULL;
+ char buffer[256];
+ int i;
+
+ if (fp == NULL && !(fp = fopen("/proc/net/rpc/nfs", "r"))) {
+ perror("/proc/net/rpc/nfs");
+ (void) kill(0, SIGINT);
+ exit(1);
+ } else {
+ fflush(fp);
+ rewind(fp);
+ }
+
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ char *sp, *line = buffer;
+
+ if ((sp = strchr(line, '\n')) != NULL)
+ *sp = '\0';
+ if (!(sp = strtok(line, " \t")) || strcmp(line, "proc2"))
+ continue;
+ if (!(sp = strtok(NULL, " \t")))
+ goto bummer;
+ count->total = 0;
+ for (i = 0; i < 18; i++) {
+ if (!(sp = strtok(NULL, " \t")))
+ goto bummer;
+ /* printf("call %d -> %s\n", i, sp); */
+ count->calls[i] = atoi(sp);
+ count->total += count->calls[i];
+ }
+ /* printf("total calls %d\n", count->total); */
+ break;
+ }
+
+ return;
+
+bummer:
+ fprintf(stderr, "parse error in /proc/net/rpc/nfs!\n");
+ kill(0, SIGINT);
+ exit(1);
+}
+
+#define LINELEN 128 /* max bytes/line in mix file */
+#define MIX_START 0
+#define MIX_DATALINE 1
+#define MIX_DONE 2
+#define MIX_FIRSTLINE 3
+
+/*
+ * Mix file parser.
+ * Assumes that the input file is in the same format as
+ * the output of the nfsstat(8) command.
+ *
+ * Uses a simple state transition to keep track of what to expect.
+ * Parsing is done a line at a time.
+ *
+ * State Input action New state
+ * MIX_START ".*nfs:.*" skip one line MIX_FIRSTLINE
+ * MIX_FIRSTLINE ".*[0-9]*.*" get ncalls MIX_DATALINE
+ * MIX_DATALINE "[0-9]* [0-9]*%"X6 get op counts MIX_DATALINE
+ * MIX_DATALINE "[0-9]* [0-9]*%"X4 get op counts MIX_DONE
+ * MIX_DONE EOF return
+ */
+int
+setmix(FILE *fp)
+{
+ int state;
+ int got;
+ int opnum;
+ int calls;
+ int len;
+ char line[LINELEN];
+
+ state = MIX_START;
+ opnum = 0;
+
+ while (state != MIX_DONE && fgets(line, LINELEN, fp)) {
+
+ switch (state) {
+
+ case MIX_START:
+ len = strlen(line);
+ if (len >= 4 && substr(line, "nfs:")) {
+ if (fgets(line, LINELEN, fp) == NULL) {
+ (void) fprintf(stderr,
+"%s: bad mix format: unexpected EOF after 'nfs:'\n", Myname);
+ return (-1);
+ }
+ state = MIX_FIRSTLINE;
+ }
+ break;
+
+ case MIX_FIRSTLINE:
+ got = sscanf(line, "%d", &calls);
+ if (got != 1) {
+ (void) fprintf(stderr,
+"%s: bad mix format: can't find 'calls' value %d\n", Myname, got);
+ return (-1);
+ }
+ if (fgets(line, LINELEN, fp) == NULL) {
+ (void) fprintf(stderr,
+"%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
+ return (-1);
+ }
+ state = MIX_DATALINE;
+ break;
+
+ case MIX_DATALINE:
+ got = sscanf(line,
+ "%d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%% %d %*d%%",
+ &Mix[opnum], &Mix[opnum+1], &Mix[opnum+2], &Mix[opnum+3],
+ &Mix[opnum+4], &Mix[opnum+5], &Mix[opnum+6]);
+ if (got == 4 && opnum == 14) {
+ /*
+ * looks like the last line
+ */
+ state = MIX_DONE;
+ } else if (got == 7) {
+ opnum += 7;
+ if (fgets(line, LINELEN, fp) == NULL) {
+ (void) fprintf(stderr,
+"%s: bad mix format: unexpected EOF after 'calls'\n", Myname);
+ return (-1);
+ }
+ } else {
+ (void) fprintf(stderr,
+"%s: bad mix format: can't find %d op values\n", Myname, got);
+ return (-1);
+ }
+ break;
+ default:
+ (void) fprintf(stderr,
+ "%s: unknown state %d\n", Myname, state);
+ return (-1);
+ }
+ }
+ if (state != MIX_DONE) {
+ (void) fprintf(stderr,
+ "%s: bad mix format: unexpected EOF\n", Myname);
+ return (-1);
+ }
+ for (opnum = 0; opnum < NOPS; opnum++) {
+ Mix[opnum] = Mix[opnum] * 100 / calls
+ + ((Mix[opnum] * 1000 / calls % 10) >= 5);
+ }
+ return (0);
+}
+
+/*
+ * return true if sp contains the substring subsp, false otherwise
+ */
+int
+substr(char *sp, char *subsp)
+{
+ int found;
+ int want;
+ char *s2;
+
+ if (sp == NULL || subsp == NULL) {
+ return (0);
+ }
+
+ want = strlen(subsp);
+
+ while (*sp != '\0') {
+ while (*sp != *subsp && *sp != '\0') {
+ sp++;
+ }
+ found = 0;
+ s2 = subsp;
+ while (*sp == *s2) {
+ sp++;
+ s2++;
+ found++;
+ }
+ if (found == want) {
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/*
+ * check to make sure that we have
+ * both read and write permissions
+ * for this file or directory.
+ */
+int
+check_access(struct stat statb)
+{
+ int gidsetlen;
+ gid_t gidset[NGROUPS];
+ int i;
+
+ if (statb.st_uid == getuid()) {
+ if ((statb.st_mode & 0200) && (statb.st_mode & 0400)) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ gidsetlen = NGROUPS;
+
+ if (getgroups(gidsetlen, gidset) == -1) {
+ perror("getgroups");
+ return -1;
+ }
+
+ for (i = 0; i < NGROUPS; i++) {
+ if (statb.st_gid == gidset[i]) {
+ if ((statb.st_mode & 020) && (statb.st_mode & 040)) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ if ((statb.st_mode & 02) && (statb.st_mode & 04)) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+void
+usage(void)
+{
+
+ (void) fprintf(stderr, "usage: %s [-v] [[-t secs] | [-c calls]] [-l load] [-p nprocs] [-m mixfile] [dir]...\n", Myname);
+}
+
+void
+error(char *str)
+{
+
+ Saveerrno = errno;
+ (void) fprintf(stderr, "%s: op failed: %s ", Myname, str);
+ errno = Saveerrno;
+ perror("");
+}
diff --git a/utils/rquotad/Makefile b/utils/rquotad/Makefile
new file mode 100644
index 0000000..1572655
--- /dev/null
+++ b/utils/rquotad/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for rpc.mountd
+#
+
+PROGRAM = rquotad
+PREFIX = rpc.
+OBJS = rquota_server.o rquota_svc.o rquota_xdr.o quotactl.o hasquota.o
+DEPLIBS =
+MAN8 = rquotad
+
+LIBS += -lnfs $(LIBBSD)
+
+include $(TOP)rules.mk
diff --git a/utils/rquotad/NEW b/utils/rquotad/NEW
new file mode 100644
index 0000000..40c6fd2
--- /dev/null
+++ b/utils/rquotad/NEW
@@ -0,0 +1,3 @@
+This is Marco van Wieringen's rpc.rquotad in quotas-1.70 from
+
+ftp://ftp.cistron.nl/pub/people/mvw/quota
diff --git a/utils/rquotad/README.okir b/utils/rquotad/README.okir
new file mode 100644
index 0000000..08938b9
--- /dev/null
+++ b/utils/rquotad/README.okir
@@ -0,0 +1,3 @@
+
+This is Marco van Wieringen's rpc.rquotad from quotas-1.55.
+
diff --git a/utils/rquotad/hasquota.c b/utils/rquotad/hasquota.c
new file mode 100644
index 0000000..008a93f
--- /dev/null
+++ b/utils/rquotad/hasquota.c
@@ -0,0 +1,72 @@
+/*
+ * QUOTA An implementation of the diskquota system for the LINUX
+ * operating system. QUOTA is implemented using the BSD systemcall
+ * interface as the means of communication with the user level.
+ * Should work for all filesystems because of integration into the
+ * VFS layer of the operating system.
+ * This is based on the Melbourne quota system wich uses both user and
+ * group quota files.
+ *
+ * Determines if a filesystem has quota enabled and how the quotafile
+ * is named.
+ *
+ * Version: $Id: hasquota.c,v 2.6 1996/11/17 16:59:46 mvw Exp mvw $
+ *
+ * Author: Marco van Wieringen <mvw@planets.elm.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/quota.h>
+#include <limits.h>
+#include <string.h>
+#include "mntent.h"
+#include "xmalloc.h"
+
+#undef min
+#define min(x,y) ((x) < (y)) ? (x) : (y)
+
+#define CORRECT_FSTYPE(type) \
+(!strcmp(type,MNTTYPE_EXT2))
+
+char *qfextension[] = INITQFNAMES;
+
+/*
+ * Check to see if a particular quota is to be enabled.
+ */
+int
+hasquota(struct mntent *mnt, int type, char **qfnamep)
+{
+ char *qfname = QUOTAFILENAME;
+ char *option, *pathname;
+
+ if (!CORRECT_FSTYPE(mnt->mnt_type))
+ return (0);
+
+ if (((type == USRQUOTA) && (option = hasmntopt(mnt, MNTOPT_USRQUOTA)) != (char *)0) ||
+ ((type == GRPQUOTA) && (option = hasmntopt(mnt, MNTOPT_GRPQUOTA)) != (char *)0)) {
+ if ((pathname = strchr(option, '=')) == (char *)0) {
+ *qfnamep=xmalloc(strlen(mnt->mnt_dir)+strlen(qfname)+strlen(qfextension[type])+2);
+ (void) sprintf(*qfnamep, "%s%s%s.%s", mnt->mnt_dir,
+ (mnt->mnt_dir[strlen(mnt->mnt_dir) - 1] == '/') ? "" : "/",
+ qfname, qfextension[type]);
+ } else {
+ if ((option = strchr(++pathname, ',')) != (char *)NULL) {
+ int len=option-pathname;
+ *qfnamep=xmalloc(len);
+ memcpy(*qfnamep, pathname, len-1);
+ (*qfnamep) [len-1] = '\0';
+ }
+ else {
+ *qfnamep=xstrdup(pathname);
+ }
+ }
+ return (1);
+ } else
+ return (0);
+}
diff --git a/utils/rquotad/mntent.h b/utils/rquotad/mntent.h
new file mode 100644
index 0000000..6c58451
--- /dev/null
+++ b/utils/rquotad/mntent.h
@@ -0,0 +1,112 @@
+#ifndef _MNTENT_H
+#define _MNTENT_H
+
+#include <features.h>
+
+#define MNTTAB "/etc/fstab"
+#define MOUNTED "/etc/mtab"
+
+#define MNTMAXSTR 512
+
+#define MNTTYPE_COHERENT "coherent" /* Coherent file system */
+#define MNTTYPE_EXT "ext" /* Extended file system */
+#define MNTTYPE_EXT2 "ext2" /* Second Extended file system */
+#define MNTTYPE_HPFS "hpfs" /* OS/2's high performance file system */
+#define MNTTYPE_ISO9660 "iso9660" /* ISO CDROM file system */
+#define MNTTYPE_MINIX "minix" /* MINIX file system */
+#define MNTTYPE_MSDOS "msdos" /* MS-DOS file system */
+#define MNTTYPE_SYSV "sysv" /* System V file system */
+#define MNTTYPE_UMSDOS "umsdos" /* U MS-DOS file system */
+#define MNTTYPE_XENIX "xenix" /* Xenix file system */
+#define MNTTYPE_XIAFS "xiafs" /* Frank Xia's file system */
+#define MNTTYPE_NFS "nfs" /* Network file system */
+#define MNTTYPE_PROC "proc" /* Linux process file system */
+#define MNTTYPE_IGNORE "ignore" /* Ignore this entry */
+#define MNTTYPE_SWAP "swap" /* Swap device */
+
+/* generic mount options */
+#define MNTOPT_DEFAULTS "defaults" /* use all default opts */
+#define MNTOPT_RO "ro" /* read only */
+#define MNTOPT_RW "rw" /* read/write */
+#define MNTOPT_SUID "suid" /* set uid allowed */
+#define MNTOPT_NOSUID "nosuid" /* no set uid allowed */
+#define MNTOPT_NOAUTO "noauto" /* don't auto mount */
+
+/* ext2 and msdos options */
+#define MNTOPT_CHECK "check" /* filesystem check level */
+
+/* ext2 specific options */
+#define MNTOPT_BSDDF "bsddf" /* disable MINIX compatibility disk free counting */
+#define MNTOPT_BSDGROUPS "bsdgroups" /* set BSD group usage */
+#define MNTOPT_ERRORS "errors" /* set behaviour on error */
+#define MNTOPT_GRPID "grpid" /* set BSD group usage */
+#define MNTOPT_MINIXDF "minixdf" /* enable MINIX compatibility disk free counting */
+#define MNTOPT_NOCHECK "nocheck" /* reset filesystem checks */
+#define MNTOPT_NOGRPID "nogrpid" /* set SYSV group usage */
+#define MNTOPT_RESGID "resgid" /* group to consider like root for reserved blocks */
+#define MNTOPT_RESUID "resuid" /* user to consider like root for reserved blocks */
+#define MNTOPT_SB "sb" /* set used super block */
+#define MNTOPT_SYSVGROUPS "sysvgroups" /* set SYSV group usage */
+
+/* options common to hpfs, isofs, and msdos */
+#define MNTOPT_CONV "conv" /* convert specified types of data */
+#define MNTOPT_GID "gid" /* use given gid */
+#define MNTOPT_UID "uid" /* use given uid */
+#define MNTOPT_UMASK "umask" /* use given umask, not isofs */
+
+/* hpfs specific options */
+#define MNTOPT_CASE "case" /* case conversation */
+
+/* isofs specific options */
+#define MNTOPT_BLOCK "block" /* use given block size */
+#define MNTOPT_CRUFT "cruft" /* ??? */
+#define MNTOPT_MAP "map" /* ??? */
+#define MNTOPT_NOROCK "norock" /* not rockwell format ??? */
+
+/* msdos specific options */
+#define MNTOPT_FAT "fat" /* set FAT size */
+#define MNTOPT_QUIET "quiet" /* ??? */
+
+/* swap specific options */
+
+/* options common to ext, ext2, minix, xiafs, sysv, xenix, coherent */
+#define MNTOPT_NOQUOTA "noquota" /* don't use any quota on this partition */
+#define MNTOPT_USRQUOTA "usrquota" /* use userquota on this partition */
+#define MNTOPT_GRPQUOTA "grpquota" /* use groupquota on this partition */
+#define MNTOPT_RSQUASH "rsquash" /* threat root as an ordinary user */
+
+/* none defined yet */
+
+__BEGIN_DECLS
+
+struct mntent{
+ char *mnt_fsname;
+ char *mnt_dir;
+ char *mnt_type;
+ char *mnt_opts;
+ int mnt_freq;
+ int mnt_passno;
+};
+
+__END_DECLS
+
+#define __need_file
+#include <stdio.h>
+
+__BEGIN_DECLS
+
+extern FILE *setmntent __P ((__const char *__filep,
+ __const char *__type));
+extern struct mntent
+ *getmntent __P ((FILE *__filep));
+extern int addmntent __P ((FILE *__filep,
+ __const struct mntent *__mnt));
+extern char *hasmntopt __P ((__const struct mntent *__mnt,
+ __const char *__opt));
+extern int endmntent __P ((FILE *__filep));
+
+extern int hasquota __P ((struct mntent *, int, char **));
+
+__END_DECLS
+
+#endif /* _MNTENT_H */
diff --git a/utils/rquotad/pathnames.h b/utils/rquotad/pathnames.h
new file mode 100644
index 0000000..6604a18
--- /dev/null
+++ b/utils/rquotad/pathnames.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ *
+ * @@(#)pathnames.h 5.3 (Berkeley) 6/1/90
+ */
+
+#include <paths.h>
+
+#undef _PATH_TMP
+#define _PATH_TMP "/tmp/EdP.aXXXXXX"
diff --git a/utils/rquotad/quotactl.c b/utils/rquotad/quotactl.c
new file mode 100644
index 0000000..30e68a4
--- /dev/null
+++ b/utils/rquotad/quotactl.c
@@ -0,0 +1,30 @@
+/*
+ * QUOTA An implementation of the diskquota system for the LINUX
+ * operating system. QUOTA is implemented using the BSD systemcall
+ * interface as the means of communication with the user level.
+ * Should work for all filesystems because of integration into the
+ * VFS layer of the operating system.
+ * This is based on the Melbourne quota system wich uses both user and
+ * group quota files.
+ *
+ * System call interface.
+ *
+ * Version: $Id: quotactl.c,v 2.3 1995/07/23 09:58:06 mvw Exp mvw $
+ *
+ * Author: Marco van Wieringen <mvw@planets.ow.nl> <mvw@tnix.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include "config.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+int quotactl(int cmd, const char * special, int id, caddr_t addr)
+{
+ return syscall(SYS_quotactl, cmd, special, id, addr);
+}
diff --git a/utils/rquotad/rquota.h b/utils/rquotad/rquota.h
new file mode 100644
index 0000000..f81e732
--- /dev/null
+++ b/utils/rquotad/rquota.h
@@ -0,0 +1,64 @@
+#define RQ_PATHLEN 1024
+
+struct getquota_args {
+ char *gqa_pathp;
+ int gqa_uid;
+};
+typedef struct getquota_args getquota_args;
+bool_t xdr_getquota_args();
+
+
+struct ext_getquota_args {
+ char *gqa_pathp;
+ int gqa_type;
+ int gqa_id;
+};
+typedef struct ext_getquota_args ext_getquota_args;
+bool_t xdr_ext_getquota_args();
+
+
+struct rquota {
+ int rq_bsize;
+ bool_t rq_active;
+ u_int rq_bhardlimit;
+ u_int rq_bsoftlimit;
+ u_int rq_curblocks;
+ u_int rq_fhardlimit;
+ u_int rq_fsoftlimit;
+ u_int rq_curfiles;
+ u_int rq_btimeleft;
+ u_int rq_ftimeleft;
+};
+typedef struct rquota rquota;
+bool_t xdr_rquota();
+
+
+enum gqr_status {
+ Q_OK = 1,
+ Q_NOQUOTA = 2,
+ Q_EPERM = 3,
+};
+typedef enum gqr_status gqr_status;
+bool_t xdr_gqr_status();
+
+
+struct getquota_rslt {
+ gqr_status status;
+ union {
+ rquota gqr_rquota;
+ } getquota_rslt_u;
+};
+typedef struct getquota_rslt getquota_rslt;
+bool_t xdr_getquota_rslt();
+
+
+#define RQUOTAPROG ((u_long)100011)
+#define RQUOTAVERS ((u_long)1)
+#define RQUOTAPROC_GETQUOTA ((u_long)1)
+extern getquota_rslt *rquotaproc_getquota_1();
+#define RQUOTAPROC_GETACTIVEQUOTA ((u_long)2)
+extern getquota_rslt *rquotaproc_getactivequota_1();
+#define EXT_RQUOTAVERS ((u_long)2)
+extern getquota_rslt *rquotaproc_getquota_2();
+extern getquota_rslt *rquotaproc_getactivequota_2();
+
diff --git a/utils/rquotad/rquota.x b/utils/rquotad/rquota.x
new file mode 100644
index 0000000..120abe5
--- /dev/null
+++ b/utils/rquotad/rquota.x
@@ -0,0 +1,84 @@
+/* @(#)rquota.x 2.1 88/08/01 4.0 RPCSRC */
+/* @(#)rquota.x 1.2 87/09/20 Copyr 1987 Sun Micro */
+
+/*
+ * Remote quota protocol
+ * Requires unix authentication
+ */
+
+#ifdef RPC_CLNT
+%#include <string.h>
+#endif
+
+const RQ_PATHLEN = 1024;
+
+struct getquota_args {
+ string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int gqa_uid; /* Inquire about quota for uid */
+};
+
+struct ext_getquota_args {
+ string gqa_pathp<RQ_PATHLEN>; /* path to filesystem of interest */
+ int gqa_type; /* Type of quota info is needed about */
+ int gqa_id; /* Inquire about quota for id */
+};
+
+/*
+ * remote quota structure
+ */
+struct rquota {
+ int rq_bsize; /* block size for block counts */
+ bool rq_active; /* indicates whether quota is active */
+ unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */
+ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */
+ unsigned int rq_curblocks; /* current block count */
+ unsigned int rq_fhardlimit; /* absolute limit on allocated files */
+ unsigned int rq_fsoftlimit; /* preferred file limit */
+ unsigned int rq_curfiles; /* current # allocated files */
+ unsigned int rq_btimeleft; /* time left for excessive disk use */
+ unsigned int rq_ftimeleft; /* time left for excessive files */
+};
+
+enum gqr_status {
+ Q_OK = 1, /* quota returned */
+ Q_NOQUOTA = 2, /* noquota for uid */
+ Q_EPERM = 3 /* no permission to access quota */
+};
+
+union getquota_rslt switch (gqr_status status) {
+case Q_OK:
+ rquota gqr_rquota; /* valid if status == Q_OK */
+case Q_NOQUOTA:
+ void;
+case Q_EPERM:
+ void;
+};
+
+program RQUOTAPROG {
+ version RQUOTAVERS {
+ /*
+ * Get all quotas
+ */
+ getquota_rslt
+ RQUOTAPROC_GETQUOTA(getquota_args) = 1;
+
+ /*
+ * Get active quotas only
+ */
+ getquota_rslt
+ RQUOTAPROC_GETACTIVEQUOTA(getquota_args) = 2;
+ } = 1;
+ version EXT_RQUOTAVERS {
+ /*
+ * Get all quotas
+ */
+ getquota_rslt
+ RQUOTAPROC_GETQUOTA(ext_getquota_args) = 1;
+
+ /*
+ * Get active quotas only
+ */
+ getquota_rslt
+ RQUOTAPROC_GETACTIVEQUOTA(ext_getquota_args) = 2;
+ } = 2;
+} = 100011;
diff --git a/utils/rquotad/rquota_server.c b/utils/rquotad/rquota_server.c
new file mode 100644
index 0000000..08c4f8c
--- /dev/null
+++ b/utils/rquotad/rquota_server.c
@@ -0,0 +1,246 @@
+/*
+ * QUOTA An implementation of the diskquota system for the LINUX
+ * operating system. QUOTA is implemented using the BSD systemcall
+ * interface as the means of communication with the user level.
+ * Should work for all filesystems because of integration into the
+ * VFS layer of the operating system.
+ * This is based on the Melbourne quota system wich uses both user and
+ * group quota files.
+ *
+ * This part does the lookup of the info.
+ *
+ * Version: $Id: rquota_server.c,v 2.9 1996/11/17 16:59:46 mvw Exp mvw $
+ *
+ * Author: Marco van Wieringen <mvw@planets.elm.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include "config.h"
+
+#include <rpc/rpc.h>
+#include "rquota.h"
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/quota.h>
+#include <dirent.h>
+#include <paths.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "mntent.h"
+#include "xmalloc.h"
+
+#define TYPE_EXTENDED 0x01
+#define ACTIVE 0x02
+
+#ifdef ELM
+#define _PATH_DEV_DSK "/dev/dsk/"
+#else
+#define _PATH_DEV_DSK "/dev/"
+#endif
+
+/*
+ * Global unix authentication credentials.
+ */
+extern struct authunix_parms *unix_cred;
+
+char *nfsmount_to_devname(char *pathname, int *blksize)
+{
+ DIR *dp;
+ dev_t device;
+ struct stat st;
+ struct dirent *de;
+ static char *devicename = NULL;
+ static int devicelen = 0;
+
+ if (stat(pathname, &st) == -1)
+ return (char *)0;
+
+ device = st.st_dev;
+ *blksize = st.st_blksize;
+
+ /*
+ * search for devicename in _PATH_DEV_DSK dir.
+ */
+ if ((dp = opendir(_PATH_DEV_DSK)) == (DIR *)0)
+ return (char *)0;
+
+ while ((de = readdir(dp)) != (struct dirent *)0) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+ continue;
+
+ if (devicelen == 0) {
+ devicelen = sizeof (_PATH_DEV_DSK) + strlen (de->d_name) + 1;
+ devicename = (char *) xmalloc (devicelen);
+ }
+ else {
+ int newlen = sizeof (_PATH_DEV_DSK) + strlen (de->d_name) + 1;
+ if (newlen > devicelen) {
+ devicelen = newlen;
+ devicename = (char *) xrealloc (devicename, devicelen);
+ }
+ }
+
+ strcpy(devicename, _PATH_DEV_DSK);
+ strcat(devicename, de->d_name);
+ if (stat(devicename, &st) == -1)
+ continue;
+
+ if (!S_ISBLK(st.st_mode))
+ continue;
+
+ if ((device == st.st_rdev) && S_ISBLK(st.st_mode))
+ break;
+ }
+ closedir(dp);
+
+ if (de != (struct dirent *)0) {
+ return devicename;
+ } else
+ return (char *)0;
+}
+
+int in_group (gid_t *gids, u_int len, gid_t gid)
+{
+ int cnt = 0;
+
+ while (cnt < len) {
+ if (gids[cnt] == gid)
+ return 1;
+ cnt++;
+ }
+ return 0;
+}
+
+getquota_rslt *getquotainfo(int flags, caddr_t *argp, struct svc_req *rqstp)
+{
+ static getquota_rslt result;
+ union {
+ getquota_args *args;
+ ext_getquota_args *ext_args;
+ } arguments;
+ FILE *fp;
+ struct dqblk dq_dqb;
+ struct mntent *mnt;
+ char *pathname, *devicename, *qfpathname;
+ int fd, err, id, type;
+
+ /*
+ * First check authentication.
+ */
+ if (flags & TYPE_EXTENDED) {
+ arguments.ext_args = (ext_getquota_args *)argp;
+ id = arguments.ext_args->gqa_id;
+ type = arguments.ext_args->gqa_type;
+ pathname = arguments.ext_args->gqa_pathp;
+
+ if (type == USRQUOTA && unix_cred->aup_uid && unix_cred->aup_uid != id) {
+ result.status = Q_EPERM;
+ return(&result);
+ }
+
+ if (type == GRPQUOTA && unix_cred->aup_uid && unix_cred->aup_gid != id &&
+ !in_group((gid_t *)unix_cred->aup_gids, unix_cred->aup_len, id)) {
+ result.status = Q_EPERM;
+ return(&result);
+ }
+ } else {
+ arguments.args = (getquota_args *)argp;
+ id = arguments.args->gqa_uid;
+ type = USRQUOTA;
+ pathname = arguments.args->gqa_pathp;
+
+ if (unix_cred->aup_uid && unix_cred->aup_uid != id) {
+ result.status = Q_EPERM;
+ return(&result);
+ }
+ }
+
+ /*
+ * Convert a nfs_mountpoint to a local devicename.
+ */
+ if ((devicename = nfsmount_to_devname(pathname,
+ &result.getquota_rslt_u.gqr_rquota.rq_bsize)) == (char *)0) {
+ result.status = Q_NOQUOTA;
+ return(&result);
+ }
+
+ fp = setmntent(MNTTAB, "r");
+ while ((mnt = getmntent(fp)) != (struct mntent *)0) {
+ if (strcmp(devicename, mnt->mnt_fsname))
+ continue;
+
+ if (hasquota(mnt, type, &qfpathname)) {
+ if ((err = quotactl(QCMD(Q_GETQUOTA, type), devicename, id,
+ (caddr_t)&dq_dqb)) == -1 && !(flags & ACTIVE)) {
+ if ((fd = open(qfpathname, O_RDONLY)) < 0)
+ {
+ free(qfpathname);
+ continue;
+ }
+ free(qfpathname);
+ lseek(fd, (long) dqoff(id), L_SET);
+ switch (read(fd, &dq_dqb, sizeof(struct dqblk))) {
+ case 0:/* EOF */
+ /*
+ * Convert implicit 0 quota (EOF) into an
+ * explicit one (zero'ed dqblk)
+ */
+ memset((caddr_t)&dq_dqb, 0, sizeof(struct dqblk));
+ break;
+ case sizeof(struct dqblk): /* OK */
+ break;
+ default: /* ERROR */
+ close(fd);
+ continue;
+ }
+ close(fd);
+ }
+ endmntent(fp);
+
+ if (err && (flags & ACTIVE)) {
+ result.status = Q_NOQUOTA;
+ return(&result);
+ }
+
+ result.status = Q_OK;
+ result.getquota_rslt_u.gqr_rquota.rq_active = (err == 0) ? TRUE : FALSE;
+ /*
+ * Make a copy of the info into the last part of the remote quota
+ * struct which is exactly the same.
+ */
+ memcpy((caddr_t *)&result.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
+ (caddr_t *)&dq_dqb, sizeof(struct dqblk));
+
+ return(&result);
+ }
+ }
+ endmntent(fp);
+
+ result.status = Q_NOQUOTA;
+ return(&result);
+}
+
+getquota_rslt *rquotaproc_getquota_1(getquota_args *argp, struct svc_req *rqstp)
+{
+ return(getquotainfo(0, (caddr_t *)argp, rqstp));
+}
+
+getquota_rslt *rquotaproc_getactivequota_1(getquota_args *argp, struct svc_req *rqstp)
+{
+ return(getquotainfo(ACTIVE, (caddr_t *)argp, rqstp));
+}
+
+getquota_rslt *rquotaproc_getquota_2(ext_getquota_args *argp, struct svc_req *rqstp)
+{
+ return(getquotainfo(TYPE_EXTENDED, (caddr_t *)argp, rqstp));
+}
+
+getquota_rslt *rquotaproc_getactivequota_2(ext_getquota_args *argp, struct svc_req *rqstp)
+{
+ return(getquotainfo(TYPE_EXTENDED | ACTIVE, (caddr_t *)argp, rqstp));
+}
diff --git a/utils/rquotad/rquota_svc.c b/utils/rquotad/rquota_svc.c
new file mode 100644
index 0000000..d402f0b
--- /dev/null
+++ b/utils/rquotad/rquota_svc.c
@@ -0,0 +1,213 @@
+/*
+ * QUOTA An implementation of the diskquota system for the LINUX
+ * operating system. QUOTA is implemented using the BSD systemcall
+ * interface as the means of communication with the user level.
+ * Should work for all filesystems because of integration into the
+ * VFS layer of the operating system.
+ * This is based on the Melbourne quota system wich uses both user and
+ * group quota files.
+ *
+ * This part accepts the rquota rpc-requests.
+ *
+ * Version: $Id: rquota_svc.c,v 2.6 1996/11/17 16:59:46 mvw Exp mvw $
+ *
+ * Author: Marco van Wieringen <mvw@planets.elm.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include "config.h"
+
+#include <unistd.h>
+#include <rpc/rpc.h>
+#include "rquota.h"
+#include <stdlib.h>
+#include <rpc/pmap_clnt.h>
+#include <string.h>
+#include <memory.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <syslog.h>
+
+#ifdef __STDC__
+#define SIG_PF void(*)(int)
+#endif
+
+extern getquota_rslt *rquotaproc_getquota_1(getquota_args *argp,
+ struct svc_req *rqstp);
+extern getquota_rslt *rquotaproc_getactivequota_1(getquota_args *argp,
+ struct svc_req *rqstp);
+extern getquota_rslt *rquotaproc_getquota_2(ext_getquota_args *argp,
+ struct svc_req *rqstp);
+extern getquota_rslt *rquotaproc_getactivequota_2(ext_getquota_args *argp,
+ struct svc_req *rqstp);
+
+/*
+ * Global authentication credentials.
+ */
+struct authunix_parms *unix_cred;
+
+static void rquotaprog_1(struct svc_req *rqstp, register SVCXPRT *transp)
+{
+ union {
+ getquota_args rquotaproc_getquota_1_arg;
+ getquota_args rquotaproc_getactivequota_1_arg;
+ } argument;
+ char *result;
+ xdrproc_t xdr_argument, xdr_result;
+ char *(*local)(char *, struct svc_req *);
+
+ /*
+ * Don't bother authentication for NULLPROC.
+ */
+ if (rqstp->rq_proc == NULLPROC) {
+ (void) svc_sendreply(transp, (xdrproc_t) xdr_void, (char *)NULL);
+ return;
+ }
+
+ /*
+ * First get authentication.
+ */
+ switch (rqstp->rq_cred.oa_flavor) {
+ case AUTH_UNIX:
+ unix_cred = (struct authunix_parms *)rqstp->rq_clntcred;
+ break;
+ case AUTH_NULL:
+ default:
+ svcerr_weakauth(transp);
+ return;
+ }
+
+ switch (rqstp->rq_proc) {
+ case RQUOTAPROC_GETQUOTA:
+ xdr_argument = (xdrproc_t) xdr_getquota_args;
+ xdr_result = (xdrproc_t) xdr_getquota_rslt;
+ local = (char *(*)(char *, struct svc_req *)) rquotaproc_getquota_1;
+ break;
+
+ case RQUOTAPROC_GETACTIVEQUOTA:
+ xdr_argument = (xdrproc_t) xdr_getquota_args;
+ xdr_result = (xdrproc_t) xdr_getquota_rslt;
+ local = (char *(*)(char *, struct svc_req *)) rquotaproc_getactivequota_1;
+ break;
+
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+
+ (void) memset((char *)&argument, 0, sizeof (argument));
+ if (!svc_getargs(transp, xdr_argument, (caddr_t) &argument)) {
+ svcerr_decode(transp);
+ return;
+ }
+ result = (*local)((char *)&argument, rqstp);
+ if (result != NULL && !svc_sendreply(transp, xdr_result, result)) {
+ svcerr_systemerr(transp);
+ }
+
+ if (!svc_freeargs(transp, xdr_argument, (caddr_t) &argument)) {
+ syslog(LOG_ERR, "unable to free arguments");
+ exit(1);
+ }
+ return;
+}
+
+static void rquotaprog_2(struct svc_req *rqstp, register SVCXPRT *transp)
+{
+ union {
+ ext_getquota_args rquotaproc_getquota_2_arg;
+ ext_getquota_args rquotaproc_getactivequota_2_arg;
+ } argument;
+ char *result;
+ xdrproc_t xdr_argument, xdr_result;
+ char *(*local)(char *, struct svc_req *);
+
+ /*
+ * Don't bother authentication for NULLPROC.
+ */
+ if (rqstp->rq_proc == NULLPROC) {
+ (void) svc_sendreply(transp, (xdrproc_t) xdr_void, (char *)NULL);
+ return;
+ }
+
+ /*
+ * First get authentication.
+ */
+ switch (rqstp->rq_cred.oa_flavor) {
+ case AUTH_UNIX:
+ unix_cred = (struct authunix_parms *)rqstp->rq_clntcred;
+ break;
+ case AUTH_NULL:
+ default:
+ svcerr_weakauth(transp);
+ return;
+ }
+
+ switch (rqstp->rq_proc) {
+ case RQUOTAPROC_GETQUOTA:
+ xdr_argument = (xdrproc_t) xdr_ext_getquota_args;
+ xdr_result = (xdrproc_t) xdr_getquota_rslt;
+ local = (char *(*)(char *, struct svc_req *)) rquotaproc_getquota_2;
+ break;
+
+ case RQUOTAPROC_GETACTIVEQUOTA:
+ xdr_argument = (xdrproc_t) xdr_ext_getquota_args;
+ xdr_result = (xdrproc_t) xdr_getquota_rslt;
+ local = (char *(*)(char *, struct svc_req *)) rquotaproc_getactivequota_2;
+ break;
+
+ default:
+ svcerr_noproc(transp);
+ return;
+ }
+
+ (void) memset((char *)&argument, 0, sizeof (argument));
+ if (!svc_getargs(transp, xdr_argument, (caddr_t) &argument)) {
+ svcerr_decode(transp);
+ return;
+ }
+ result = (*local)((char *)&argument, rqstp);
+ if (result != NULL && !svc_sendreply(transp, xdr_result, result)) {
+ svcerr_systemerr(transp);
+ }
+
+ if (!svc_freeargs(transp, xdr_argument, (caddr_t) &argument)) {
+ syslog(LOG_ERR, "unable to free arguments");
+ exit(1);
+ }
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ register SVCXPRT *transp;
+
+ (void) pmap_unset(RQUOTAPROG, RQUOTAVERS);
+ (void) pmap_unset(RQUOTAPROG, EXT_RQUOTAVERS);
+
+ openlog("rquota", LOG_PID, LOG_DAEMON);
+
+ transp = svcudp_create(RPC_ANYSOCK);
+ if (transp == NULL) {
+ syslog(LOG_ERR, "cannot create udp service.");
+ exit(1);
+ }
+ if (!svc_register(transp, RQUOTAPROG, RQUOTAVERS, rquotaprog_1, IPPROTO_UDP)) {
+ syslog(LOG_ERR, "unable to register (RQUOTAPROG, RQUOTAVERS, udp).");
+ exit(1);
+ }
+ if (!svc_register(transp, RQUOTAPROG, EXT_RQUOTAVERS, rquotaprog_2, IPPROTO_UDP)) {
+ syslog(LOG_ERR, "unable to register (RQUOTAPROG, EXT_RQUOTAVERS, udp).");
+ exit(1);
+ }
+
+ daemon(1,1);
+ svc_run();
+
+ syslog(LOG_ERR, "svc_run returned");
+ exit(1);
+ /* NOTREACHED */
+}
diff --git a/utils/rquotad/rquota_xdr.c b/utils/rquotad/rquota_xdr.c
new file mode 100644
index 0000000..6e68bd4
--- /dev/null
+++ b/utils/rquotad/rquota_xdr.c
@@ -0,0 +1,123 @@
+#include "config.h"
+
+#include <rpc/rpc.h>
+#include "rquota.h"
+
+
+bool_t
+xdr_getquota_args(xdrs, objp)
+ XDR *xdrs;
+ getquota_args *objp;
+{
+ if (!xdr_string(xdrs, &objp->gqa_pathp, RQ_PATHLEN)) {
+ return (FALSE);
+ }
+ if (!xdr_int(xdrs, &objp->gqa_uid)) {
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+
+
+
+bool_t
+xdr_ext_getquota_args(xdrs, objp)
+ XDR *xdrs;
+ ext_getquota_args *objp;
+{
+ if (!xdr_string(xdrs, &objp->gqa_pathp, RQ_PATHLEN)) {
+ return (FALSE);
+ }
+ if (!xdr_int(xdrs, &objp->gqa_type)) {
+ return (FALSE);
+ }
+ if (!xdr_int(xdrs, &objp->gqa_id)) {
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+
+
+
+bool_t
+xdr_rquota(xdrs, objp)
+ XDR *xdrs;
+ rquota *objp;
+{
+ if (!xdr_int(xdrs, &objp->rq_bsize)) {
+ return (FALSE);
+ }
+ if (!xdr_bool(xdrs, &objp->rq_active)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_bhardlimit)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_bsoftlimit)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_curblocks)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_fhardlimit)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_fsoftlimit)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_curfiles)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_btimeleft)) {
+ return (FALSE);
+ }
+ if (!xdr_u_int(xdrs, &objp->rq_ftimeleft)) {
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+
+
+
+bool_t
+xdr_gqr_status(xdrs, objp)
+ XDR *xdrs;
+ gqr_status *objp;
+{
+ if (!xdr_enum(xdrs, (enum_t *)objp)) {
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+
+
+
+bool_t
+xdr_getquota_rslt(xdrs, objp)
+ XDR *xdrs;
+ getquota_rslt *objp;
+{
+ if (!xdr_gqr_status(xdrs, &objp->status)) {
+ return (FALSE);
+ }
+ switch (objp->status) {
+ case Q_OK:
+ if (!xdr_rquota(xdrs, &objp->getquota_rslt_u.gqr_rquota)) {
+ return (FALSE);
+ }
+ break;
+ case Q_NOQUOTA:
+ break;
+ case Q_EPERM:
+ break;
+ default:
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+
diff --git a/utils/rquotad/rquotad.man b/utils/rquotad/rquotad.man
new file mode 100644
index 0000000..da8fa8c
--- /dev/null
+++ b/utils/rquotad/rquotad.man
@@ -0,0 +1,41 @@
+.\"@(#)rquotad.8c"
+.TH RQUOTAD 8C"
+.SH NAME
+rquotad, rpc.rquotad \- remote quota server
+.SH SYNOPSIS
+.B /usr/etc/rpc.rquotad
+.SH DESCRIPTION
+.LP
+.IX "rquotad daemon" "" "\fLrquotad\fP \(em remote quota server"
+.IX daemons "rquotad daemon" "" "\fLrquotad\fP \(em remote quota server"
+.IX "user quotas" "rquotad daemon" "" "\fLrquotad\fP \(em remote quota server"
+.IX "disk quotas" "rquotad daemon" "" "\fLrquotad\fP \(em remote quota server"
+.IX "quotas" "rquotad daemon" "" "\fLrquotad\fP \(em remote quota server"
+.IX "file system" "rquotad daemon" "" "\fLrquotad\fP \(em remote quota server"
+.IX "remote procedure call services" "rquotad" "" "\fLrquotad\fP \(em remote quota server"
+.B rquotad
+is an
+.BR rpc (3N)
+server which returns quotas for a user of a local file system
+which is mounted by a remote machine over the
+.SM NFS\s0.
+The results are used by
+.BR quota (1)
+to display user quotas for remote file systems.
+The
+.B rquotad
+daemon is normally started at boottime from the
+.BR rc.net
+script
+.SH FILES
+.PD 0
+.TP 20
+.B quotas
+quota file at the file system root
+.PD
+.SH "SEE ALSO"
+.BR quota (1),
+.BR rpc (3N),
+.BR nfs (4P),
+.BR services (5)
+.BR inetd (8C),
diff --git a/utils/showmount/Makefile b/utils/showmount/Makefile
new file mode 100644
index 0000000..c8aa34d
--- /dev/null
+++ b/utils/showmount/Makefile
@@ -0,0 +1,11 @@
+#
+# dummy Makefile
+#
+
+PROGRAM = showmount
+OBJS = showmount.o
+LIBDEPS = $(TOP)support/lib/libexport.a
+LIBS = -lexport
+MAN8 = showmount
+
+include $(TOP)rules.mk
diff --git a/utils/showmount/showmount.c b/utils/showmount/showmount.c
new file mode 100644
index 0000000..47b5825
--- /dev/null
+++ b/utils/showmount/showmount.c
@@ -0,0 +1,287 @@
+/*
+ * showmount.c -- show mount information for an NFS server
+ * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <rpc/rpc.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/time.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <memory.h>
+#include <stdlib.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <getopt.h>
+#include <mount.h>
+#include <unistd.h>
+
+static char * version = "showmount for " VERSION;
+static char * program_name;
+static int headers = 1;
+static int hflag = 0;
+static int aflag = 0;
+static int dflag = 0;
+static int eflag = 0;
+
+static struct option longopts[] =
+{
+ { "all", 0, 0, 'a' },
+ { "directories", 0, 0, 'd' },
+ { "exports", 0, 0, 'e' },
+ { "no-headers", 0, &headers, 0 },
+ { "version", 0, 0, 'v' },
+ { "help", 0, 0, 'h' },
+ { NULL, 0, 0, 0 }
+};
+
+#define MAXHOSTLEN 256
+
+int dump_cmp(p, q)
+char **p;
+char **q;
+{
+ return strcmp(*p, *q);
+}
+
+static void usage(fp, n)
+FILE *fp;
+int n;
+{
+ fprintf(fp, "Usage: %s [-adehv]\n", program_name);
+ fprintf(fp, " [--all] [--directories] [--exports]\n");
+ fprintf(fp, " [--no-headers] [--help] [--version] [host]\n");
+ exit(n);
+}
+
+int main(argc, argv)
+int argc;
+char **argv;
+{
+ char hostname_buf[MAXHOSTLEN];
+ char *hostname;
+ enum clnt_stat clnt_stat;
+ struct hostent *hp;
+ struct sockaddr_in server_addr;
+ int msock;
+ struct timeval total_timeout;
+ struct timeval pertry_timeout;
+ int c;
+ CLIENT *mclient;
+ groups grouplist;
+ exports exportlist, exl;
+ mountlist dumplist;
+ mountlist list;
+ int i;
+ int n;
+ int maxlen;
+ char **dumpv;
+
+ program_name = argv[0];
+ while ((c = getopt_long(argc, argv, "adehv", longopts, NULL)) != EOF) {
+ switch (c) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'h':
+ usage(stdout, 0);
+ break;
+ case 'v':
+ printf("%s\n", version);
+ exit(0);
+ case 0:
+ break;
+ case '?':
+ default:
+ usage(stderr, 1);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ switch (aflag + dflag + eflag) {
+ case 0:
+ hflag = 1;
+ break;
+ case 1:
+ break;
+ default:
+ fprintf(stderr, "%s: only one of -a, -d or -e is allowed\n",
+ program_name);
+ exit(1);
+ break;
+ }
+
+ switch (argc) {
+ case 0:
+ if (gethostname(hostname_buf, MAXHOSTLEN) < 0) {
+ perror("getting hostname");
+ exit(1);
+ }
+ hostname = hostname_buf;
+ break;
+ case 1:
+ hostname = argv[0];
+ break;
+ default:
+ fprintf(stderr, "%s: only one hostname is allowed\n",
+ program_name);
+ exit(1);
+ break;
+ }
+
+ if (hostname[0] >= '0' && hostname[0] <= '9') {
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = inet_addr(hostname);
+ }
+ else {
+ if ((hp = gethostbyname(hostname)) == NULL) {
+ fprintf(stderr, "%s: can't get address for %s\n",
+ program_name, hostname);
+ exit(1);
+ }
+ server_addr.sin_family = AF_INET;
+ memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
+ }
+
+ /* create mount deamon client */
+
+ server_addr.sin_port = 0;
+ msock = RPC_ANYSOCK;
+ if ((mclient = clnttcp_create(&server_addr,
+ MOUNTPROG, MOUNTVERS, &msock, 0, 0)) == NULL) {
+ server_addr.sin_port = 0;
+ msock = RPC_ANYSOCK;
+ pertry_timeout.tv_sec = 3;
+ pertry_timeout.tv_usec = 0;
+ if ((mclient = clntudp_create(&server_addr,
+ MOUNTPROG, MOUNTVERS, pertry_timeout, &msock)) == NULL) {
+ clnt_pcreateerror("mount clntudp_create");
+ exit(1);
+ }
+ }
+ mclient->cl_auth = authunix_create_default();
+ total_timeout.tv_sec = 20;
+ total_timeout.tv_usec = 0;
+
+ if (eflag) {
+ memset(&exportlist, '\0', sizeof(exportlist));
+ clnt_stat = clnt_call(mclient, MOUNTPROC_EXPORT,
+ (xdrproc_t) xdr_void, NULL,
+ (xdrproc_t) xdr_exports, (caddr_t) &exportlist,
+ total_timeout);
+ if (clnt_stat != RPC_SUCCESS) {
+ clnt_perror(mclient, "rpc mount export");
+ exit(1);
+ }
+ if (headers)
+ printf("Export list for %s:\n", hostname);
+ maxlen = 0;
+ for (exl = exportlist; exl; exl = exl->ex_next) {
+ if ((n = strlen(exl->ex_dir)) > maxlen)
+ maxlen = n;
+ }
+ while (exportlist) {
+ printf("%-*s ", maxlen, exportlist->ex_dir);
+ grouplist = exportlist->ex_groups;
+ if (grouplist)
+ while (grouplist) {
+ printf("%s%s", grouplist->gr_name,
+ grouplist->gr_next ? "," : "");
+ grouplist = grouplist->gr_next;
+ }
+ else
+ printf("(everyone)");
+ printf("\n");
+ exportlist = exportlist->ex_next;
+ }
+ exit(0);
+ }
+
+ memset(&dumplist, '\0', sizeof(dumplist));
+ clnt_stat = clnt_call(mclient, MOUNTPROC_DUMP,
+ (xdrproc_t) xdr_void, NULL,
+ (xdrproc_t) xdr_mountlist, (caddr_t) &dumplist,
+ total_timeout);
+ if (clnt_stat != RPC_SUCCESS) {
+ clnt_perror(mclient, "rpc mount dump");
+ exit(1);
+ }
+
+ n = 0;
+ for (list = dumplist; list; list = list->ml_next)
+ n++;
+ dumpv = (char **) calloc(n, sizeof (char *));
+ if (n && !dumpv) {
+ fprintf(stderr, "%s: out of memory\n", program_name);
+ exit(1);
+ }
+ i = 0;
+
+ if (hflag) {
+ if (headers)
+ printf("Hosts on %s:\n", hostname);
+ while (dumplist) {
+ dumpv[i++] = dumplist->ml_hostname;
+ dumplist = dumplist->ml_next;
+ }
+ }
+ else if (aflag) {
+ if (headers)
+ printf("All mount points on %s:\n", hostname);
+ while (dumplist) {
+ char *t;
+
+ t=malloc(strlen(dumplist->ml_hostname)+strlen(dumplist->ml_directory)+2);
+ if (!t)
+ {
+ fprintf(stderr, "%s: out of memory\n", program_name);
+ exit(1);
+ }
+ sprintf(t, "%s:%s", dumplist->ml_hostname, dumplist->ml_directory);
+ dumpv[i++] = t;
+ dumplist = dumplist->ml_next;
+ }
+ }
+ else if (dflag) {
+ if (headers)
+ printf("Directories on %s:\n", hostname);
+ while (dumplist) {
+ dumpv[i++] = dumplist->ml_directory;
+ dumplist = dumplist->ml_next;
+ }
+ }
+
+ qsort(dumpv, n, sizeof (char *), dump_cmp);
+
+ for (i = 0; i < n; i++) {
+ if (i == 0 || strcmp(dumpv[i], dumpv[i - 1]) != 0)
+ printf("%s\n", dumpv[i]);
+ }
+ exit(0);
+}
+
diff --git a/utils/showmount/showmount.man b/utils/showmount/showmount.man
new file mode 100644
index 0000000..63342c7
--- /dev/null
+++ b/utils/showmount/showmount.man
@@ -0,0 +1,58 @@
+.\" Copyright 1993 Rick Sladkey <jrs@world.std.com>
+.\" May be distributed under the GNU General Public License
+.TH SHOWMOUNT 8 "6 October 1993"
+.SH NAME
+showmount \- show mount information for an NFS server
+.SH SYNOPSIS
+.B /usr/sbin/showmount
+.B "[\ \-adehv\ ]"
+.B "[\ \-\-all\ ]"
+.B "[\ \-\-directories\ ]"
+.B "[\ \-\-exports\ ]"
+.B "[\ \-\-help\ ]"
+.B "[\ \-\-version\ ]"
+.B "[\ host\ ]"
+.SH DESCRIPTION
+.B showmount
+queries the mount daemon on a remote host for information about
+the state of the NFS server on that machine. With no options
+.B showmount
+lists the set of clients who are mounting from that host.
+The output from
+.B showmount
+is designed to
+appear as though it were processesed through ``sort -u''.
+.SH OPTIONS
+.TP
+.BR \-a " or " \-\-all
+List both the client hostname and mounted directory in
+host:dir format.
+.TP
+.BR \-d " or " \-\-directories
+List only the directories mounted by some client.
+.TP
+.BR \-e " or " \-\-exports
+Show the NFS server's export list.
+.TP
+.BR \-h " or " \-\-help
+Provide a short help summary.
+.TP
+.BR \-v " or " \-\-version
+Report the current version number of the program.
+.TP
+.B \-\-no\-headers
+Suppress the descriptive headings from the output.
+.SH "SEE ALSO"
+.BR rpc.mountd (8),
+.BR rpc.nfsd (8)
+.SH BUGS
+The completeness and accurary of the information that
+.B showmount
+displays varies according to the NFS server's implementation.
+.P
+Because
+.B showmount
+sorts and uniqs the output, it is impossible to determine from
+the output whether a client is mounting the same directory more than once.
+.SH AUTHOR
+Rick Sladkey <jrs@world.std.com>
diff --git a/utils/statd/COPYING b/utils/statd/COPYING
new file mode 100644
index 0000000..60549be
--- /dev/null
+++ b/utils/statd/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/utils/statd/COPYRIGHT b/utils/statd/COPYRIGHT
new file mode 100644
index 0000000..7b91031
--- /dev/null
+++ b/utils/statd/COPYRIGHT
@@ -0,0 +1,25 @@
+rpc.statd -- Network Status Monitor (NSM) protocol daemon for Linux.
+Copyright (C) 1995-1999 Jeffrey A. Uphoff
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
+
+Jeffrey A. Uphoff
+Transmeta Corporation
+2540 Mission College Blvd.
+Santa Clara, CA, 95054
+USA
+
+Phone: +1-408-919-6458
+Internet: juphoff@transmeta.com
diff --git a/utils/statd/Makefile b/utils/statd/Makefile
new file mode 100644
index 0000000..3a3a794
--- /dev/null
+++ b/utils/statd/Makefile
@@ -0,0 +1,58 @@
+# Copyright (C) 1995-1999 Jeffrey A. Uphoff
+# Adapted for linux-nfs build tree by Olaf Kirch, 1996.
+#
+# NSM for Linux.
+
+# Uncomment for embedded client-side simulation functions.
+#SIMUL = -DSIMULATIONS
+
+# Undefined is normal, defined provides debug logging.
+#DEBUG = -DDEBUG
+
+##################################################################
+# no user-serviceable parts below this line
+##################################################################
+PROGRAM = statd
+PREFIX = rpc.
+OBJS = $(SRCS:.c=.o)
+CCOPTS = $(DEBUG) $(SIMUL)
+LIBS = -lexport
+
+SRCS = $(RPCSRCS) $(SIMSRCS) \
+ callback.c notlist.c log.c misc.c monitor.c notify.c simu.c \
+ stat.c statd.c state.c svc_run.c rmtcall.c
+HDRS = $(RPCHDRS) $(SIMHDRS)
+
+RPCSRCS = sm_inter_clnt.c sm_inter_svc.c sm_inter_xdr.c
+RPCHDRS = sm_inter.h
+
+ifdef SIMUL
+SIMSRCS = sim_sm_inter_clnt.c sim_sm_inter_svc.c
+SIMHDRS = sim_sm_inter.h
+SRCS += simulate.c
+endif
+
+MAN8 = statd
+
+include $(TOP)rules.mk
+
+AFLAGS += -Wno-unused
+
+$(RPCHDRS) $(RPCSRCS): sm_inter.x
+ $(RM) $(RPCHDRS) $(RPCSRCS)
+ $(RPCGEN) -h -o sm_inter.h $<
+ $(RPCGEN) -l -o sm_inter_clnt.c $<
+ $(RPCGEN) -m -o sm_inter_svc.c $<
+ $(RPCGEN) -c -o sm_inter_xdr.c $<
+
+$(SIMHDRS) $(SIMSRCS): sim_sm_inter.x
+ $(RM) $(SIMHDRS) $(SIMSRCS)
+ $(RPCGEN) -h -o sim_sm_inter.h $<
+ $(RPCGEN) -l -o sim_sm_inter_clnt.c $<
+ $(RPCGEN) -m -o sim_sm_inter_svc.c $<
+
+clean::
+ $(RM) $(PROGRAM)
+
+distclean::
+ $(RM) $(RPCHDRS) $(RPCSRCS) $(SIMHDRS) $(SIMSRCS)
diff --git a/utils/statd/TODO b/utils/statd/TODO
new file mode 100644
index 0000000..0ee050a
--- /dev/null
+++ b/utils/statd/TODO
@@ -0,0 +1,13 @@
+Some things still left to do (not a comprehensive list):
+
+* Go through Olaf's extensive changes (especially the list and callback
+ handling, which is the meat of the server) and understand everything
+ that he's done.
+
+* Continue checking for security holes.
+
+* Handle multiple SM_MON requests that are identical save for the "priv"
+ information. How should I do this? No spec's...(it's not really
+ supposed to happen). [Did Olaf already address this?]
+
+* BETTER CODE COMMENTS!
diff --git a/utils/statd/callback.c b/utils/statd/callback.c
new file mode 100644
index 0000000..e3fad6a
--- /dev/null
+++ b/utils/statd/callback.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, Oct. 1996.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#include "statd.h"
+#include "notlist.h"
+
+/* Callback notify list. */
+notify_list *cbnl = NULL;
+
+
+/*
+ * Services SM_NOTIFY requests.
+ * Any clients that have asked us to monitor that host are put on
+ * the global callback list, which is processed as soon as statd
+ * returns to svc_run.
+ */
+void *
+sm_notify_1_svc(struct stat_chge *argp, struct svc_req *rqstp)
+{
+ notify_list *lp, *call;
+ static char *result = NULL;
+
+ dprintf(L_DEBUG, "Received SM_NOTIFY from %s, state: %d",
+ argp->mon_name, argp->state);
+
+ if ((lp = rtnl) != NULL) {
+ log(L_WARNING, "SM_NOTIFY from %s--nobody looking!",
+ argp->mon_name, argp->state);
+ return ((void *) &result);
+ }
+
+ /* okir change: statd doesn't remove the remote host from its
+ * internal monitor list when receiving an SM_NOTIFY call from
+ * it. Lockd will want to continue monitoring the remote host
+ * until it issues an SM_UNMON call.
+ */
+ while ((lp = nlist_gethost(lp, argp->mon_name, 0)) != NULL) {
+ if (NL_STATE(lp) != argp->state) {
+ NL_STATE(lp) = argp->state;
+ call = nlist_clone(lp);
+ NL_TYPE(call) = NOTIFY_CALLBACK;
+ nlist_insert(&notify, call);
+ }
+ lp = NL_NEXT(lp);
+ }
+
+ return ((void *) &result);
+}
diff --git a/utils/statd/log.c b/utils/statd/log.c
new file mode 100644
index 0000000..38f7d3a
--- /dev/null
+++ b/utils/statd/log.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 1995 Olaf Kirch
+ * Modified by Jeffrey A. Uphoff, 1995, 1997, 1999.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * log.c - logging functions for lockd/statd
+ * 260295 okir started with simply syslog logging.
+ */
+
+#include "config.h"
+
+#include <syslog.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <sys/types.h>
+#include "log.h"
+
+static char progname[256];
+static pid_t mypid;
+ /* Turns on logging to console/stderr. */
+static int opt_debug = 0; /* Will be command-line option, eventually */
+
+void
+log_init(char *name)
+{
+ char *sp;
+
+ openlog(name, LOG_PID, LOG_LOCAL5);
+ if ((sp = strrchr(name, '/')) != NULL)
+ name = ++sp;
+ strncpy(progname, name, sizeof (progname) - 1);
+ progname[sizeof (progname) - 1] = '\0';
+ mypid = getpid();
+}
+
+void
+log_background(void)
+{
+ /* NOP */
+}
+
+void
+log_enable(int level)
+{
+ opt_debug = 1;
+}
+
+int
+log_enabled(int level)
+{
+ return opt_debug;
+}
+
+void
+die(char *fmt, ...)
+{
+ char buffer[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf (buffer, 1024, fmt, ap);
+ va_end(ap);
+ buffer[1023]=0;
+
+ log(L_FATAL, "%s", buffer);
+
+#ifndef DEBUG
+ exit (2);
+#else
+ abort(); /* make a core */
+#endif
+}
+
+void
+log(int level, char *fmt, ...)
+{
+ char buffer[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf (buffer, 1024, fmt, ap);
+ va_end(ap);
+ buffer[1023]=0;
+
+ if (level < L_DEBUG) {
+ syslog(level, buffer);
+ }
+
+ if (opt_debug) {
+ time_t now;
+ struct tm * tm;
+
+ time(&now);
+ tm = localtime(&now);
+ fprintf (stderr, "%02d.%02d.%02d %02d:%02d:%02d %s[%d]: %s\n",
+ tm->tm_mday, tm->tm_mon, tm->tm_year,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ progname, mypid,
+ buffer);
+ }
+}
diff --git a/utils/statd/log.h b/utils/statd/log.h
new file mode 100644
index 0000000..f00bb63
--- /dev/null
+++ b/utils/statd/log.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 1995 Olaf Kirch
+ * Modified by Jeffrey A. Uphoff, 1996, 1997, 1999.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * logging functionality
+ * 260295 okir
+ */
+
+#ifndef _LOCKD_LOG_H_
+#define _LOCKD_LOG_H_
+
+#include <syslog.h>
+
+void log_init(char *name);
+void log_background(void);
+void log_enable(int facility);
+int log_enabled(int facility);
+void log(int level, char *fmt, ...);
+void die(char *fmt, ...);
+
+/*
+ * Map per-application severity to system severity. What's fatal for
+ * lockd is merely an itching spot from the universe's point of view.
+ */
+#define L_CRIT LOG_CRIT
+#define L_FATAL LOG_ERR
+#define L_ERROR LOG_WARNING
+#define L_WARNING LOG_NOTICE
+#define L_DEBUG LOG_DEBUG
+
+#ifdef DEBUG
+#define dprintf log
+#else
+#define dprintf if (0) log
+#endif
+
+#endif /* _LOCKD_LOG_H_ */
diff --git a/utils/statd/misc.c b/utils/statd/misc.c
new file mode 100644
index 0000000..42f6e57
--- /dev/null
+++ b/utils/statd/misc.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 1995-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include "statd.h"
+#include "notlist.h"
+
+/*
+ * Error-checking malloc() wrapper.
+ */
+void *
+xmalloc (size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ return ((void *)NULL);
+
+ if (!(ptr = malloc (size)))
+ /* SHIT! SHIT! SHIT! */
+ die ("malloc failed");
+
+ return (ptr);
+}
+
+
+/*
+ * Error-checking strdup() wrapper.
+ */
+char *
+xstrdup (const char *string)
+{
+ char *result;
+
+ /* Will only fail if underlying malloc() fails (ENOMEM). */
+ if (!(result = strdup (string)))
+ die ("strdup failed");
+
+ return (result);
+}
+
+
+/*
+ * Call with check=1 to verify that this host is not still on the rtnl
+ * before unlinking file.
+ */
+void
+xunlink (char *path, char *host, short int check)
+{
+ char *tozap;
+
+ tozap=alloca (strlen(path)+strlen(host)+2);
+ sprintf (tozap, "%s/%s", path, host);
+
+ if (!check || !nlist_gethost(rtnl, host, 0))
+ if (unlink (tozap) == -1)
+ log (L_ERROR, "unlink (%s): %s", tozap, strerror (errno));
+ else
+ dprintf (L_DEBUG, "Unlinked %s", tozap);
+ else
+ dprintf (L_DEBUG, "Not unlinking %s--host still monitored.", tozap);
+}
diff --git a/utils/statd/monitor.c b/utils/statd/monitor.c
new file mode 100644
index 0000000..5a782dc
--- /dev/null
+++ b/utils/statd/monitor.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 1995-1999 Jeffrey A. Uphoff
+ * Major rewrite by Olaf Kirch, Dec. 1996.
+ * Modified by H.J. Lu, 1998.
+ * Tighter access control, Olaf Kirch June 1999.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include "misc.h"
+#include "statd.h"
+#include "notlist.h"
+
+notify_list * rtnl = NULL; /* Run-time notify list. */
+
+
+/*
+ * Services SM_MON requests.
+ */
+struct sm_stat_res *
+sm_mon_1_svc(struct mon *argp, struct svc_req *rqstp)
+{
+ static sm_stat_res result;
+ char *mon_name = argp->mon_id.mon_name,
+ *my_name = argp->mon_id.my_id.my_name;
+ struct my_id *id = &argp->mon_id.my_id;
+ char *path;
+ int fd;
+ notify_list *clnt;
+ struct in_addr my_addr;
+#ifdef RESTRICTED_STATD
+ struct in_addr mon_addr, caller;
+#else
+ struct hostent *hostinfo = NULL;
+#endif
+
+ /* Assume that we'll fail. */
+ result.res_stat = STAT_FAIL;
+ result.state = -1; /* State is undefined for STAT_FAIL. */
+
+ /* Restrict access to statd.
+ * In the light of CERT CA-99.05, we tighten access to
+ * statd. --okir
+ */
+#ifdef RESTRICTED_STATD
+ /* 1. Reject anyone not calling from 127.0.0.1.
+ * Ignore the my_name specified by the caller, and
+ * use "127.0.0.1" instead.
+ */
+ caller = svc_getcaller(rqstp->rq_xprt)->sin_addr;
+ if (caller.s_addr != htonl(INADDR_LOOPBACK)) {
+ log(L_WARNING,
+ "Call to statd from non-local host %s",
+ inet_ntoa(caller));
+ goto failure;
+ }
+ my_addr.s_addr = htonl(INADDR_LOOPBACK);
+ my_name = "127.0.0.1";
+
+ /* 2. Reject any registrations for non-lockd services.
+ * This is specific to the linux kernel lockd, which
+ * makes the callback procedure part of the lockd interface.
+ */
+ if (id->my_proc != 100021) {
+ log(L_WARNING,
+ "Attempt to register callback to service %d",
+ id->my_proc);
+ goto failure;
+ }
+
+ /* 3. mon_name must be an address in dotted quad.
+ * Again, specific to the linux kernel lockd.
+ */
+ if (!inet_aton(mon_name, &mon_addr)) {
+ log(L_WARNING,
+ "Attempt to register host %s (not a dotted quad)",
+ mon_name);
+ goto failure;
+ }
+#else
+ /*
+ * Check hostnames. If I can't look them up, I won't monitor. This
+ * might not be legal, but it adds a little bit of safety and sanity.
+ */
+
+ /* must check for /'s in hostname! See CERT's CA-96.09 for details. */
+ if (strchr(mon_name, '/')) {
+ log(L_CRIT, "SM_MON request for hostname containing '/': %s",
+ mon_name);
+ log(L_CRIT, "POSSIBLE SPOOF/ATTACK ATTEMPT!");
+ goto failure;
+ } else if (gethostbyname(mon_name) == NULL) {
+ log(L_WARNING, "gethostbyname error for %s", mon_name);
+ goto failure;
+ } else if (!(hostinfo = gethostbyname(my_name))) {
+ log(L_WARNING, "gethostbyname error for %s", my_name);
+ goto failure;
+ } else
+ my_addr = *(struct in_addr *) hostinfo->h_addr;
+#endif
+
+ /*
+ * Hostnames checked OK.
+ * Now check to see if this is a duplicate, and warn if so.
+ * I will also return STAT_FAIL. (I *think* this is how I should
+ * handle it.)
+ *
+ * Olaf requests that I allow duplicate SM_MON requests for
+ * hosts due to the way he is coding lockd. No problem,
+ * I'll just do a quickie success return and things should
+ * be happy.
+ */
+ if (rtnl) {
+ notify_list *temp = rtnl;
+
+ while ((temp = nlist_gethost(temp, mon_name, 0))) {
+ if (matchhostname(NL_MY_NAME(temp), my_name) &&
+ NL_MY_PROC(temp) == id->my_proc &&
+ NL_MY_PROG(temp) == id->my_prog &&
+ NL_MY_VERS(temp) == id->my_vers) {
+ /* Hey! We already know you guys! */
+ dprintf(L_DEBUG,
+ "Duplicate SM_MON request for %s "
+ "from procedure on %s",
+ mon_name, my_name);
+
+ /* But we'll let you pass anyway. */
+ result.res_stat = STAT_SUCC;
+ result.state = MY_STATE;
+ return (&result);
+ }
+ temp = NL_NEXT(temp);
+ }
+ }
+
+ /*
+ * We're committed...ignoring errors. Let's hope that a malloc()
+ * doesn't fail. (I should probably fix this assumption.)
+ */
+ if (!(clnt = nlist_new(my_name, mon_name, 0))) {
+ log(L_WARNING, "out of memory");
+ goto failure;
+ }
+
+ NL_ADDR(clnt) = my_addr;
+ NL_MY_PROG(clnt) = id->my_prog;
+ NL_MY_VERS(clnt) = id->my_vers;
+ NL_MY_PROC(clnt) = id->my_proc;
+ memcpy(NL_PRIV(clnt), argp->priv, SM_PRIV_SIZE);
+
+ /*
+ * Now, Create file on stable storage for host.
+ */
+
+ path=xmalloc(strlen(SM_DIR)+strlen(mon_name)+2);
+ sprintf(path, SM_DIR "/%s", mon_name);
+ if ((fd = open(path, O_WRONLY|O_SYNC|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
+ /* Didn't fly. We won't monitor. */
+ log(L_ERROR, "creat(%s) failed: %m", path);
+ nlist_free(NULL, clnt);
+ free(path);
+ goto failure;
+ }
+ free(path);
+ nlist_insert(&rtnl, clnt);
+ close(fd);
+
+ result.res_stat = STAT_SUCC;
+ result.state = MY_STATE;
+ dprintf(L_DEBUG, "MONITORING %s for %s", mon_name, my_name);
+ return (&result);
+
+failure:
+ log(L_WARNING, "STAT_FAIL to %s for SM_MON of %s", my_name, mon_name);
+ return (&result);
+}
+
+
+/*
+ * Services SM_UNMON requests.
+ *
+ * There is no statement in the X/Open spec's about returning an error
+ * for requests to unmonitor a host that we're *not* monitoring. I just
+ * return the state of the NSM when I get such foolish requests for lack
+ * of any better ideas. (I also log the "offense.")
+ */
+struct sm_stat *
+sm_unmon_1_svc(struct mon_id *argp, struct svc_req *rqstp)
+{
+ static sm_stat result;
+ notify_list *clnt;
+ char *mon_name = argp->mon_name,
+ *my_name = argp->my_id.my_name;
+ struct my_id *id = &argp->my_id;
+
+ result.state = MY_STATE;
+
+ /* Check if we're monitoring anyone. */
+ if (!(clnt = rtnl)) {
+ log(L_WARNING,
+ "Received SM_UNMON request from %s for %s while not "
+ "monitoring any hosts.", my_name, argp->mon_name);
+ return (&result);
+ }
+
+ /*
+ * OK, we are. Now look for appropriate entry in run-time list.
+ * There should only be *one* match on this, since I block "duplicate"
+ * SM_MON calls. (Actually, duplicate calls are allowed, but only one
+ * entry winds up in the list the way I'm currently handling them.)
+ */
+ while ((clnt = nlist_gethost(clnt, mon_name, 0))) {
+ if (matchhostname(NL_MY_NAME(clnt), my_name) &&
+ NL_MY_PROC(clnt) == id->my_proc &&
+ NL_MY_PROG(clnt) == id->my_prog &&
+ NL_MY_VERS(clnt) == id->my_vers) {
+ /* Match! */
+ dprintf(L_DEBUG, "UNMONITORING %s for %s",
+ mon_name, my_name);
+ nlist_free(&rtnl, clnt);
+ xunlink(SM_DIR, mon_name, 1);
+
+ return (&result);
+ } else
+ clnt = NL_NEXT(clnt);
+ }
+
+ log(L_WARNING, "Received erroneous SM_UNMON request from %s for %s",
+ my_name, mon_name);
+ return (&result);
+}
+
+
+struct sm_stat *
+sm_unmon_all_1_svc(struct my_id *argp, struct svc_req *rqstp)
+{
+ short int count = 0;
+ static sm_stat result;
+ notify_list *clnt;
+
+ result.state = MY_STATE;
+
+ if (!(clnt = rtnl)) {
+ log(L_WARNING, "Received SM_UNMON_ALL request from %s "
+ "while not monitoring any hosts", argp->my_name);
+ return (&result);
+ }
+
+ while ((clnt = nlist_gethost(clnt, argp->my_name, 1))) {
+ if (NL_MY_PROC(clnt) == argp->my_proc &&
+ NL_MY_PROG(clnt) == argp->my_prog &&
+ NL_MY_VERS(clnt) == argp->my_vers) {
+ /* Watch stack! */
+ char mon_name[SM_MAXSTRLEN + 1];
+ notify_list *temp;
+
+ dprintf(L_DEBUG,
+ "UNMONITORING (SM_UNMON_ALL) %s for %s",
+ NL_MON_NAME(clnt), NL_MY_NAME(clnt));
+ strncpy(mon_name, NL_MON_NAME(clnt),
+ sizeof (mon_name) - 1);
+ mon_name[sizeof (mon_name) - 1] = '\0';
+ temp = NL_NEXT(clnt);
+ nlist_free(&rtnl, clnt);
+ xunlink(SM_DIR, mon_name, 1);
+ ++count;
+ clnt = temp;
+ } else
+ clnt = NL_NEXT(clnt);
+ }
+
+ if (!count) {
+ dprintf(L_DEBUG, "SM_UNMON_ALL request from %s with no "
+ "SM_MON requests from it.", argp->my_name);
+ }
+
+ return (&result);
+}
diff --git a/utils/statd/notify.c b/utils/statd/notify.c
new file mode 100644
index 0000000..89d2946
--- /dev/null
+++ b/utils/statd/notify.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, Oct. 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * NSM notify list handling.
+ */
+
+#include "config.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+#include "misc.h"
+#include "statd.h"
+#include "notlist.h"
+
+/*
+ * Initial (startup) notify list.
+ */
+notify_list *inl = NULL;
+
+
+/*
+ * Get list of hosts from stable storage, build list of hosts to
+ * contact. These hosts are added to the global RPC notify list
+ * which is processed as soon as statd enters svc_run.
+ */
+void
+notify_hosts(void)
+{
+ DIR *nld;
+ struct dirent *de;
+ notify_list *call;
+
+ if (!(nld = opendir(SM_BAK_DIR))) {
+ perror("opendir");
+ exit(errno);
+ }
+
+ while ((de = readdir(nld))) {
+ if (de->d_name[0] == '.')
+ continue;
+
+ /* The following can happen for loopback NFS mounts
+ * (e.g. with cfsd) */
+ if (matchhostname(de->d_name, MY_NAME)
+ || matchhostname(de->d_name, "localhost")) {
+ char *fname;
+ fname=xmalloc(strlen(SM_BAK_DIR)+sizeof(de->d_name)+2);
+ dprintf(L_DEBUG, "We're on our own notify list?!?");
+ sprintf(fname, SM_BAK_DIR "/%s", de->d_name);
+ if (unlink(fname))
+ log(L_ERROR, "unlink(%s): %s",
+ fname, strerror(errno));
+ free(fname);
+ continue;
+ }
+
+ call = nlist_new(MY_NAME, de->d_name, -1);
+ NL_TYPE(call) = NOTIFY_REBOOT;
+ nlist_insert(&notify, call);
+ }
+
+ if (closedir(nld) == -1) {
+ perror("closedir");
+ exit(1);
+ }
+}
diff --git a/utils/statd/notlist.c b/utils/statd/notlist.c
new file mode 100644
index 0000000..bc0c294
--- /dev/null
+++ b/utils/statd/notlist.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * Simple list management for notify list
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include "misc.h"
+#include "statd.h"
+#include "notlist.h"
+
+notify_list *
+nlist_new(char *my_name, char *mon_name, int state)
+{
+ notify_list *new;
+
+ if (!(new = (notify_list *) xmalloc(sizeof(notify_list))))
+ return NULL;
+ memset(new, 0, sizeof(*new));
+
+ NL_TIMES(new) = MAX_TRIES;
+ NL_STATE(new) = state;
+ if (!(NL_MY_NAME(new) = xstrdup(my_name))
+ || !(NL_MON_NAME(new) = xstrdup(mon_name)))
+ return NULL;
+
+ return new;
+}
+
+void
+nlist_insert(notify_list **head, notify_list *entry)
+{
+ notify_list *next = *head, *tail = entry;
+
+ /* Find end of list to be inserted */
+ while (tail->next)
+ tail = tail->next;
+
+ if (next)
+ next->prev = tail;
+ tail->next = next;
+ *head = entry;
+}
+
+void
+nlist_insert_timer(notify_list **head, notify_list *entry)
+{
+ /* Find first entry with higher timeout value */
+ while (*head && NL_WHEN(*head) <= NL_WHEN(entry))
+ head = &(*head)->next;
+ nlist_insert(head, entry);
+}
+
+void
+nlist_remove(notify_list **head, notify_list *entry)
+{
+ notify_list *prev = entry->prev,
+ *next = entry->next;
+
+ if (next)
+ next->prev = prev;
+ if (prev)
+ prev->next = next;
+ else
+ *head = next;
+ entry->next = entry->prev = NULL;
+}
+
+notify_list *
+nlist_clone(notify_list *entry)
+{
+ notify_list *new;
+
+ new = nlist_new(NL_MY_NAME(entry), NL_MON_NAME(entry), NL_STATE(entry));
+ NL_MY_PROG(new) = NL_MY_PROG(entry);
+ NL_MY_VERS(new) = NL_MY_VERS(entry);
+ NL_MY_PROC(new) = NL_MY_PROC(entry);
+ NL_ADDR(new) = NL_ADDR(entry);
+ memcpy(NL_PRIV(new), NL_PRIV(entry), SM_PRIV_SIZE);
+
+ return new;
+}
+
+void
+nlist_free(notify_list **head, notify_list *entry)
+{
+ if (head)
+ nlist_remove(head, entry);
+ if (NL_MY_NAME(entry))
+ free(NL_MY_NAME(entry));
+ if (NL_MON_NAME(entry))
+ free(NL_MON_NAME(entry));
+ free(entry);
+}
+
+void
+nlist_kill(notify_list **head)
+{
+ while (*head)
+ nlist_free(head, *head);
+}
+
+/*
+ * Walk a list looking for a matching name in the NL_MON_NAME field.
+ */
+notify_list *
+nlist_gethost(notify_list *list, char *host, int myname)
+{
+ notify_list *lp;
+
+ for (lp = list; lp; lp = lp->next) {
+ if (matchhostname(host, myname? NL_MY_NAME(lp) : NL_MON_NAME(lp)))
+ return lp;
+ }
+
+ return (notify_list *) NULL;
+}
diff --git a/utils/statd/notlist.h b/utils/statd/notlist.h
new file mode 100644
index 0000000..0c6709c
--- /dev/null
+++ b/utils/statd/notlist.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Major rewrite by Olaf Kirch, Dec. 1996.
+ *
+ * NSM for Linux.
+ */
+
+#include <netinet/in.h>
+
+/*
+ * Primary information structure.
+ */
+struct notify_list {
+ mon mon; /* Big honkin' NSM structure. */
+ struct in_addr addr; /* IP address for callback. */
+ unsigned short port; /* port number for callback */
+ short int times; /* Counter used for various things. */
+ int state; /* For storing notified state for callbacks. */
+ struct notify_list *next; /* Linked list forward pointer. */
+ struct notify_list *prev; /* Linked list backward pointer. */
+ u_int32_t xid; /* XID of MS_NOTIFY RPC call */
+ time_t when; /* notify: timeout for re-xmit */
+ int type; /* type of notify (REBOOT/CALLBACK) */
+};
+
+typedef struct notify_list notify_list;
+
+#define NOTIFY_REBOOT 0 /* notify remote of our reboot */
+#define NOTIFY_CALLBACK 1 /* notify client of remote reboot */
+
+/*
+ * Global Variables
+ */
+extern notify_list * rtnl; /* Run-time notify list */
+extern notify_list * notify; /* Pending RPC calls */
+
+/*
+ * List-handling functions
+ */
+extern notify_list * nlist_new(char *, char *, int);
+extern void nlist_insert(notify_list **, notify_list *);
+extern void nlist_remove(notify_list **, notify_list *);
+extern void nlist_insert_timer(notify_list **, notify_list *);
+extern notify_list * nlist_clone(notify_list *);
+extern void nlist_free(notify_list **, notify_list *);
+extern void nlist_kill(notify_list **);
+extern notify_list * nlist_gethost(notify_list *, char *, int);
+
+/*
+ * List-handling macros.
+ * THESE INHERIT INFORMATION FROM PREVIOUSLY-DEFINED MACROS.
+ * (So don't change their order unless you study them first!)
+ */
+#define NL_NEXT(L) ((L)->next)
+#define NL_FIRST NL_NEXT
+#define NL_PREV(L) ((L)->prev)
+#define NL_DATA(L) ((L)->mon)
+#define NL_ADDR(L) ((L)->addr)
+#define NL_STATE(L) ((L)->state)
+#define NL_TIMES(L) ((L)->times)
+#define NL_MON_ID(L) (NL_DATA((L)).mon_id)
+#define NL_PRIV(L) (NL_DATA((L)).priv)
+#define NL_MON_NAME(L) (NL_MON_ID((L)).mon_name)
+#define NL_MY_ID(L) (NL_MON_ID((L)).my_id)
+#define NL_MY_NAME(L) (NL_MY_ID((L)).my_name)
+#define NL_MY_PROC(L) (NL_MY_ID((L)).my_proc)
+#define NL_MY_PROG(L) (NL_MY_ID((L)).my_prog)
+#define NL_MY_VERS(L) (NL_MY_ID((L)).my_vers)
+#define NL_WHEN(L) ((L)->when)
+#define NL_TYPE(L) ((L)->type)
+
+#if 0
+#define NL_ADD_NO_ZERO(LIST, ITEM)\
+ NL_PREV(NL_FIRST((LIST))) = (ITEM);\
+ NL_NEXT((ITEM)) = NL_FIRST((LIST));\
+ NL_FIRST((LIST)) = (ITEM);\
+ NL_PREV((ITEM)) = (LIST);\
+ NL_TIMES((ITEM)) = 0;
+
+#define NL_ADD(LIST, ITEM)\
+ NL_ADD_NO_ZERO((LIST), (ITEM));\
+ NL_ADDR((ITEM)) = 0;\
+ NL_STATE((ITEM)) = 0;
+
+#define NL_DEL(ITEM)\
+ NL_NEXT(NL_PREV((ITEM))) = NL_NEXT((ITEM));\
+ NL_PREV(NL_NEXT((ITEM))) = NL_PREV((ITEM));
+
+#define NL_FREE(ITEM)\
+ if (NL_MY_NAME ((ITEM)))\
+ free (NL_MY_NAME ((ITEM)));\
+ if (NL_MON_NAME ((ITEM)))\
+ free (NL_MON_NAME((ITEM)));\
+ free ((ITEM));
+
+#define NL_DEL_FREE(ITEM)\
+ NL_DEL((ITEM))\
+ NL_FREE((ITEM))
+
+/* Yuck. Kludge. */
+#define NL_COPY(SRC, DEST)\
+ NL_TIMES((DEST)) = NL_TIMES((SRC));\
+ NL_STATE((DEST)) = NL_TIMES((SRC));\
+ NL_MY_PROC((DEST)) = NL_MY_PROC((SRC));\
+ NL_MY_PROG((DEST)) = NL_MY_PROG((SRC));\
+ NL_MY_VERS((DEST)) = NL_MY_VERS((SRC));\
+ NL_MON_NAME((DEST)) = xstrdup (NL_MON_NAME((SRC)));\
+ NL_MY_NAME((DEST)) = xstrdup (NL_MY_NAME((SRC)));\
+ memcpy (&NL_ADDR((DEST)), &NL_ADDR((SRC)), sizeof (u_long));\
+ memcpy (NL_PRIV((DEST)), NL_PRIV((SRC)), SM_PRIV_SIZE);
+#endif
diff --git a/utils/statd/rmtcall.c b/utils/statd/rmtcall.c
new file mode 100644
index 0000000..a08c4b1
--- /dev/null
+++ b/utils/statd/rmtcall.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 1996, 1999 Olaf Kirch
+ * Modified by Jeffrey A. Uphoff, 1997-1999.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * After reboot, notify all hosts on our notify list. In order not to
+ * hang statd with delivery to dead hosts, we perform all RPC calls in
+ * parallel.
+ *
+ * It would have been nice to use the portmapper's rmtcall feature,
+ * but that's not possible for security reasons (the portmapper would
+ * have to forward the call with root privs for most statd's, which
+ * it won't if it's worth its money).
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_prot.h>
+#include <rpc/pmap_rmt.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+#include "sm_inter.h"
+#include "statd.h"
+#include "notlist.h"
+#include "log.h"
+
+#define MAXMSGSIZE (2048 / sizeof(unsigned int))
+
+static unsigned long xid = 0; /* RPC XID counter */
+static int sockfd = -1; /* notify socket */
+
+/*
+ * Initialize callback socket
+ */
+static int
+get_socket(void)
+{
+ struct sockaddr_in sin;
+
+ if (sockfd >= 0)
+ return sockfd;
+
+ if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
+ log(L_CRIT, "Can't create socket: %m");
+ return -1;
+ }
+
+ FD_SET(sockfd, &SVC_FDSET);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ if (bindresvport(sockfd, &sin) < 0) {
+ dprintf(L_WARNING,
+ "process_hosts: can't bind to reserved port\n");
+ }
+
+ return sockfd;
+}
+
+/*
+ * Try to resolve host name for notify/callback request
+ *
+ * When compiled with RESTRICTED_STATD defined, we expect all
+ * host names to be dotted quads. See monitor.c for details. --okir
+ */
+#ifdef RESTRICTED_STATD
+static int
+try_to_resolve(notify_list *lp)
+{
+ char *hname;
+
+ if (NL_TYPE(lp) == NOTIFY_REBOOT)
+ hname = NL_MON_NAME(lp);
+ else
+ hname = NL_MY_NAME(lp);
+ if (!inet_aton(hname, &(NL_ADDR(lp)))) {
+ log(L_ERROR, "%s is not an dotted-quad address", hname);
+ NL_TIMES(lp) = 0;
+ return 0;
+ }
+
+ /* XXX: In order to handle multi-homed hosts, we could do
+ * a reverse lookup, a forward lookup, and cycle through
+ * all the addresses.
+ */
+ return 1;
+}
+#else
+static int
+try_to_resolve(notify_list *lp)
+{
+ struct hostent *hp;
+ char *hname;
+
+ if (NL_TYPE(lp) == NOTIFY_REBOOT)
+ hname = NL_MON_NAME(lp);
+ else
+ hname = NL_MY_NAME(lp);
+
+ dprintf(L_DEBUG, "Trying to resolve %s.", hname);
+ if (!(hp = gethostbyname(hname))) {
+ herror("gethostbyname");
+ NL_TIMES(lp) -= 1;
+ return 0;
+ }
+
+ if (hp->h_addrtype != AF_INET) {
+ log(L_ERROR, "%s is not an AF_INET address", hname);
+ NL_TIMES(lp) = 0;
+ return 0;
+ }
+
+ /* FIXME: should try all addresses for multi-homed hosts in
+ * alternation because one interface might be down/unreachable. */
+ NL_ADDR(lp) = *(struct in_addr *) hp->h_addr;
+
+ dprintf(L_DEBUG, "address of %s is %s", hname, inet_ntoa(NL_ADDR(lp)));
+ return 1;
+}
+#endif
+
+static unsigned long
+xmit_call(int sockfd, struct sockaddr_in *sin,
+ u_int32_t prog, u_int32_t vers, u_int32_t proc,
+ xdrproc_t func, void *obj)
+/* __u32 prog, __u32 vers, __u32 proc, xdrproc_t func, void *obj) */
+{
+ unsigned int msgbuf[MAXMSGSIZE], msglen;
+ struct rpc_msg mesg;
+ struct pmap pmap;
+ XDR xdr, *xdrs = &xdr;
+ int err;
+
+ if (!xid)
+ xid = getpid() + time(NULL);
+
+ mesg.rm_xid = ++xid;
+ mesg.rm_direction = CALL;
+ mesg.rm_call.cb_rpcvers = 2;
+ if (sin->sin_port == 0) {
+ sin->sin_port = htons(PMAPPORT);
+ mesg.rm_call.cb_prog = PMAPPROG;
+ mesg.rm_call.cb_vers = PMAPVERS;
+ mesg.rm_call.cb_proc = PMAPPROC_GETPORT;
+ pmap.pm_prog = prog;
+ pmap.pm_vers = vers;
+ pmap.pm_prot = IPPROTO_UDP;
+ pmap.pm_port = 0;
+ func = (xdrproc_t) xdr_pmap;
+ obj = &pmap;
+ } else {
+ mesg.rm_call.cb_prog = prog;
+ mesg.rm_call.cb_vers = vers;
+ mesg.rm_call.cb_proc = proc;
+ }
+ mesg.rm_call.cb_cred.oa_flavor = AUTH_NULL;
+ mesg.rm_call.cb_cred.oa_base = (caddr_t) NULL;
+ mesg.rm_call.cb_cred.oa_length = 0;
+ mesg.rm_call.cb_verf.oa_flavor = AUTH_NULL;
+ mesg.rm_call.cb_verf.oa_base = (caddr_t) NULL;
+ mesg.rm_call.cb_verf.oa_length = 0;
+
+ /* Create XDR memory object for encoding */
+ xdrmem_create(xdrs, (caddr_t) msgbuf, sizeof(msgbuf), XDR_ENCODE);
+
+ /* Encode the RPC header part and payload */
+ if (!xdr_callmsg(xdrs, &mesg) || !func(xdrs, obj)) {
+ dprintf(L_WARNING, "xmit_mesg: can't encode RPC message!\n");
+ xdr_destroy(xdrs);
+ return 0;
+ }
+
+ /* Get overall length of datagram */
+ msglen = xdr_getpos(xdrs);
+
+ if ((err = sendto(sockfd, msgbuf, msglen, 0,
+ (struct sockaddr *) sin, sizeof(*sin))) < 0) {
+ dprintf(L_WARNING, "xmit_mesg: sendto failed: %m");
+ } else if (err != msglen) {
+ dprintf(L_WARNING, "xmit_mesg: short write: %m\n");
+ }
+
+ xdr_destroy(xdrs);
+
+ return err == msglen? xid : 0;
+}
+
+static notify_list *
+recv_rply(int sockfd, struct sockaddr_in *sin, u_long *portp)
+{
+ unsigned int msgbuf[MAXMSGSIZE], msglen;
+ struct rpc_msg mesg;
+ notify_list *lp = NULL;
+ XDR xdr, *xdrs = &xdr;
+ int alen = sizeof(*sin);
+
+ /* Receive message */
+ if ((msglen = recvfrom(sockfd, msgbuf, sizeof(msgbuf), 0,
+ (struct sockaddr *) sin, &alen)) < 0) {
+ dprintf(L_WARNING, "recv_rply: recvfrom failed: %m");
+ return NULL;
+ }
+
+ /* Create XDR object for decoding buffer */
+ xdrmem_create(xdrs, (caddr_t) msgbuf, msglen, XDR_DECODE);
+
+ memset(&mesg, 0, sizeof(mesg));
+ mesg.rm_reply.rp_acpt.ar_results.where = NULL;
+ mesg.rm_reply.rp_acpt.ar_results.proc = (xdrproc_t) xdr_void;
+
+ if (!xdr_replymsg(xdrs, &mesg)) {
+ log(L_WARNING, "recv_rply: can't decode RPC message!\n");
+ goto done;
+ }
+
+ if (mesg.rm_reply.rp_stat != 0) {
+ log(L_WARNING, "recv_rply: [%s] RPC status %d\n",
+ inet_ntoa(sin->sin_addr),
+ mesg.rm_reply.rp_stat);
+ goto done;
+ }
+ if (mesg.rm_reply.rp_acpt.ar_stat != 0) {
+ log(L_WARNING, "recv_rply: [%s] RPC status %d\n",
+ inet_ntoa(sin->sin_addr),
+ mesg.rm_reply.rp_acpt.ar_stat);
+ goto done;
+ }
+
+ for (lp = notify; lp != NULL; lp = lp->next) {
+ if (lp->xid != xid)
+ continue;
+ if (lp->addr.s_addr != sin->sin_addr.s_addr) {
+ char addr [18];
+ strncpy (addr, inet_ntoa(lp->addr),
+ sizeof (addr) - 1);
+ addr [sizeof (addr) - 1] = '\0';
+ dprintf(L_WARNING, "address mismatch: "
+ "expected %s, got %s\n",
+ addr, inet_ntoa(sin->sin_addr));
+ }
+ if (lp->port == 0) {
+ if (!xdr_u_long(xdrs, portp)) {
+ log(L_WARNING, "recv_rply: [%s] "
+ "can't decode reply body!\n",
+ inet_ntoa(sin->sin_addr));
+ lp = NULL;
+ goto done;
+ }
+ }
+ break;
+ }
+
+done:
+ xdr_destroy(xdrs);
+ return lp;
+}
+
+/*
+ * Notify operation for a single list entry
+ */
+static int
+process_entry(int sockfd, notify_list *lp)
+{
+ struct sockaddr_in sin;
+ struct status new_status;
+ xdrproc_t func;
+ void *objp;
+ u_int32_t proc, vers, prog;
+/* __u32 proc, vers, prog; */
+
+ if (lp->addr.s_addr == INADDR_ANY && !try_to_resolve(lp))
+ return NL_TIMES(lp);
+ if (NL_TIMES(lp) == 0) {
+ log(L_DEBUG, "Cannot notify %s, giving up.\n",
+ inet_ntoa(NL_ADDR(lp)));
+ return 0;
+ }
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = lp->port;
+ sin.sin_addr = lp->addr;
+
+ switch (NL_TYPE(lp)) {
+ case NOTIFY_REBOOT:
+ prog = SM_PROG;
+ vers = SM_VERS;
+ proc = SM_NOTIFY;
+ func = (xdrproc_t) xdr_stat_chge;
+ objp = &SM_stat_chge;
+ break;
+ case NOTIFY_CALLBACK:
+ prog = NL_MY_PROG(lp);
+ vers = NL_MY_VERS(lp);
+ proc = NL_MY_PROC(lp);
+ func = (xdrproc_t) xdr_status;
+ objp = &new_status;
+ new_status.mon_name = NL_MON_NAME(lp);
+ new_status.state = NL_STATE(lp);
+ memcpy(new_status.priv, NL_PRIV(lp), SM_PRIV_SIZE);
+ break;
+ default:
+ log(L_ERROR, "notify_host: unknown notify type %d",
+ NL_TYPE(lp));
+ return 0;
+ }
+
+ lp->xid = xmit_call(sockfd, &sin, prog, vers, proc, func, objp);
+ if (!lp->xid) {
+ log(L_WARNING, "notify_host: failed to notify %s\n",
+ inet_ntoa(lp->addr));
+ }
+ NL_TIMES(lp) -= 1;
+
+ return 1;
+}
+
+/*
+ * Process a datagram received on the notify socket
+ */
+int
+process_reply(FD_SET_TYPE *rfds)
+{
+ struct sockaddr_in sin;
+ notify_list *lp;
+ u_long port;
+
+ if (sockfd == -1 || !FD_ISSET(sockfd, rfds))
+ return 0;
+
+ if (!(lp = recv_rply(sockfd, &sin, &port)))
+ return 1;
+
+ if (lp->port == 0) {
+ if (port != 0) {
+ lp->port = htons((unsigned short) port);
+ process_entry(sockfd, lp);
+ NL_WHEN(lp) = time(NULL) + NOTIFY_TIMEOUT;
+ nlist_remove(&notify, lp);
+ nlist_insert_timer(&notify, lp);
+ return 1;
+ }
+ log(L_WARNING, "recv_rply: [%s] service %d not registered",
+ inet_ntoa(lp->addr),
+ NL_TYPE(lp) == NOTIFY_REBOOT? SM_PROG : NL_MY_PROG(lp));
+ } else if (NL_TYPE(lp) == NOTIFY_REBOOT) {
+ dprintf(L_DEBUG, "Notification of %s succeeded.",
+ NL_MON_NAME(lp));
+ xunlink(SM_BAK_DIR, NL_MON_NAME(lp), 0);
+ } else {
+ dprintf(L_DEBUG, "Callback to %s (for %d) succeeded.",
+ NL_MY_NAME(lp), NL_MON_NAME(lp));
+ }
+ nlist_free(&notify, lp);
+ return 1;
+}
+
+/*
+ * Process a notify list, either for notifying remote hosts after reboot
+ * or for calling back (local) statd clients when the remote has notified
+ * us of a crash.
+ */
+int
+process_notify_list(void)
+{
+ notify_list *entry;
+ time_t now;
+ int fd;
+
+ if ((fd = get_socket()) < 0)
+ return 0;
+
+ while ((entry = notify) != NULL && NL_WHEN(entry) < time(&now)) {
+ if (process_entry(fd, entry)) {
+ NL_WHEN(entry) = time(NULL) + NOTIFY_TIMEOUT;
+ nlist_remove(&notify, entry);
+ nlist_insert_timer(&notify, entry);
+ } else if (NL_TYPE(entry) == NOTIFY_CALLBACK) {
+ log(L_ERROR,
+ "Can't callback %s (%d,%d), giving up.",
+ NL_MY_NAME(entry),
+ NL_MY_PROG(entry),
+ NL_MY_VERS(entry));
+ nlist_free(&notify, entry);
+ } else {
+ log(L_ERROR,
+ "Can't notify %s, giving up.",
+ NL_MON_NAME(entry));
+ xunlink(SM_BAK_DIR, NL_MON_NAME(entry), 0);
+ nlist_free(&notify, entry);
+ }
+ }
+
+ return 1;
+}
diff --git a/utils/statd/sim_sm_inter.x b/utils/statd/sim_sm_inter.x
new file mode 100644
index 0000000..4346199
--- /dev/null
+++ b/utils/statd/sim_sm_inter.x
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+#ifdef RPC_CLNT
+%#include <string.h>
+#endif
+
+program SIM_SM_PROG {
+ version SIM_SM_VERS {
+ void SIM_SM_MON(struct status) = 1;
+ } = 1;
+} = 200048;
+
+const SM_MAXSTRLEN = 1024;
+const SM_PRIV_SIZE = 16;
+
+/*
+ * structure of the status message sent back by the status monitor
+ * when monitor site status changes
+ */
+%#ifndef SM_INTER_X
+struct status {
+ string mon_name<SM_MAXSTRLEN>;
+ int state;
+ opaque priv[SM_PRIV_SIZE]; /* stored private information */
+};
+%#endif /* SM_INTER_X */
diff --git a/utils/statd/simu.c b/utils/statd/simu.c
new file mode 100644
index 0000000..fa4e3a6
--- /dev/null
+++ b/utils/statd/simu.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#include "statd.h"
+#include "notlist.h"
+
+extern void my_svc_exit (void);
+
+
+/*
+ * Services SM_SIMU_CRASH requests.
+ */
+void *
+sm_simu_crash_1_svc (void *argp, struct svc_req *rqstp)
+{
+ static char *result = NULL;
+
+ log (L_WARNING, "*** SIMULATING CRASH! ***");
+ my_svc_exit ();
+
+ if (rtnl)
+ nlist_kill (&rtnl);
+
+ return ((void *)&result);
+}
diff --git a/utils/statd/simulate.c b/utils/statd/simulate.c
new file mode 100644
index 0000000..4b8d59c
--- /dev/null
+++ b/utils/statd/simulate.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 1995-1997, 1999 Jeffrey A. Uphoff
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#ifndef SIMULATIONS
+# error How the hell did we get here?
+#endif
+
+/* If we're running the simulator, we're debugging. Pretty simple. */
+#ifndef DEBUG
+# define DEBUG
+#endif
+
+#include <signal.h>
+#include <string.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include "statd.h"
+#include "sim_sm_inter.h"
+
+static void daemon_simulator (void);
+static void sim_killer (int sig);
+static void simulate_crash (char *);
+static void simulate_mon (char *, char *, char *, char *, char *);
+static void simulate_stat (char *, char *);
+static void simulate_unmon (char *, char *, char *, char *);
+static void simulate_unmon_all (char *, char *, char *);
+
+static int sim_port = 0;
+
+extern void sim_sm_prog_1 (struct svc_req *, register SVCXPRT);
+extern void svc_exit (void);
+
+void
+simulator (int argc, char **argv)
+{
+ log_enable (1);
+
+ if (argc == 2)
+ if (!strcasecmp (*argv, "crash"))
+ simulate_crash (*(&argv[1]));
+
+ if (argc == 3) {
+ if (!strcasecmp (*argv, "stat"))
+ simulate_stat (*(&argv[1]), *(&argv[2]));
+ }
+ if (argc == 4) {
+ if (!strcasecmp (*argv, "unmon_all"))
+ simulate_unmon_all (*(&argv[1]), *(&argv[2]), *(&argv[3]));
+ }
+ if (argc == 5) {
+ if (!strcasecmp (*argv, "unmon"))
+ simulate_unmon (*(&argv[1]), *(&argv[2]), *(&argv[3]), *(&argv[4]));
+ }
+ if (argc == 6) {
+ if (!strcasecmp (*argv, "mon"))
+ simulate_mon (*(&argv[1]), *(&argv[2]), *(&argv[3]), *(&argv[4]),
+ *(&argv[5]));
+ }
+ die ("WTF? Give me something I can use!");
+}
+
+static void
+simulate_mon (char *calling, char *monitoring, char *as, char *proggy,
+ char *fool)
+{
+ CLIENT *client;
+ sm_stat_res *result;
+ mon mon;
+
+ dprintf (L_DEBUG, "Calling %s (as %s) to monitor %s", calling, as,
+ monitoring);
+
+ if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
+ die ("%s", clnt_spcreateerror ("clnt_create"));
+
+ memcpy (mon.priv, fool, SM_PRIV_SIZE);
+ mon.mon_id.my_id.my_name = xstrdup (as);
+ sim_port = atoi (proggy) * SIM_SM_PROG;
+ mon.mon_id.my_id.my_prog = sim_port; /* Pseudo-dummy */
+ mon.mon_id.my_id.my_vers = SIM_SM_VERS;
+ mon.mon_id.my_id.my_proc = SIM_SM_MON;
+ mon.mon_id.mon_name = monitoring;
+
+ if (!(result = sm_mon_1 (&mon, client)))
+ die ("%s", clnt_sperror (client, "sm_mon_1"));
+
+ free (mon.mon_id.my_id.my_name);
+
+ if (result->res_stat != STAT_SUCC) {
+ log (L_FATAL, "SM_MON request failed, state: %d", result->state);
+ exit (0);
+ } else {
+ dprintf (L_DEBUG, "SM_MON result successful, state: %d\n", result->state);
+ dprintf (L_DEBUG, "Waiting for callback.");
+ daemon_simulator ();
+ exit (0);
+ }
+}
+
+static void
+simulate_unmon (char *calling, char *unmonitoring, char *as, char *proggy)
+{
+ CLIENT *client;
+ sm_stat *result;
+ mon_id mon_id;
+
+ dprintf (L_DEBUG, "Calling %s (as %s) to unmonitor %s", calling, as,
+ unmonitoring);
+
+ if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
+ die ("%s", clnt_spcreateerror ("clnt_create"));
+
+ mon_id.my_id.my_name = xstrdup (as);
+ mon_id.my_id.my_prog = atoi (proggy) * SIM_SM_PROG;
+ mon_id.my_id.my_vers = SIM_SM_VERS;
+ mon_id.my_id.my_proc = SIM_SM_MON;
+ mon_id.mon_name = unmonitoring;
+
+ if (!(result = sm_unmon_1 (&mon_id, client)))
+ die ("%s", clnt_sperror (client, "sm_unmon_1"));
+
+ free (mon_id.my_id.my_name);
+ dprintf (L_DEBUG, "SM_UNMON request returned state: %d\n", result->state);
+ exit (0);
+}
+
+static void
+simulate_unmon_all (char *calling, char *as, char *proggy)
+{
+ CLIENT *client;
+ sm_stat *result;
+ my_id my_id;
+
+ dprintf (L_DEBUG, "Calling %s (as %s) to unmonitor all hosts", calling, as);
+
+ if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
+ die ("%s", clnt_spcreateerror ("clnt_create"));
+
+ my_id.my_name = xstrdup (as);
+ my_id.my_prog = atoi (proggy) * SIM_SM_PROG;
+ my_id.my_vers = SIM_SM_VERS;
+ my_id.my_proc = SIM_SM_MON;
+
+ if (!(result = sm_unmon_all_1 (&my_id, client)))
+ die ("%s", clnt_sperror (client, "sm_unmon_all_1"));
+
+ free (my_id.my_name);
+ dprintf (L_DEBUG, "SM_UNMON_ALL request returned state: %d\n", result->state);
+ exit (0);
+}
+
+static void
+simulate_crash (char *host)
+{
+ CLIENT *client;
+
+ if ((client = clnt_create (host, SM_PROG, SM_VERS, "udp")) == NULL)
+ die ("%s", clnt_spcreateerror ("clnt_create"));
+
+ if (!sm_simu_crash_1 (NULL, client))
+ die ("%s", clnt_sperror (client, "sm_simu_crash_1"));
+
+ exit (0);
+}
+
+static void
+simulate_stat (char *calling, char *monitoring)
+{
+ CLIENT *client;
+ sm_name checking;
+ sm_stat_res *result;
+
+ if ((client = clnt_create (calling, SM_PROG, SM_VERS, "udp")) == NULL)
+ die ("%s", clnt_spcreateerror ("clnt_create"));
+
+ checking.mon_name = monitoring;
+
+ if (!(result = sm_stat_1 (&checking, client)))
+ die ("%s", clnt_sperror (client, "sm_stat_1"));
+
+ if (result->res_stat == STAT_SUCC)
+ dprintf (L_DEBUG, "STAT_SUCC from %s for %s, state: %d", calling,
+ monitoring, result->state);
+ else
+ dprintf (L_DEBUG, "STAT_FAIL from %s for %s, state: %d", calling,
+ monitoring, result->state);
+
+ exit (0);
+}
+
+static void
+sim_killer (int sig)
+{
+ log (L_FATAL, "Simulator caught signal %d, un-registering and exiting.", sig);
+ pmap_unset (sim_port, SIM_SM_VERS);
+ exit (0);
+}
+
+static void
+daemon_simulator (void)
+{
+ signal (SIGHUP, sim_killer);
+ signal (SIGINT, sim_killer);
+ signal (SIGTERM, sim_killer);
+ pmap_unset (sim_port, SIM_SM_VERS);
+ do_regist (sim_port, sim_sm_prog_1);
+/* do_regist (sim_port, (__dispatch_fn_t)sim_sm_prog_1); */
+ svc_run ();
+ pmap_unset (sim_port, SIM_SM_VERS);
+}
+
+void *
+sim_sm_mon_1_svc (struct status *argp, struct svc_req *rqstp)
+{
+ static char *result;
+
+ dprintf (L_DEBUG, "Recieved state %d for mon_name %s (opaque \"%s\")",
+ argp->state, argp->mon_name, argp->priv);
+ svc_exit ();
+ return ((void *)&result);
+}
diff --git a/utils/statd/sm_inter.x b/utils/statd/sm_inter.x
new file mode 100644
index 0000000..5232a28
--- /dev/null
+++ b/utils/statd/sm_inter.x
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 1986 Sun Microsystems, Inc.
+ * Modified by Jeffrey A. Uphoff, 1995, 1997-1999.
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part. Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+
+/*
+ * Status monitor protocol specification
+ */
+
+#ifdef RPC_CLNT
+%#include <string.h>
+#endif
+
+program SM_PROG {
+ version SM_VERS {
+ /* res_stat = stat_succ if status monitor agrees to monitor */
+ /* res_stat = stat_fail if status monitor cannot monitor */
+ /* if res_stat == stat_succ, state = state number of site sm_name */
+ struct sm_stat_res SM_STAT(struct sm_name) = 1;
+
+ /* res_stat = stat_succ if status monitor agrees to monitor */
+ /* res_stat = stat_fail if status monitor cannot monitor */
+ /* stat consists of state number of local site */
+ struct sm_stat_res SM_MON(struct mon) = 2;
+
+ /* stat consists of state number of local site */
+ struct sm_stat SM_UNMON(struct mon_id) = 3;
+
+ /* stat consists of state number of local site */
+ struct sm_stat SM_UNMON_ALL(struct my_id) = 4;
+
+ void SM_SIMU_CRASH(void) = 5;
+
+ void SM_NOTIFY(struct stat_chge) = 6;
+
+ } = 1;
+} = 100024;
+
+const SM_MAXSTRLEN = 1024;
+const SM_PRIV_SIZE = 16;
+
+struct sm_name {
+ string mon_name<SM_MAXSTRLEN>;
+};
+
+struct my_id {
+ string my_name<SM_MAXSTRLEN>; /* name of the site iniates the monitoring request*/
+ int my_prog; /* rpc program # of the requesting process */
+ int my_vers; /* rpc version # of the requesting process */
+ int my_proc; /* rpc procedure # of the requesting process */
+};
+
+struct mon_id {
+ string mon_name<SM_MAXSTRLEN>; /* name of the site to be monitored */
+ struct my_id my_id;
+};
+
+
+struct mon {
+ struct mon_id mon_id;
+ opaque priv[SM_PRIV_SIZE]; /* private information to store at monitor for requesting process */
+};
+
+struct stat_chge {
+ string mon_name<SM_MAXSTRLEN>; /* name of the site that had the state change */
+ int state;
+};
+
+/*
+ * state # of status monitor monitonically increases each time
+ * status of the site changes:
+ * an even number (>= 0) indicates the site is down and
+ * an odd number (> 0) indicates the site is up;
+ */
+struct sm_stat {
+ int state; /* state # of status monitor */
+};
+
+enum res {
+ stat_succ = 0, /* status monitor agrees to monitor */
+ stat_fail = 1 /* status monitor cannot monitor */
+};
+
+struct sm_stat_res {
+ res res_stat;
+ int state;
+};
+
+/*
+ * structure of the status message sent back by the status monitor
+ * when monitor site status changes
+ */
+struct status {
+ string mon_name<SM_MAXSTRLEN>;
+ int state;
+ opaque priv[SM_PRIV_SIZE]; /* stored private information */
+};
+
+%#define SM_INTER_X
diff --git a/utils/statd/stat.c b/utils/statd/stat.c
new file mode 100644
index 0000000..021e786
--- /dev/null
+++ b/utils/statd/stat.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 1995, 1997, 1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, 1996.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#include <netdb.h>
+#include "statd.h"
+
+/*
+ * Services SM_STAT requests.
+ *
+ * According the the X/Open spec's on this procedure: "Implementations
+ * should not rely on this procedure being operative. In many current
+ * implementations of the NSM it will always return a 'STAT_FAIL'
+ * status." My implementation is operative; it returns 'STAT_SUCC'
+ * whenever it can resolve the hostname that it's being asked to
+ * monitor, and returns 'STAT_FAIL' otherwise.
+ */
+struct sm_stat_res *
+sm_stat_1_svc (struct sm_name *argp, struct svc_req *rqstp)
+{
+ static sm_stat_res result;
+
+ if (gethostbyname (argp->mon_name) == NULL) {
+ log (L_WARNING, "gethostbyname error for %s", argp->mon_name);
+ result.res_stat = STAT_FAIL;
+ dprintf (L_DEBUG, "STAT_FAIL for %s", argp->mon_name);
+ } else {
+ result.res_stat = STAT_SUCC;
+ dprintf (L_DEBUG, "STAT_SUCC for %s", argp->mon_name);
+ }
+ result.state = MY_STATE;
+ return(&result);
+}
diff --git a/utils/statd/statd.c b/utils/statd/statd.c
new file mode 100644
index 0000000..3b76e30
--- /dev/null
+++ b/utils/statd/statd.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 1995, 1997-1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, Oct. 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#include <limits.h>
+#include <signal.h>
+#include <unistd.h>
+#include <string.h>
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#include "statd.h"
+#include "version.h"
+
+short int restart = 0;
+int _rpcpmstart = 0; /* flags for tirpc rpcgen */
+int _rpcfdtype = 0;
+int _rpcsvcdirty = 0;
+
+extern void sm_prog_1 (struct svc_req *, register SVCXPRT);
+
+#ifdef SIMULATIONS
+extern void simulator (int, char **);
+#endif
+
+
+/*
+ * Signal handler.
+ */
+static void
+killer (int sig)
+{
+ log (L_FATAL, "Caught signal %d, un-registering and exiting.", sig);
+ pmap_unset (SM_PROG, SM_VERS);
+ exit (0);
+}
+
+
+/*
+ * Entry routine/main loop.
+ */
+int
+main (int argc, char **argv)
+{
+ int pid;
+ int foreground = 0;
+
+ log_init (argv[0]);
+
+ if (argc == 2 && strcmp (argv [1], "-F") == 0) {
+ foreground = 1;
+ argc--;
+ argv++;
+ }
+
+#ifdef SIMULATIONS
+ if (argc > 1)
+ simulator (--argc, ++argv); /* simulator() does exit() */
+#endif
+
+ if (!foreground) {
+ int filedes;
+
+ if ((pid = fork ()) < 0) {
+ perror ("Could not fork");
+ exit (1);
+ } else if (pid != 0) {
+ /* Parent. */
+ exit (0);
+ }
+ /* Child. */
+ setsid ();
+ chdir (DIR_BASE);
+
+ for (filedes = 0; filedes < OPEN_MAX; filedes++) {
+ close (filedes);
+ }
+ }
+
+ /* Child. */
+ signal (SIGHUP, killer);
+ signal (SIGINT, killer);
+ signal (SIGTERM, killer);
+
+ for (;;) {
+ pmap_unset (SM_PROG, SM_VERS);
+ change_state ();
+ shuffle_dirs ();
+ notify_hosts ();
+ ++restart;
+ do_regist (SM_PROG, sm_prog_1);
+ my_svc_run (); /* I rolled my own, Olaf made it better... */
+ }
+ return 0;
+}
+
+
+/*
+ * Register services.
+ */
+void
+do_regist(u_long prog, void (*sm_prog_1)())
+/* do_regist(u_long prog, __dispatch_fn_t sm_prog_1) */
+{
+ SVCXPRT *transp;
+
+ if ((transp = svcudp_create(RPC_ANYSOCK)) == NULL)
+ die("cannot create udp service.");
+
+ if (!svc_register(transp, prog, SM_VERS, sm_prog_1, IPPROTO_UDP))
+ die("unable to register (SM_PROG, SM_VERS, udp).");
+
+ if ((transp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL)
+ die("cannot create tcp service.");
+
+ if (!svc_register(transp, prog, SM_VERS, sm_prog_1, IPPROTO_TCP))
+ die("unable to register (SM_PROG, SM_VERS, tcp).");
+}
diff --git a/utils/statd/statd.h b/utils/statd/statd.h
new file mode 100644
index 0000000..77a179a
--- /dev/null
+++ b/utils/statd/statd.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 1995-1997, 1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, Dec. 1996.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#include "sm_inter.h"
+#include "system.h"
+#include "log.h"
+
+/*
+ * Paths and filenames.
+ */
+#if defined(NFS_STATEDIR)
+# define DIR_BASE NFS_STATEDIR "/"
+#else
+# define DIR_BASE "/var/lib/nfs/"
+#endif
+#define SM_DIR DIR_BASE "sm"
+#define SM_BAK_DIR DIR_BASE "sm.bak"
+#define SM_STAT_PATH DIR_BASE "state"
+
+/*
+ * Status definitions.
+ */
+#define STAT_FAIL stat_fail
+#define STAT_SUCC stat_succ
+
+/*
+ * Function prototypes.
+ */
+extern void change_state(void);
+extern void do_regist(u_long, void (*)());
+extern void my_svc_run(void);
+extern void notify_hosts(void);
+extern void shuffle_dirs(void);
+extern int process_notify_list(void);
+extern int process_reply(FD_SET_TYPE *);
+extern char * xstrdup(const char *);
+extern void * xmalloc(size_t);
+extern void xunlink (char *, char *, short int);
+
+/*
+ * Host status structure and macros.
+ */
+stat_chge SM_stat_chge;
+#define MY_NAME SM_stat_chge.mon_name
+#define MY_STATE SM_stat_chge.state
+
+/*
+ * Some timeout values. (Timeout values are in whole seconds.)
+ */
+#define CALLBACK_TIMEOUT 3 /* For client call-backs. */
+#define NOTIFY_TIMEOUT 5 /* For status-change notifications. */
+#define SELECT_TIMEOUT 10 /* Max select() timeout when work to do. */
+#define MAX_TRIES 5 /* Max number of tries for any host. */
diff --git a/utils/statd/statd.man b/utils/statd/statd.man
new file mode 100644
index 0000000..373cf77
--- /dev/null
+++ b/utils/statd/statd.man
@@ -0,0 +1,53 @@
+.\"
+.\" statd(8)
+.\"
+.\" Copyright (C) 1999 Olaf Kirch <okir@monad.swb.de>
+.\" Modified by Jeffrey A. Uphoff, 1999.
+.TH rpc.statd 8 "11 June 1999"
+.SH NAME
+rpc.statd \- NSM status monitor
+.SH SYNOPSIS
+.B "/usr/sbin/rpc.statd [-F]
+.SH DESCRIPTION
+The
+.B rpc.statd
+server implements the NSM (Network Status Monitor) RPC protocol.
+This service is somewhat misnomed, since it doesn't actually provide
+active monitoring as one might suspect; instead, NSM implements a
+reboot notification service. It is used by the NFS file locking service,
+.BR rpc.lockd ,
+to implement lock recovery when the NFS server machine crashes and
+reboots.
+.SS Operation
+For each NFS client or server machine to be monitored,
+.B rpc.statd
+creates a file in
+.BR /var/lib/nfs/sm .
+When starting, it iterates through these files and notifies the
+peer
+.B rpc.statd
+on those machines.
+.SH OPTIONS
+.TP
+.B -F
+By default,
+.B rpc.statd
+forks and puts itself in the background when started. The
+.B -F
+argument tells it to remain in the foreground. This option is
+mainly for debugging purposes.
+.SH FILES
+.BR /var/lib/nfs/sm/state
+.br
+.BR /var/lib/nfs/sm/*
+.br
+.BR /var/lib/nfs/sm.bak/*
+.SH SEE ALSO
+.BR rpc.nfsd(8)
+.SH AUTHORS
+.br
+Jeff Uphoff <juphoff@transmeta.com>
+.br
+Olaf Kirch <okir@monad.swb.de>
+.br
+H.J. Lu <hjl@gnu.org>
diff --git a/utils/statd/state.c b/utils/statd/state.c
new file mode 100644
index 0000000..101c00b
--- /dev/null
+++ b/utils/statd/state.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 1995-1997, 1999 Jeffrey A. Uphoff
+ * Modified by Olaf Kirch, 1996.
+ * Modified by H.J. Lu, 1998.
+ *
+ * NSM for Linux.
+ */
+
+#include "config.h"
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include "statd.h"
+
+
+/*
+ * Most NSM's keep the status number in an ASCII file. I'm keeping it
+ * as an int (4-byte binary) for now...
+ */
+void
+change_state (void)
+{
+ int fd, size;
+ extern short int restart;
+
+ if ((fd = open (SM_STAT_PATH, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) == -1)
+ die ("open (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if ((size = read (fd, &MY_STATE, sizeof MY_STATE)) == -1)
+ die ("read (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if (size != 0 && size != sizeof MY_STATE) {
+ log (L_ERROR, "Error in status file format...correcting.");
+
+ if (close (fd) == -1)
+ die ("close (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if ((fd = creat (SM_STAT_PATH, S_IRUSR | S_IWUSR)) == -1)
+ die ("creat (%s): %s", SM_STAT_PATH, strerror (errno));
+ }
+ log (L_DEBUG, "New state: %u", (++MY_STATE % 2) ? MY_STATE : ++MY_STATE);
+
+ if (lseek (fd, 0, SEEK_SET) == -1)
+ die ("lseek (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if (write (fd, &MY_STATE, sizeof MY_STATE) != sizeof MY_STATE)
+ die ("write (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if (fsync (fd) == -1)
+ log (L_ERROR, "fsync (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if (close (fd) == -1)
+ log (L_ERROR, "close (%s): %s", SM_STAT_PATH, strerror (errno));
+
+ if (!restart) {
+ char fullhost[SM_MAXSTRLEN + 1];
+ struct hostent *hostinfo;
+
+ if (gethostname (fullhost, SM_MAXSTRLEN) == -1)
+ die ("gethostname: %s", strerror (errno));
+
+ if ((hostinfo = gethostbyname (fullhost)) == NULL)
+ log (L_ERROR, "gethostbyname error for %s", fullhost);
+ else {
+ strncpy (fullhost, hostinfo->h_name, sizeof (fullhost) - 1);
+ fullhost[sizeof (fullhost) - 1] = '\0';
+ }
+
+ MY_NAME = xstrdup (fullhost);
+ }
+}
+
+
+/*
+ * Fairly traditional use of two directories for this.
+ */
+void
+shuffle_dirs (void)
+{
+ DIR *nld;
+ struct dirent *de;
+ struct stat st;
+ char *src, *dst;
+ int len1, len2, len;
+
+ if (stat (SM_DIR, &st) == -1 && errno != ENOENT)
+ die ("stat (%s): %s", SM_DIR, strerror (errno));
+
+ if (!S_ISDIR (st.st_mode))
+ if (mkdir (SM_DIR, S_IRWXU) == -1)
+ die ("mkdir (%s): %s", SM_DIR, strerror (errno));
+
+ memset (&st, 0, sizeof st);
+
+ if (stat (SM_BAK_DIR, &st) == -1 && errno != ENOENT)
+ die ("stat (%s): %s", SM_BAK_DIR, strerror (errno));
+
+ if (!S_ISDIR (st.st_mode))
+ if (mkdir (SM_BAK_DIR, S_IRWXU) == -1)
+ die ("mkdir (%s): %s", SM_BAK_DIR, strerror (errno));
+
+ if (!(nld = opendir (SM_DIR)))
+ die ("opendir (%s): %s", SM_DIR, strerror (errno));
+
+ len1=strlen(SM_DIR);
+ len2=strlen(SM_BAK_DIR);
+ while ((de = readdir (nld))) {
+ if (de->d_name[0] == '.')
+ continue;
+ len=strlen(de->d_name);
+ src=xmalloc(len1+len+2);
+ dst=xmalloc(len2+len+2);
+ sprintf (src, "%s/%s", SM_DIR, de->d_name);
+ sprintf (dst, "%s/%s", SM_BAK_DIR, de->d_name);
+ if (rename (src, dst) == -1)
+ die ("rename (%s to %s): %s", SM_DIR, SM_BAK_DIR, strerror (errno));
+ free(src);
+ free(dst);
+ }
+ if (closedir (nld) == -1)
+ log (L_ERROR, "closedir (%s): %s", SM_DIR, strerror (errno));
+}
diff --git a/utils/statd/svc_run.c b/utils/statd/svc_run.c
new file mode 100644
index 0000000..8f6d9fe
--- /dev/null
+++ b/utils/statd/svc_run.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 1984 Sun Microsystems, Inc.
+ * Modified by Jeffrey A. Uphoff, 1995, 1997-1999.
+ * Modified by Olaf Kirch, 1996.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
+ * unrestricted use provided that this legend is included on all tape
+ * media and as a part of the software program in whole or part. Users
+ * may copy or modify Sun RPC without charge, but are not authorized
+ * to license or distribute it to anyone else except as part of a product or
+ * program developed by the user.
+ *
+ * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
+ * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
+ *
+ * Sun RPC is provided with no support and without any obligation on the
+ * part of Sun Microsystems, Inc. to assist in its use, correction,
+ * modification or enhancement.
+ *
+ * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
+ * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
+ * OR ANY PART THEREOF.
+ *
+ * In no event will Sun Microsystems, Inc. be liable for any lost revenue
+ * or profits or other special, indirect and consequential damages, even if
+ * Sun has been advised of the possibility of such damages.
+ *
+ * Sun Microsystems, Inc.
+ * 2550 Garcia Avenue
+ * Mountain View, California 94043
+ */
+
+/*
+ * This has been modified for my own evil purposes to prevent deadlocks
+ * when two hosts start NSM's simultaneously and try to notify each
+ * other (which mainly occurs during testing), or to stop and smell the
+ * roses when I have callbacks due.
+ * --Jeff Uphoff.
+ */
+
+/*
+ * This is the RPC server side idle loop.
+ * Wait for input, call server program.
+ */
+#include "config.h"
+#include <errno.h>
+#include "statd.h"
+#include "notlist.h"
+
+static int svc_stop = 0;
+
+/*
+ * This is the global notify list onto which all SM_NOTIFY and CALLBACK
+ * requests are put.
+ */
+notify_list * notify = NULL;
+
+/*
+ * Jump-off function.
+ */
+void
+my_svc_exit(void)
+{
+ svc_stop = 1;
+}
+
+
+/*
+ * The heart of the server. A crib from libc for the most part...
+ */
+void
+my_svc_run(void)
+{
+ FD_SET_TYPE readfds;
+ int selret;
+ time_t now;
+
+ svc_stop = 0;
+
+ for (;;) {
+ if (svc_stop)
+ return;
+
+ /* Ah, there are some notifications to be processed */
+ while (notify && NL_WHEN(notify) <= time(&now)) {
+ process_notify_list();
+ }
+
+ readfds = SVC_FDSET;
+ if (notify) {
+ struct timeval tv;
+
+ tv.tv_sec = NL_WHEN(notify) - now;
+ tv.tv_usec = 0;
+ dprintf(L_DEBUG, "Waiting for reply... (timeo %d)",
+ tv.tv_sec);
+ selret = select(FD_SETSIZE, &readfds,
+ (void *) 0, (void *) 0, &tv);
+ } else {
+ dprintf(L_DEBUG, "Waiting for client connections.");
+ selret = select(FD_SETSIZE, &readfds,
+ (void *) 0, (void *) 0, (struct timeval *) 0);
+ }
+
+ switch (selret) {
+ case -1:
+ if (errno == EINTR || errno == ECONNREFUSED
+ || errno == ENETUNREACH || errno == EHOSTUNREACH)
+ continue;
+ log(L_ERROR, "my_svc_run() - select: %m");
+ return;
+
+ case 0:
+ /* A notify/callback timed out. */
+ continue;
+
+ default:
+ selret -= process_reply(&readfds);
+ if (selret)
+ svc_getreqset(&readfds);
+ }
+ }
+}
diff --git a/utils/statd/system.h b/utils/statd/system.h
new file mode 100644
index 0000000..a1739c4
--- /dev/null
+++ b/utils/statd/system.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 1996 Olaf Kirch
+ * Modified by Jeffrey A. Uphoff, 1997, 1999.
+ *
+ * NSM for Linux.
+ */
+
+/*
+ * System-dependent declarations
+ */
+
+#ifdef FD_SETSIZE
+# define FD_SET_TYPE fd_set
+# define SVC_FDSET svc_fdset
+#else
+# define FD_SET_TYPE int
+# define SVC_FDSET svc_fds
+#endif
diff --git a/utils/statd/version.h b/utils/statd/version.h
new file mode 100644
index 0000000..12f16bd
--- /dev/null
+++ b/utils/statd/version.h
@@ -0,0 +1,7 @@
+/*
+ * Copyright (C) 1997-1999 Jeffrey A. Uphoff
+ *
+ * NSM for Linux.
+ */
+
+#define STATD_RELEASE "1.1.1"