diff options
author | hjl <hjl> | 1999-10-18 23:21:12 +0000 |
---|---|---|
committer | hjl <hjl> | 1999-10-18 23:21:12 +0000 |
commit | 8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9 (patch) | |
tree | 0904ef8554ed680fe3244fa618685e1fb7ea148b /utils | |
download | nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.tar.gz nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.tar.xz nfs-utils-8b7ad01b14df1e7529b9ba8a1ea17df0d6004ef9.zip |
Initial revision
Diffstat (limited to 'utils')
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(¬ify, 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(¬ify, 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(¬ify, lp); + nlist_insert_timer(¬ify, 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(¬ify, 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(¬ify, entry); + nlist_insert_timer(¬ify, 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(¬ify, 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(¬ify, 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" |