diff options
Diffstat (limited to 'nfs')
-rw-r--r-- | nfs/Makefile | 32 | ||||
-rw-r--r-- | nfs/cache.c | 211 | ||||
-rw-r--r-- | nfs/consts.c | 25 | ||||
-rw-r--r-- | nfs/main.c | 438 | ||||
-rw-r--r-- | nfs/mount.c | 277 | ||||
-rw-r--r-- | nfs/mount.h | 40 | ||||
-rw-r--r-- | nfs/name-cache.c | 305 | ||||
-rw-r--r-- | nfs/nfs-spec.h | 168 | ||||
-rw-r--r-- | nfs/nfs.c | 681 | ||||
-rw-r--r-- | nfs/nfs.h | 204 | ||||
-rw-r--r-- | nfs/ops.c | 1925 | ||||
-rw-r--r-- | nfs/rpc.c | 425 | ||||
-rw-r--r-- | nfs/storage-info.c | 104 |
13 files changed, 4835 insertions, 0 deletions
diff --git a/nfs/Makefile b/nfs/Makefile new file mode 100644 index 00000000..d814f67d --- /dev/null +++ b/nfs/Makefile @@ -0,0 +1,32 @@ +# Copyright (C) 1995, 1996, 1997, 2000, 2001, 2008, 2011, 2012 Free Software +# Foundation, Inc. +# +# Written by Michael I. Bushnell. +# +# This file is part of the GNU Hurd. +# +# The GNU Hurd 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. +# +# The GNU Hurd 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 Mass Ave, Cambridge, MA 02139, USA. + +dir := nfs +makemode := server + +target = nfs +SRCS = ops.c rpc.c mount.c nfs.c cache.c consts.c main.c name-cache.c \ + storage-info.c +OBJS = $(SRCS:.c=.o) +HURDLIBS = netfs fshelp iohelp ports ihash shouldbeinlibc +OTHERLIBS = -lpthread + +include ../Makeconf diff --git a/nfs/cache.c b/nfs/cache.c new file mode 100644 index 00000000..506b90ff --- /dev/null +++ b/nfs/cache.c @@ -0,0 +1,211 @@ +/* cache.c - Node cache management for NFS client implementation. + Copyright (C) 1995, 1996, 1997, 2002 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#include "nfs.h" + +#include <string.h> +#include <stdio.h> +#include <netinet/in.h> + +/* Hash table containing all the nodes currently active. XXX Was 512, + however, a prime is much nicer for the hash function. 509 is nice + as not only is it prime, it also keeps the array within a page or + two. */ +#define CACHESIZE 509 +static struct node *nodehash [CACHESIZE]; + +/* Compute and return a hash key for NFS file handle DATA of LEN + bytes. */ +static inline int +hash (int *data, size_t len) +{ + unsigned int h = 0; + char *cp = (char *)data; + int i; + + for (i = 0; i < len; i++) + h += cp[i]; + + return h % CACHESIZE; +} + +/* Lookup the file handle P (length LEN) in the hash table. If it is + not present, initialize a new node structure and insert it into the + hash table. Whichever course, a new reference is generated and the + node is returned in *NPP; the lock on the node, (*NPP)->LOCK, is + held. */ +void +lookup_fhandle (void *p, size_t len, struct node **npp) +{ + struct node *np; + struct netnode *nn; + int h; + + h = hash (p, len); + + pthread_spin_lock (&netfs_node_refcnt_lock); + for (np = nodehash[h]; np; np = np->nn->hnext) + { + if (np->nn->handle.size != len + || memcmp (np->nn->handle.data, p, len) != 0) + continue; + + np->references++; + pthread_spin_unlock (&netfs_node_refcnt_lock); + pthread_mutex_lock (&np->lock); + *npp = np; + return; + } + + /* Could not find it */ + nn = malloc (sizeof (struct netnode)); + assert (nn); + + nn->handle.size = len; + memcpy (nn->handle.data, p, len); + nn->stat_updated = 0; + nn->dtrans = NOT_POSSIBLE; + nn->dead_dir = 0; + nn->dead_name = 0; + + np = netfs_make_node (nn); + pthread_mutex_lock (&np->lock); + nn->hnext = nodehash[h]; + if (nn->hnext) + nn->hnext->nn->hprevp = &nn->hnext; + nn->hprevp = &nodehash[h]; + nodehash[h] = np; + + pthread_spin_unlock (&netfs_node_refcnt_lock); + + *npp = np; +} + +/* Package holding args to forked_node_delete. */ +struct fnd +{ + struct node *dir; + char *name; +}; + +/* Worker function to delete nodes that don't have any more local + references or links. */ +void * +forked_node_delete (void *arg) +{ + struct fnd *args = arg; + + pthread_mutex_lock (&args->dir->lock); + netfs_attempt_unlink ((struct iouser *)-1, args->dir, args->name); + netfs_nput (args->dir); + free (args->name); + free (args); + return 0; +}; + +/* Called by libnetfs when node NP has no more references. (See + <hurd/libnetfs.h> for details. Just clear its local state and + remove it from the hash table. Called and expected to leave with + NETFS_NODE_REFCNT_LOCK held. */ +void +netfs_node_norefs (struct node *np) +{ + if (np->nn->dead_dir) + { + struct fnd *args; + pthread_t thread; + error_t err; + + args = malloc (sizeof (struct fnd)); + assert (args); + + np->references++; + pthread_spin_unlock (&netfs_node_refcnt_lock); + + args->dir = np->nn->dead_dir; + args->name = np->nn->dead_name; + np->nn->dead_dir = 0; + np->nn->dead_name = 0; + netfs_nput (np); + + /* Do this in a separate thread so that we don't wait for it; it + acquires a lock on the dir, which we are not allowed to + do. */ + err = pthread_create (&thread, NULL, forked_node_delete, args); + if (!err) + pthread_detach (thread); + else + { + errno = err; + perror ("pthread_create"); + } + + /* Caller expects us to leave this locked... */ + pthread_spin_lock (&netfs_node_refcnt_lock); + } + else + { + *np->nn->hprevp = np->nn->hnext; + if (np->nn->hnext) + np->nn->hnext->nn->hprevp = np->nn->hprevp; + if (np->nn->dtrans == SYMLINK) + free (np->nn->transarg.name); + free (np->nn); + free (np); + } +} + +/* Change the file handle used for node NP to be the handle at P. + Make sure the hash table stays up to date. Return the address + after the handle. The lock on the node should be held. */ +int * +recache_handle (int *p, struct node *np) +{ + int h; + size_t len; + + if (protocol_version == 2) + len = NFS2_FHSIZE; + else + { + len = ntohl (*p); + p++; + } + + /* Unlink it */ + pthread_spin_lock (&netfs_node_refcnt_lock); + *np->nn->hprevp = np->nn->hnext; + if (np->nn->hnext) + np->nn->hnext->nn->hprevp = np->nn->hprevp; + + /* Change the name */ + np->nn->handle.size = len; + memcpy (np->nn->handle.data, p, len); + + /* Reinsert it */ + h = hash (p, len); + np->nn->hnext = nodehash[h]; + if (np->nn->hnext) + np->nn->hnext->nn->hprevp = &np->nn->hnext; + np->nn->hprevp = &nodehash[h]; + + pthread_spin_unlock (&netfs_node_refcnt_lock); + return p + len / sizeof (int); +} diff --git a/nfs/consts.c b/nfs/consts.c new file mode 100644 index 00000000..9146615b --- /dev/null +++ b/nfs/consts.c @@ -0,0 +1,25 @@ +/* Definition of constants required by libnetfs + Copyright (C) 1995, 1996 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#include "nfs.h" + +/* Maximum number of times to walk through symlinks before returning + ELOOP. See <hurd/libnetfs.h> for details. */ +int netfs_maxsymlinks = 8; diff --git a/nfs/main.c b/nfs/main.c new file mode 100644 index 00000000..cd1c29a0 --- /dev/null +++ b/nfs/main.c @@ -0,0 +1,438 @@ +/* + Copyright (C) 1996, 1997, 2002 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#include <hurd/netfs.h> +#include <sys/socket.h> +#include <stdio.h> +#include <device/device.h> +#include "nfs.h" +#include <netinet/in.h> +#include <unistd.h> +#include <string.h> +#include <maptime.h> +#include <argp.h> +#include <argz.h> +#include <error.h> +#include <version.h> + +char *netfs_server_name = "nfs"; +char *netfs_server_version = HURD_VERSION; + +extern char *localhost (); + +/* Default number of times to retry RPCs when mounted soft. */ +#define DEFAULT_SOFT_RETRIES 3 + +/* Default number of seconds to timeout cached stat information. */ +#define DEFAULT_STAT_TIMEOUT 3 + +/* Default number of seconds to timeout cached file contents. */ +#define DEFAULT_CACHE_TIMEOUT 3 + +/* Default number of seconds to timeout cache positive dir hits. */ +#define DEFAULT_NAME_CACHE_TIMEOUT 3 + +/* Default number of seconds to timeout cache negative dir hits. */ +#define DEFAULT_NAME_CACHE_NEG_TIMEOUT 3 + +/* Default maximum number of bytes to read at once. */ +#define DEFAULT_READ_SIZE 8192 + +/* Default maximum number of bytes to write at once. */ +#define DEFAULT_WRITE_SIZE 8192 + + +/* Number of seconds to timeout cached stat information. */ +int stat_timeout = DEFAULT_STAT_TIMEOUT; + +/* Number of seconds to timeout cached file contents. */ +int cache_timeout = DEFAULT_CACHE_TIMEOUT; + +/* Number of seconds to timeout cached positive dir hits. */ +int name_cache_timeout = DEFAULT_NAME_CACHE_TIMEOUT; + +/* Number of seconds to timeout cached negative dir hits. */ +int name_cache_neg_timeout = DEFAULT_NAME_CACHE_NEG_TIMEOUT; + +/* Number of seconds to wait for first retransmission of an RPC. */ +int initial_transmit_timeout = 1; + +/* Maximum number of seconds to wait between retransmission of RPCs. */ +int max_transmit_timeout = 30; + +/* Maximum number of retries to send when mounted soft. */ +int soft_retries = DEFAULT_SOFT_RETRIES; + +/* True iff we are mounted soft. */ +int mounted_soft = 0; + +/* Maximum number of bytes to read at once. */ +int read_size = DEFAULT_READ_SIZE; + +/* Maximum number of bytes to write at once. */ +int write_size = DEFAULT_WRITE_SIZE; + +#define OPT_SOFT 's' +#define OPT_HARD 'h' +#define OPT_RSIZE 'R' +#define OPT_WSIZE 'W' +#define OPT_STAT_TO -2 +#define OPT_CACHE_TO -3 +#define OPT_INIT_TR_TO -4 +#define OPT_MAX_TR_TO -5 +#define OPT_MNT_PORT -6 +#define OPT_MNT_PORT_D -7 +#define OPT_NFS_PORT -8 +#define OPT_NFS_PORT_D -9 +#define OPT_HOLD -10 +#define OPT_MNT_PROG -11 +#define OPT_NFS_PROG -12 +#define OPT_PMAP_PORT -13 +#define OPT_NCACHE_TO -14 +#define OPT_NCACHE_NEG_TO -15 + +/* Return a string corresponding to the printed rep of DEFAULT_what */ +#define ___D(what) #what +#define __D(what) ___D(what) +#define _D(what) __D(DEFAULT_ ## what) + +const char *argp_program_version = STANDARD_HURD_VERSION (nfs); + +/* Options usable both at startup and at runtime. */ +static const struct argp_option common_options[] = +{ + {0,0,0,0,0,1}, + {"soft", OPT_SOFT, "RETRIES", OPTION_ARG_OPTIONAL, + "File system requests will eventually fail, after RETRIES tries" + " (default " _D(SOFT_RETRIES) ")" }, + {"hard", OPT_HARD, 0, 0, + "Retry file systems requests until they succeed"}, + + {0,0,0,0,0,2}, + {"read-size", OPT_RSIZE, "BYTES", 0, + "Max packet size for reads (default " _D(READ_SIZE) ")"}, + {"rsize",0,0,OPTION_ALIAS}, + {"write-size", OPT_WSIZE, "BYTES", 0, + "Max packet size for writes (default " _D(WRITE_SIZE)")"}, + {"wsize",0,0,OPTION_ALIAS}, + + {0,0,0,0,"Timeouts:",3}, + {"stat-timeout", OPT_STAT_TO, "SEC", 0, + "Timeout for cached stat information (default " _D(STAT_TIMEOUT) ")"}, + {"cache-timeout", OPT_CACHE_TO, "SEC", 0, + "Timeout for cached file data (default " _D(CACHE_TIMEOUT) ")"}, + {"name-cache-timeout", OPT_NCACHE_TO, "SEC", 0, + "Timeout for positive directory cache entries (default " + _D(NAME_CACHE_TIMEOUT) ")"}, + {"name-cache-neg-timeout", OPT_NCACHE_NEG_TO, "SEC", 0, + "Timeout for negative directory cache entires (default " + _D(NAME_CACHE_NEG_TIMEOUT) ")"}, + {"init-transmit-timeout", OPT_INIT_TR_TO,"SEC", 0}, + {"max-transmit-timeout", OPT_MAX_TR_TO, "SEC", 0}, + + {0} +}; + +static error_t +parse_common_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case OPT_SOFT: + mounted_soft = 1; + if (arg) + soft_retries = atoi (arg); + break; + case OPT_HARD: + mounted_soft = 0; + break; + + case OPT_RSIZE: read_size = atoi (arg); break; + case OPT_WSIZE: write_size = atoi (arg); break; + + case OPT_STAT_TO: stat_timeout = atoi (arg); break; + case OPT_CACHE_TO: cache_timeout = atoi (arg); break; + case OPT_INIT_TR_TO: initial_transmit_timeout = atoi (arg); break; + case OPT_MAX_TR_TO: max_transmit_timeout = atoi (arg); break; + case OPT_NCACHE_TO: name_cache_timeout = atoi (arg); break; + case OPT_NCACHE_NEG_TO: name_cache_neg_timeout = atoi (arg); break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Options usable only at startup. */ +static const struct argp_option startup_options[] = { + {0,0,0,0,"Server specification:",10}, + {"mount-port", OPT_MNT_PORT, "PORT", 0, + "Port for mount server"}, + {"default-mount-port", OPT_MNT_PORT_D,"PORT", 0, + "Port for mount server, if none can be found automatically"}, + {"mount-program", OPT_MNT_PROG, "ID[.VERS]"}, + + {"nfs-port", OPT_NFS_PORT, "PORT", 0, + "Port for nfs operations"}, + {"default-nfs-port", OPT_NFS_PORT_D,"PORT", 0, + "Port for nfs operations, if none can be found automatically"}, + {"nfs-program", OPT_NFS_PROG, "ID[.VERS]"}, + + {"pmap-port", OPT_PMAP_PORT, "SVC|PORT"}, + + {"hold", OPT_HOLD, 0, OPTION_HIDDEN}, /* */ + { 0 } +}; +static char *args_doc = "REMOTE_FS [HOST]"; +static char *doc = "Hurd nfs translator" +"\vIf HOST is not specified, an attempt is made to extract" +" it from REMOTE_FS using either the `HOST:FS' or `FS@HOST' notations."; + +static const struct argp_child +runtime_argp_children[] = { {&netfs_std_runtime_argp}, {0} }; +static struct argp +runtime_argp = { common_options, parse_common_opt, 0, 0, + runtime_argp_children }; + +/* Used by netfs_set_options to handle runtime option parsing. */ +struct argp *netfs_runtime_argp = &runtime_argp; + +/* Where to find the remote filesystem. */ +static char *remote_fs; /* = 0; */ +static char *host; /* = 0; */ + +/* Return an argz string describing the current options. Fill *ARGZ + with a pointer to newly malloced storage holding the list and *LEN + to the length of that storage. */ +error_t +netfs_append_args (char **argz, size_t *argz_len) +{ + char buf[80]; + error_t err = 0; + +#define FOPT(fmt, arg) \ + do { \ + if (! err) \ + { \ + snprintf (buf, sizeof buf, fmt, arg); \ + err = argz_add (argz, argz_len, buf); \ + } \ + } while (0) + + if (mounted_soft) + FOPT ("--soft=%d", soft_retries); + else + err = argz_add (argz, argz_len, "--hard"); + + FOPT ("--read-size=%d", read_size); + FOPT ("--write-size=%d", write_size); + + FOPT ("--stat-timeout=%d", stat_timeout); + FOPT ("--cache-timeout=%d", cache_timeout); + FOPT ("--init-transmit-timeout=%d", initial_transmit_timeout); + FOPT ("--max-transmit-timeout=%d", max_transmit_timeout); + FOPT ("--name-cache-timeout=%d", name_cache_timeout); + FOPT ("--name-cache-neg-timeout=%d", name_cache_neg_timeout); + + if (! err) + err = netfs_append_std_options (argz, argz_len); + + if (! err) + { + char *fs; + if (asprintf (&fs, "%s:%s", host, remote_fs)) + { + err = argz_add (argz, argz_len, fs); + free (fs); + } + else + err = ENOMEM; + } + + return err; +} + +/* The user may define this function. The function must set source to + the source of CRED. The function may return an EOPNOTSUPP to + indicate that the concept of a source device is not applicable. The + default function always returns EOPNOTSUPP. */ +error_t +netfs_get_source (struct protid *cred, char *source, size_t source_len) +{ + if (! cred) + return EOPNOTSUPP; + + snprintf (source, source_len, "%s:%s", host, remote_fs); + return 0; +} + +/* Extract the host and remote filesystem names from SPEC, which should use + either HOST:FS or FS@HOST notation. Returns the malloced storage into + which both REMOTE_FS and HOST point, or 0 if SPEC is invalid. */ +static char * +extract_nfs_args (char *spec, char **remote_fs, char **host) +{ + char *sep; + + spec = strdup (spec); /* So we can trash it. */ + if (! spec) + return NULL; + + sep = index (spec, ':'); + if (sep) + { + *sep++ = '\0'; + *host = spec; + *remote_fs = sep; + return spec; + } + + sep = index (spec, '@'); + if (sep) + { + *sep++ = '\0'; + *host = sep; + *remote_fs = spec; + return spec; + } + + free (spec); + + return 0; +} + +static error_t +parse_startup_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case OPT_MNT_PORT: + mount_port_override = 1; + /* fall through */ + case OPT_MNT_PORT_D: + mount_port = atoi (arg); + break; + + case OPT_NFS_PORT: + nfs_port_override = 1; + /* fall through */ + case OPT_NFS_PORT_D: + nfs_port = atoi (arg); + break; + + case ARGP_KEY_ARG: + if (state->arg_num == 0) + remote_fs = arg; + else if (state->arg_num == 1) + host = arg; + else + return ARGP_ERR_UNKNOWN; + break; + + case ARGP_KEY_END: + if (!host && !extract_nfs_args (remote_fs, &remote_fs, &host)) + argp_error (state, "No HOST specified"); + break; + + case ARGP_KEY_NO_ARGS: + argp_error (state, "No REMOTE_FS specified"); + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + + +/* NFS client main program */ +int +main (int argc, char **argv) +{ + pthread_t thread; + error_t err; + + struct argp common_argp = { common_options, parse_common_opt }; + const struct argp_child argp_children[] = + { {&common_argp}, {&netfs_std_startup_argp}, {0} }; + struct argp argp = + { startup_options, parse_startup_opt, args_doc, doc, argp_children }; + mach_port_t bootstrap; + struct sockaddr_in addr; + int ret; + + argp_parse (&argp, argc, argv, 0, 0, 0); + + task_get_bootstrap_port (mach_task_self (), &bootstrap); + netfs_init (); + + main_udp_socket = socket (PF_INET, SOCK_DGRAM, 0); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons (IPPORT_RESERVED); + do + { + addr.sin_port = htons (ntohs (addr.sin_port) - 1); + ret = bind (main_udp_socket, (struct sockaddr *)&addr, + sizeof (struct sockaddr_in)); + if (ret == -1 && errno == EACCES) + { + /* We aren't allowed privileged ports; no matter; + let the server deny us later if it wants. */ + ret = 0; + break; + } + } + while ((ret == -1) && (errno == EADDRINUSE)); + if (ret == -1) + error (1, errno, "binding main udp socket"); + + err = maptime_map (0, 0, &mapped_time); + if (err) + error (2, err, "mapping time"); + + err = pthread_create (&thread, NULL, timeout_service_thread, NULL); + if (!err) + pthread_detach (thread); + else + { + errno = err; + perror ("pthread_create"); + } + err = pthread_create (&thread, NULL, rpc_receive_thread, NULL); + if (!err) + pthread_detach (thread); + else + { + errno = err; + perror ("pthread_create"); + } + + hostname = localhost (); + + netfs_root_node = mount_root (remote_fs, host); + + if (!netfs_root_node) + exit (1); + + netfs_startup (bootstrap, 0); + + for (;;) + netfs_server_loop (); +} diff --git a/nfs/mount.c b/nfs/mount.c new file mode 100644 index 00000000..29b203aa --- /dev/null +++ b/nfs/mount.c @@ -0,0 +1,277 @@ +/* + Copyright (C) 1995,96,97,98,2001,02 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#define malloc a_byte_for_every_bozotic_sun_lossage_and_youd_need_a_lotta_ram +#include <rpc/types.h> +#undef TRUE /* Get rid of sun defs. */ +#undef FALSE +#undef malloc +#include <rpc/pmap_prot.h> +#include <errno.h> +#include <error.h> +#include <sys/socket.h> +#include <netdb.h> +#include <string.h> +#include <netinet/in.h> +#include <stdio.h> + +#include "nfs.h" +#include "mount.h" + +/* Service name for portmapper */ +char *pmap_service_name = "sunrpc"; + +/* Fallback port number for portmapper */ +short pmap_service_number = PMAPPORT; + +/* RPC program for mount server. */ +int mount_program = MOUNTPROG; + +/* RPC version for mount server. */ +int mount_version = MOUNTVERS; + +/* Fallback port number for mount server. */ +short mount_port = 0; + +/* True iff MOUNT_PORT should be used even if portmapper present. */ +int mount_port_override = 0; + +/* RPC program number for NFS server. */ +int nfs_program = NFS_PROGRAM; + +/* RPC version number for NFS server. */ +int nfs_version = NFS_VERSION; + +/* Fallback port number for NFS server. */ +short nfs_port = NFS_PORT; + +/* True iff NFS_PORT should be used even if portmapper present. */ +int nfs_port_override = 0; + +/* Host name and port number we actually decided to use. */ +const char *mounted_hostname; +uint16_t mounted_nfs_port; /* host order */ + +int protocol_version = 2; + +/* Set up an RPC for procedure PROCNUM for talking to the portmapper. + Allocate storage with malloc and point *BUF at it; caller must free + this when done. Return the address where the args for the + procedure should be placed. */ +static int * +pmap_initialize_rpc (int procnum, void **buf) +{ + return initialize_rpc (PMAPPROG, PMAPVERS, procnum, 0, buf, 0, 0, -1); +} + +/* Set up an RPC for procedure PROCNUM for talking to the mount + server. Allocate storage with malloc and point *BUF at it; caller + must free this when done. Return the address where the args for + the procedure should be placed. */ +static int * +mount_initialize_rpc (int procnum, void **buf) +{ + return initialize_rpc (MOUNTPROG, MOUNTVERS, procnum, 0, buf, 0, 0, -1); +} + +/* Using the mount protocol, lookup NAME at host HOST. + Return a node for it or null for an error. If an + error occurs, a message is automatically sent to stderr. */ +struct node * +mount_root (char *name, char *host) +{ + struct sockaddr_in addr; + struct hostent *h; + int *p; + void *rpcbuf; + int port; + error_t err; + struct node *np; + short pmapport; + + /* Lookup the portmapper port number */ + if (pmap_service_name) + { + struct servent *s; + + /* XXX This will always fail! pmap_service_name will always be "sunrpc" + What should pmap_service_name really be? By definition the second + argument is either "tcp" or "udp" Thus, is this backwards + (as service_name suggests)? If so, should it read: + s = getservbyname (pmap_service_name, "udp"); + or is there something I am missing here? */ + s = getservbyname ("sunrpc", pmap_service_name); + if (s) + pmapport = s->s_port; + else + pmapport = htons (pmap_service_number); + } + else + pmapport = htons (pmap_service_number); + + /* Lookup the host */ + h = gethostbyname (host); + if (!h) + { + herror (host); + return 0; + } + + addr.sin_family = h->h_addrtype; + memcpy (&addr.sin_addr, h->h_addr_list[0], h->h_length); + addr.sin_port = pmapport; + + if (mount_port_override) + addr.sin_port = htons (mount_port); + else + { + /* Formulate and send a PMAPPROC_GETPORT request + to lookup the mount program on the server. */ + if (connect (main_udp_socket, (struct sockaddr *)&addr, + sizeof (struct sockaddr_in)) == -1) + { + error (0, errno, "server mount program"); + return 0; + } + + p = pmap_initialize_rpc (PMAPPROC_GETPORT, &rpcbuf); + if (! p) + { + error (0, errno, "creating rpc packet"); + return 0; + } + + *(p++) = htonl (MOUNTPROG); + *(p++) = htonl (MOUNTVERS); + *(p++) = htonl (IPPROTO_UDP); + *(p++) = htonl (0); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + port = ntohl (*p); + p++; + addr.sin_port = htons (port); + } + else if (mount_port) + addr.sin_port = htons (mount_port); + else + { + error (0, err, "portmap of mount"); + goto error_with_rpcbuf; + } + free (rpcbuf); + } + + /* Now, talking to the mount program, fetch a file handle + for the root. */ + if (connect (main_udp_socket, (struct sockaddr *) &addr, + sizeof (struct sockaddr_in)) == -1) + { + error (0, errno, "connect"); + goto error_with_rpcbuf; + } + + p = mount_initialize_rpc (MOUNTPROC_MNT, &rpcbuf); + if (! p) + { + error (0, errno, "rpc"); + goto error_with_rpcbuf; + } + + p = xdr_encode_string (p, name); + err = conduct_rpc (&rpcbuf, &p); + if (err) + { + error (0, err, "%s", name); + goto error_with_rpcbuf; + } + /* XXX Protocol spec says this should be a "unix error code"; we'll + pretend that an NFS error code is what's meant; the numbers match + anyhow. */ + err = nfs_error_trans (htonl (*p)); + p++; + if (err) + { + error (0, err, "%s", name); + goto error_with_rpcbuf; + } + + /* Create the node for root */ + xdr_decode_fhandle (p, &np); + free (rpcbuf); + pthread_mutex_unlock (&np->lock); + + if (nfs_port_override) + port = nfs_port; + else + { + /* Send another PMAPPROC_GETPORT request to lookup the nfs server. */ + addr.sin_port = pmapport; + if (connect (main_udp_socket, (struct sockaddr *) &addr, + sizeof (struct sockaddr_in)) == -1) + { + error (0, errno, "connect"); + return 0; + } + + p = pmap_initialize_rpc (PMAPPROC_GETPORT, &rpcbuf); + if (! p) + { + error (0, errno, "rpc"); + goto error_with_rpcbuf; + } + *(p++) = htonl (NFS_PROGRAM); + *(p++) = htonl (NFS_VERSION); + *(p++) = htonl (IPPROTO_UDP); + *(p++) = htonl (0); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + port = ntohl (*p); + p++; + } + else if (nfs_port) + port = nfs_port; + else + { + error (0, err, "portmap of nfs server"); + goto error_with_rpcbuf; + } + free (rpcbuf); + } + + addr.sin_port = htons (port); + if (connect (main_udp_socket, (struct sockaddr *) &addr, + sizeof (struct sockaddr_in)) == -1) + { + error (0, errno, "connect"); + return 0; + } + + mounted_hostname = host; + mounted_nfs_port = port; + + return np; + +error_with_rpcbuf: + free (rpcbuf); + + return 0; +} diff --git a/nfs/mount.h b/nfs/mount.h new file mode 100644 index 00000000..4cb62a83 --- /dev/null +++ b/nfs/mount.h @@ -0,0 +1,40 @@ +/* Manifest constants describing the mount protocol + Copyright (C) 1995, 1996 Free Software Foundation, Inc. + Written by Michael I. Bushnell, p/BSG. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +/* These constants define the RPC mount protocol; see RFC 1094. */ + +#ifndef NFS_MOUNT_H +#define NFS_MOUNT_H + +#define MOUNTPROG 100005 +#define MOUNTVERS 1 + +/* Obnoxious arbitrary limits */ +#define MOUNT_MNTPATHLEN 1024 +#define MOUNT_MNTNAMLEN 255 + +#define MOUNTPROC_NULL 0 +#define MOUNTPROC_MNT 1 +#define MOUNTPROC_DUMP 2 +#define MOUNTPROC_UMNT 3 +#define MOUNTPROC_UMNTALL 4 +#define MOUNTPROC_EXPORT 5 + +#endif /* NFS_MOUNT_H */ diff --git a/nfs/name-cache.c b/nfs/name-cache.c new file mode 100644 index 00000000..7553d7c0 --- /dev/null +++ b/nfs/name-cache.c @@ -0,0 +1,305 @@ +/* Directory name lookup caching + + Copyright (C) 1996, 1997 Free Software Foundation, Inc. + Written by Thomas Bushnell, n/BSG, & Miles Bader. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#include "nfs.h" +#include <string.h> +#include <cacheq.h> + + +/* Maximum number of names to cache at any given time */ +#define MAXCACHE 200 + +/* Maximum length of file name we bother caching */ +#define CACHE_NAME_LEN 100 + +/* Cache entry */ +struct lookup_cache +{ + struct cacheq_hdr hdr; + + /* File handles and lengths for cache entries. 0 for NODE_CACHE_LEN + means a */ + char dir_cache_fh[NFS3_FHSIZE]; + size_t dir_cache_len; + + /* Zero means a `negative' entry -- recording that there's + definitely no node with this name. */ + struct node *np; + + /* Name of the node NODE_CACHE_ID in the directory DIR_CACHE_ID. Entries + with names too long to fit in this buffer aren't cached at all. */ + char name[CACHE_NAME_LEN]; + + /* Strlen of NAME. If this is zero, it's an unused entry. */ + size_t name_len; + + /* Time that this cache entry was created. */ + time_t cache_stamp; + + /* XXX */ + int stati; +}; + +/* The contents of the cache in no particular order */ +static struct cacheq lookup_cache = { sizeof (struct lookup_cache) }; + +static pthread_spinlock_t cache_lock = PTHREAD_SPINLOCK_INITIALIZER; + +/* Buffer to hold statistics */ +static struct stats +{ + long pos_hits; + long neg_hits; + long miss; + long fetch_errors; +} statistics; + +#define PARTIAL_THRESH 100 +#define NPARTIALS (MAXCACHE / PARTIAL_THRESH) +struct stats partial_stats [NPARTIALS]; + + +/* If there's an entry for NAME, of length NAME_LEN, in directory DIR in the + cache, return its entry, otherwise 0. CACHE_LOCK must be held. */ +static struct lookup_cache * +find_cache (char *dir, size_t len, const char *name, size_t name_len) +{ + struct lookup_cache *c; + int i; + + /* Search the list. All unused entries are contiguous at the end of the + list, so we can stop searching when we see the first one. */ + for (i = 0, c = lookup_cache.mru; + c && c->name_len; + c = c->hdr.next, i++) + if (c->name_len == name_len + && c->dir_cache_len == len + && c->name[0] == name[0] + && memcmp (c->dir_cache_fh, dir, len) == 0 + && strcmp (c->name, name) == 0) + { + c->stati = i / PARTIAL_THRESH; + return c; + } + + return 0; +} + +/* Node NP has just been found in DIR with NAME. If NP is null, this + name has been confirmed as absent in the directory. DIR is the + fhandle of the directory and LEN is its length. */ +void +enter_lookup_cache (char *dir, size_t len, struct node *np, char *name) +{ + struct lookup_cache *c; + size_t name_len = strlen (name); + + if (name_len > CACHE_NAME_LEN - 1) + return; + + pthread_spin_lock (&cache_lock); + + if (lookup_cache.length == 0) + /* There should always be an lru_cache; this being zero means that the + cache hasn't been initialized yet. Do so. */ + cacheq_set_length (&lookup_cache, MAXCACHE); + + /* See if there's an old entry for NAME in DIR. If not, replace the least + recently used entry. */ + c = find_cache (dir, len, name, name_len) ?: lookup_cache.lru; + + /* Fill C with the new entry. */ + memcpy (c->dir_cache_fh, dir, len); + c->dir_cache_len = len; + if (c->np) + netfs_nrele (c->np); + c->np = np; + if (c->np) + netfs_nref (c->np); + strcpy (c->name, name); + c->name_len = name_len; + c->cache_stamp = mapped_time->seconds; + + /* Now C becomes the MRU entry! */ + cacheq_make_mru (&lookup_cache, c); + + pthread_spin_unlock (&cache_lock); +} + +/* Purge all references in the cache to NAME within directory DIR. */ +void +purge_lookup_cache (struct node *dp, char *name, size_t namelen) +{ + struct lookup_cache *c, *next; + + pthread_spin_lock (&cache_lock); + for (c = lookup_cache.mru; c; c = next) + { + /* Save C->hdr.next, since we may move C from this position. */ + next = c->hdr.next; + + if (c->name_len == namelen + && c->dir_cache_len == dp->nn->handle.size + && memcmp (c->dir_cache_fh, dp->nn->handle.data, + c->dir_cache_len) == 0 + && strcmp (c->name, name) == 0) + { + if (c->np) + netfs_nrele (c->np); + c->name_len = 0; + c->np = 0; + cacheq_make_lru (&lookup_cache, c); /* Use C as the next free + entry. */ + } + } + pthread_spin_unlock (&cache_lock); +} + +/* Purge all references in the cache to node NP. */ +void +purge_lookup_cache_node (struct node *np) +{ + struct lookup_cache *c, *next; + + pthread_spin_lock (&cache_lock); + for (c = lookup_cache.mru; c; c = next) + { + next = c->hdr.next; + + if (c->np == np) + { + netfs_nrele (c->np); + c->name_len = 0; + c->np = 0; + cacheq_make_lru (&lookup_cache, c); + } + } + pthread_spin_unlock (&cache_lock); +} + + + +/* Register a negative hit for an entry in the Nth stat class */ +void +register_neg_hit (int n) +{ + int i; + + statistics.neg_hits++; + + for (i = 0; i < n; i++) + partial_stats[i].miss++; + for (; i < NPARTIALS; i++) + partial_stats[i].neg_hits++; +} + +/* Register a positive hit for an entry in the Nth stat class */ +void +register_pos_hit (int n) +{ + int i; + + statistics.pos_hits++; + + for (i = 0; i < n; i++) + partial_stats[i].miss++; + for (; i < NPARTIALS; i++) + partial_stats[i].pos_hits++; +} + +/* Register a miss */ +void +register_miss () +{ + int i; + + statistics.miss++; + for (i = 0; i < NPARTIALS; i++) + partial_stats[i].miss++; +} + + + +/* Scan the cache looking for NAME inside DIR. If we know nothing + about the entry, then return 0. If the entry is confirmed to not + exist, then return -1. Otherwise, return NP for the entry, with + a newly allocated reference. For all return values other than 0, + unlock DIR->LOCK before returning. For positive hits, lock the + returned node. */ +struct node * +check_lookup_cache (struct node *dir, char *name) +{ + struct lookup_cache *c; + + pthread_spin_lock (&cache_lock); + + c = find_cache (dir->nn->handle.data, dir->nn->handle.size, + name, strlen (name)); + if (c) + { + int timeout = c->np + ? name_cache_timeout + : name_cache_neg_timeout; + + /* Make sure the entry is still usable; if not, zap it now. */ + if (mapped_time->seconds - c->cache_stamp >= timeout) + { + register_neg_hit (c->stati); + if (c->np) + netfs_nrele (c->np); + c->name_len = 0; + c->np = 0; + cacheq_make_lru (&lookup_cache, c); + pthread_spin_unlock (&cache_lock); + return 0; + } + + cacheq_make_mru (&lookup_cache, c); /* Record C as recently used. */ + + if (c->np == 0) + /* A negative cache entry. */ + { + register_neg_hit (c->stati); + pthread_spin_unlock (&cache_lock); + pthread_mutex_unlock (&dir->lock); + return (struct node *)-1; + } + else + { + struct node *np; + + np = c->np; + netfs_nref (np); + register_pos_hit (c->stati); + pthread_spin_unlock (&cache_lock); + + pthread_mutex_unlock (&dir->lock); + pthread_mutex_lock (&np->lock); + + return np; + } + } + + register_miss (); + pthread_spin_unlock (&cache_lock); + + return 0; +} diff --git a/nfs/nfs-spec.h b/nfs/nfs-spec.h new file mode 100644 index 00000000..bed03874 --- /dev/null +++ b/nfs/nfs-spec.h @@ -0,0 +1,168 @@ +#ifndef NFS_NFS_SPEC_H +#define NFS_NFS_SPEC_H + +#define NFS_PORT 2049 +#define NFS_MAXDATA 8192 +#define NFS_MAXPATHLEN 1024 +#define NFS_MAXNAMLEN 255 +#define NFS2_FHSIZE 32 +#define NFS3_FHSIZE 64 +#define NFS_COOKIESIZE 4 +#define NFS_FIFO_DEV -1 +#define NFS3_COOKIEVERFSIZE 8 +#define NFS3_CREATEVERFSIZE 8 +#define NFS3_WRITEVERFSIZE 8 +#define NFSMODE_FMT 0170000 +#define NFSMODE_DIR 0040000 +#define NFSMODE_CHR 0020000 +#define NFSMODE_BLK 0060000 +#define NFSMODE_REG 0100000 +#define NFSMODE_LNK 0120000 +#define NFSMODE_SOCK 0140000 +#define NFSMODE_FIFO 0010000 + +enum nfsstat { + NFS_OK = 0, + NFSERR_PERM = 1, + NFSERR_NOENT = 2, + NFSERR_IO = 5, + NFSERR_NXIO = 6, + NFSERR_ACCES = 13, + NFSERR_EXIST = 17, + NFSERR_XDEV = 18, /* v3 only */ + NFSERR_NODEV = 19, + NFSERR_NOTDIR = 20, + NFSERR_ISDIR = 21, + NFSERR_INVAL = 22, /* v3 only */ + NFSERR_FBIG = 27, + NFSERR_NOSPC = 28, + NFSERR_ROFS = 30, + NFSERR_MLINK = 31, /* v3 only */ + NFSERR_NAMETOOLONG = 63, + NFSERR_NOTEMPTY = 66, + NFSERR_DQUOT = 69, + NFSERR_STALE = 70, + NFSERR_REMOTE = 71, /* v3 only */ + NFSERR_WFLUSH = 99, /* v2 only */ + NFSERR_BADHANDLE = 10001, /* v3 only */ + NFSERR_NOT_SYNC = 10002, /* v3 only */ + NFSERR_BAD_COOKIE = 10003, /* v3 only */ + NFSERR_NOTSUPP = 10004, /* v3 only */ + NFSERR_TOOSMALL = 10005, /* v3 only */ + NFSERR_SERVERFAULT = 10006, /* v3 only */ + NFSERR_BADTYPE = 10007, /* v3 only */ + NFSERR_JUKEBOX = 10008, /* v3 only */ +#define NFSERR_TRYLATER NFSERR_JUKEBOX +}; + + +enum ftype { + NF2NON = 0, /* v2 only */ + NFREG = 1, + NFDIR = 2, + NFBLK = 3, + NFCHR = 4, + NFLNK = 5, + NFSOCK = 6, + NF3FIFO = 7, /* v3 only */ +#define NF2BAD NF3FIFO /* v2 only */ + NF2FIFO = 8, /* v2 only */ +}; + +/* Ways to set the time in setattr structures */ +enum sattr_time_how +{ + DONT_CHANGE = 0, + SET_TO_SERVER_TIME = 1, + SET_TO_CLIENT_TIME = 2, +}; + +/* Construction of ACCESS arg to NFS3PROC_ACCESS. */ +#define ACCESS3_READ 0x01 +#define ACCESS3_LOOKUP 0x02 +#define ACCESS3_MODIFY 0x04 +#define ACCESS3_EXTEND 0x08 +#define ACCESS3_DELETE 0x10 +#define ACCESS3_EXECUTE 0x20 + +/* STABLE arg to NFS3PROC_READ */ +enum stable_how { + UNSTABLE = 0, + DATA_SYNC = 1, + FILE_SYNC = 2, +}; + +/* MODE arg to NFS3PROC_CREATE */ +enum createmode +{ + UNCHECKED = 0, + GUARDED = 1, + EXCLUSIVE = 2, +}; + +#define NFS_PROGRAM ((u_long)100003) +#define NFS_VERSION ((u_long)2) + +#define NFS_PROTOCOL_FUNC(proc,vers) \ + (vers == 2 ? NFS2PROC_ ## proc : NFS3PROC_ ## proc) + +#define NFSPROC_NULL(v) NFS_PROTOCOL_FUNC (NULL,v) +#define NFSPROC_GETATTR(v) NFS_PROTOCOL_FUNC (GETATTR, v) +#define NFSPROC_SETATTR(v) NFS_PROTOCOL_FUNC (SETATTR, v) +#define NFSPROC_LOOKUP(v) NFS_PROTOCOL_FUNC (LOOKUP, v) +#define NFSPROC_READLINK(v) NFS_PROTOCOL_FUNC (READLINK, v) +#define NFSPROC_READ(v) NFS_PROTOCOL_FUNC (READ, v) +#define NFSPROC_WRITE(v) NFS_PROTOCOL_FUNC (WRITE, v) +#define NFSPROC_CREATE(v) NFS_PROTOCOL_FUNC (CREATE, v) +#define NFSPROC_REMOVE(v) NFS_PROTOCOL_FUNC (REMOVE, v) +#define NFSPROC_RENAME(v) NFS_PROTOCOL_FUNC (RENAME, v) +#define NFSPROC_LINK(v) NFS_PROTOCOL_FUNC (LINK, v) +#define NFSPROC_SYMLINK(v) NFS_PROTOCOL_FUNC (SYMLINK, v) +#define NFSPROC_MKDIR(v) NFS_PROTOCOL_FUNC (MKDIR, v) +#define NFSPROC_RMDIR(v) NFS_PROTOCOL_FUNC (RMDIR, v) +#define NFSPROC_READDIR(v) NFS_PROTOCOL_FUNC (READDIR, v) + +/* Values for each protocol */ +#define NFS2PROC_NULL 0 +#define NFS2PROC_GETATTR 1 +#define NFS2PROC_SETATTR 2 +#define NFS2PROC_ROOT 3 +#define NFS2PROC_LOOKUP 4 +#define NFS2PROC_READLINK 5 +#define NFS2PROC_READ 6 +#define NFS2PROC_WRITECACHE 7 +#define NFS2PROC_WRITE 8 +#define NFS2PROC_CREATE 9 +#define NFS2PROC_REMOVE 10 +#define NFS2PROC_RENAME 11 +#define NFS2PROC_LINK 12 +#define NFS2PROC_SYMLINK 13 +#define NFS2PROC_MKDIR 14 +#define NFS2PROC_RMDIR 15 +#define NFS2PROC_READDIR 16 +#define NFS2PROC_STATFS 17 + +#define NFS3PROC_NULL 0 +#define NFS3PROC_GETATTR 1 +#define NFS3PROC_SETATTR 2 +#define NFS3PROC_LOOKUP 3 +#define NFS3PROC_ACCESS 4 +#define NFS3PROC_READLINK 5 +#define NFS3PROC_READ 6 +#define NFS3PROC_WRITE 7 +#define NFS3PROC_CREATE 8 +#define NFS3PROC_MKDIR 9 +#define NFS3PROC_SYMLINK 10 +#define NFS3PROC_MKNOD 11 +#define NFS3PROC_REMOVE 12 +#define NFS3PROC_RMDIR 13 +#define NFS3PROC_RENAME 14 +#define NFS3PROC_LINK 15 +#define NFS3PROC_READDIR 16 +#define NFS3PROC_READDIRPLUS 17 +#define NFS3PROC_FSSTAT 18 +#define NFS3PROC_FSINFO 19 +#define NFS3PROC_PATHCONF 20 +#define NFS3PROC_COMMIT 21 + +#endif /* NFS_NFS_SPEC_H */ diff --git a/nfs/nfs.c b/nfs/nfs.c new file mode 100644 index 00000000..4916df65 --- /dev/null +++ b/nfs/nfs.c @@ -0,0 +1,681 @@ +/* nfs.c - XDR frobbing and lower level routines for NFS client. + + Copyright (C) 1995, 1996, 1997, 1999, 2002, 2007 + Free Software Foundation, Inc. + + Written by Michael I. Bushnell, p/BSG. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#include "nfs.h" + +#include <string.h> +#include <netinet/in.h> +#include <stdio.h> + +/* Convert an NFS mode (TYPE and MODE) to a Hurd mode and return + it. */ +mode_t +nfs_mode_to_hurd_mode (int type, int mode) +{ + int hurdmode; + + switch (type) + { + case NFDIR: + hurdmode = S_IFDIR; + break; + + case NFCHR: + hurdmode = S_IFCHR; + break; + + case NFBLK: + hurdmode = S_IFBLK; + break; + + case NFREG: + hurdmode = S_IFREG; + break; + + case NFLNK: + hurdmode = S_IFLNK; + break; + + case NFSOCK: + hurdmode = S_IFSOCK; + break; + + default: + if (protocol_version == 2) + switch (type) + { + case NF2NON: + case NF2BAD: + default: + hurdmode = S_IFREG; + break; + + case NF2FIFO: + hurdmode = S_IFIFO; + break; + } + else + switch (type) + { + case NF3FIFO: + hurdmode = S_IFIFO; + break; + + default: + hurdmode = S_IFREG; + break; + } + break; + } + + hurdmode |= mode & ~NFSMODE_FMT; + return hurdmode; +} + +/* Convert a Hurd mode to an NFS mode. */ +int +hurd_mode_to_nfs_mode (mode_t mode) +{ + /* This function is used only for chmod; just trim the bits that NFS + doesn't support. */ + return mode & 07777; +} + +/* Convert a Hurd mode to an NFS type. */ +int +hurd_mode_to_nfs_type (mode_t mode) +{ + switch (mode & S_IFMT) + { + case S_IFDIR: + return NFDIR; + + case S_IFCHR: + default: + return NFCHR; + + case S_IFBLK: + return NFBLK; + + case S_IFREG: + return NFREG; + + case S_IFLNK: + return NFLNK; + + case S_IFSOCK: + return NFSOCK; + + case S_IFIFO: + return protocol_version == 2 ? NF2FIFO : NF3FIFO; + } +} + + + +/* Each of the functions on this page copies its second arg to *P, + converting it to XDR representation along the way. They then + return the address after the copied value. */ + +/* Encode an NFS file handle. */ +int * +xdr_encode_fhandle (int *p, struct fhandle *fhandle) +{ + if (protocol_version == 2) + { + memcpy (p, fhandle->data, NFS2_FHSIZE); + return p + INTSIZE (NFS2_FHSIZE); + } + else + return xdr_encode_data (p, fhandle->data, fhandle->size); +} + +/* Encode uninterpreted bytes. */ +int * +xdr_encode_data (int *p, char *data, size_t len) +{ + int nints = INTSIZE (len); + + p[nints] = 0; + *(p++) = htonl (len); + memcpy (p, data, len); + return p + nints; +} + +/* Encode a 64 bit integer. */ +int * +xdr_encode_64bit (int *p, long long n) +{ + *(p++) = htonl (n & 0xffffffff00000000LL >> 32); + *(p++) = htonl (n & 0xffffffff); + return p; +} + +/* Encode a C string. */ +int * +xdr_encode_string (int *p, char *string) +{ + return xdr_encode_data (p, string, strlen (string)); +} + +/* Encode a MODE into an otherwise empty sattr. */ +int * +xdr_encode_sattr_mode (int *p, mode_t mode) +{ + if (protocol_version == 2) + { + *(p++) = htonl (hurd_mode_to_nfs_mode (mode)); + *(p++) = -1; /* uid */ + *(p++) = -1; /* gid */ + *(p++) = -1; /* size */ + *(p++) = -1; /* atime secs */ + *(p++) = -1; /* atime usecs */ + *(p++) = -1; /* mtime secs */ + *(p++) = -1; /* mtime usecs */ + } + else + { + *(p++) = htonl (1); /* set mode */ + *(p++) = htonl (hurd_mode_to_nfs_mode (mode)); + *(p++) = 0; /* no uid */ + *(p++) = 0; /* no gid */ + *(p++) = 0; /* no size */ + *(p++) = DONT_CHANGE; /* no atime */ + *(p++) = DONT_CHANGE; /* no mtime */ + } + return p; +} + +/* Encode UID and GID into an otherwise empty sattr. */ +int * +xdr_encode_sattr_ids (int *p, u_int uid, u_int gid) +{ + if (protocol_version == 2) + { + *(p++) = -1; /* mode */ + *(p++) = htonl (uid); + *(p++) = htonl (gid); + *(p++) = -1; /* size */ + *(p++) = -1; /* atime secs */ + *(p++) = -1; /* atime usecs */ + *(p++) = -1; /* mtime secs */ + *(p++) = -1; /* mtime usecs */ + } + else + { + *(p++) = 0; /* no mode */ + *(p++) = htonl (1); /* set uid */ + *(p++) = htonl (uid); + *(p++) = htonl (1); /* set gid */ + *(p++) = htonl (gid); + *(p++) = 0; /* no size */ + *(p++) = DONT_CHANGE; /* no atime */ + *(p++) = DONT_CHANGE; /* no mtime */ + } + return p; +} + +/* Encode a file size into an otherwise empty sattr. */ +int * +xdr_encode_sattr_size (int *p, off_t size) +{ + if (protocol_version == 2) + { + *(p++) = -1; /* mode */ + *(p++) = -1; /* uid */ + *(p++) = -1; /* gid */ + *(p++) = htonl (size); + *(p++) = -1; /* atime secs */ + *(p++) = -1; /* atime usecs */ + *(p++) = -1; /* mtime secs */ + *(p++) = -1; /* mtime secs */ + } + else + { + *(p++) = 0; /* no mode */ + *(p++) = 0; /* no uid */ + *(p++) = 0; /* no gid */ + *(p++) = htonl (1); /* size */ + p = xdr_encode_64bit (p, size); + *(p++) = DONT_CHANGE; /* no atime */ + *(p++) = DONT_CHANGE; /* no mtime */ + } + return p; +} + +/* Encode ATIME and MTIME into an otherwise empty sattr. */ +int * +xdr_encode_sattr_times (int *p, struct timespec *atime, struct timespec *mtime) +{ + if (protocol_version == 2) + { + *(p++) = -1; /* mode */ + *(p++) = -1; /* uid */ + *(p++) = -1; /* gid */ + *(p++) = -1; /* size */ + *(p++) = htonl (atime->tv_sec); + *(p++) = htonl (atime->tv_nsec / 1000); + *(p++) = htonl (mtime->tv_sec); + *(p++) = htonl (mtime->tv_nsec / 1000); + } + else + { + *(p++) = 0; /* no mode */ + *(p++) = 0; /* no uid */ + *(p++) = 0; /* no gid */ + *(p++) = 0; /* no size */ + *(p++) = htonl (SET_TO_CLIENT_TIME); /* atime */ + *(p++) = htonl (atime->tv_sec); + *(p++) = htonl (atime->tv_nsec); + *(p++) = htonl (SET_TO_CLIENT_TIME); /* mtime */ + *(p++) = htonl (mtime->tv_sec); + *(p++) = htonl (mtime->tv_nsec); + } + return p; +} + +/* Encode MODE, a size of zero, and the specified owner into an + otherwise empty sattr. */ +int * +xdr_encode_create_state (int *p, + mode_t mode, + uid_t owner) +{ + if (protocol_version == 2) + { + *(p++) = htonl (hurd_mode_to_nfs_mode (mode)); + *(p++) = htonl (owner); /* uid */ + *(p++) = -1; /* gid */ + *(p++) = 0; /* size */ + *(p++) = -1; /* atime sec */ + *(p++) = -1; /* atime usec */ + *(p++) = -1; /* mtime sec */ + *(p++) = -1; /* mtime usec */ + } + else + { + *(p++) = htonl (1); /* mode */ + *(p++) = htonl (hurd_mode_to_nfs_mode (mode)); + *(p++) = htonl (1); /* set uid */ + *(p++) = htonl (owner); + *(p++) = 0; /* no gid */ + *(p++) = htonl (1); /* set size */ + p = xdr_encode_64bit (p, 0); + *(p++) = htonl (SET_TO_SERVER_TIME); /* atime */ + *(p++) = htonl (SET_TO_SERVER_TIME); /* mtime */ + } + return p; +} + +/* Encode ST into an sattr. */ +int * +xdr_encode_sattr_stat (int *p, + struct stat *st) +{ + if (protocol_version == 2) + { + *(p++) = htonl (hurd_mode_to_nfs_mode (st->st_mode)); + *(p++) = htonl (st->st_uid); + *(p++) = htonl (st->st_gid); + *(p++) = htonl (st->st_size); + *(p++) = htonl (st->st_atim.tv_sec); + *(p++) = htonl (st->st_atim.tv_nsec / 1000); + *(p++) = htonl (st->st_mtim.tv_sec); + *(p++) = htonl (st->st_mtim.tv_nsec / 1000); + } + else + { + *(p++) = htonl (1); /* set mode */ + *(p++) = htonl (hurd_mode_to_nfs_mode (st->st_mode)); + *(p++) = htonl (1); /* set uid */ + *(p++) = htonl (st->st_uid); + *(p++) = htonl (1); /* set gid */ + *(p++) = htonl (st->st_gid); + *(p++) = htonl (1); /* set size */ + p = xdr_encode_64bit (p, st->st_size); + *(p++) = htonl (SET_TO_CLIENT_TIME); /* set atime */ + *(p++) = htonl (st->st_atim.tv_sec); + *(p++) = htonl (st->st_atim.tv_nsec); + *(p++) = htonl (SET_TO_CLIENT_TIME); /* set mtime */ + *(p++) = htonl (st->st_mtim.tv_sec); + *(p++) = htonl (st->st_mtim.tv_nsec); + } + return p; +} + + +/* Decode *P into a long long; return the address of the following + data. */ +int * +xdr_decode_64bit (int *p, long long *n) +{ + long long high, low; + high = ntohl (*p); + p++; + low = ntohl (*p); + p++; + *n = ((high & 0xffffffff) << 32) | (low & 0xffffffff); + return p; +} + +/* Decode *P into an fhandle and look up the associated node. Return + the address of the following data. */ +int * +xdr_decode_fhandle (int *p, struct node **npp) +{ + size_t len; + + if (protocol_version == 2) + len = NFS2_FHSIZE; + else + { + len = ntohl (*p); + p++; + } + /* Enter into cache. */ + lookup_fhandle (p, len, npp); + return p + len / sizeof (int); +} + +/* Decode *P into a stat structure; return the address of the + following data. */ +int * +xdr_decode_fattr (int *p, struct stat *st) +{ + int type, mode; + + type = ntohl (*p); + p++; + mode = ntohl (*p); + p++; + st->st_mode = nfs_mode_to_hurd_mode (type, mode); + st->st_nlink = ntohl (*p); + p++; + st->st_uid = ntohl (*p); + p++; + st->st_gid = ntohl (*p); + p++; + if (protocol_version == 2) + { + st->st_size = ntohl (*p); + p++; + st->st_blksize = ntohl (*p); + p++; + st->st_rdev = ntohl (*p); + p++; + st->st_blocks = ntohl (*p); + p++; + } + else + { + long long size; + int major, minor; + p = xdr_decode_64bit (p, &size); + st->st_size = size; + p = xdr_decode_64bit (p, &size); + st->st_blocks = size / 512; + st->st_blksize = read_size < write_size ? read_size : write_size; + major = ntohl (*p); + p++; + minor = ntohl (*p); + p++; + st->st_rdev = makedev (major, minor); + } + st->st_fsid = ntohl (*p); + p++; + st->st_ino = ntohl (*p); + p++; + st->st_atim.tv_sec = ntohl (*p); + p++; + st->st_atim.tv_nsec = ntohl (*p); + p++; + st->st_mtim.tv_sec = ntohl (*p); + p++; + st->st_mtim.tv_nsec = ntohl (*p); + p++; + st->st_ctim.tv_sec = ntohl (*p); + p++; + st->st_ctim.tv_nsec = ntohl (*p); + p++; + + if (protocol_version < 3) + { + st->st_atim.tv_nsec *= 1000; + st->st_mtim.tv_nsec *= 1000; + st->st_ctim.tv_nsec *= 1000; + } + + return p; + +} + +/* Decode *P into a string, stored at BUF; return the address + of the following data. */ +int * +xdr_decode_string (int *p, char *buf) +{ + int len; + + len = ntohl (*p); + p++; + memcpy (buf, p, len); + buf[len] = '\0'; + return p + INTSIZE (len); +} + + +/* Set up an RPC for procedure RPC_PROC for talking to the NFS server. + Allocate storage with malloc and point *BUFP at it; caller must + free this when done. Initialize RPC credential information with + information from CRED (identifying the user making this call; -1 + means superuser), NP (identifying the node we are operating on), and + SECOND_GID (specifying another GID the server might be interested + in). Allocate at least LEN bytes of space for bulk data in + addition to the normal amount for an RPC. */ +int * +nfs_initialize_rpc (int rpc_proc, struct iouser *cred, + size_t len, void **bufp, struct node *np, + uid_t second_gid) +{ + uid_t uid; + uid_t gid; + error_t err; + + /* Use heuristics to figure out what ids to present to the server. + Don't lie, but adjust ids as necessary to secure the desired + result. */ + + if (cred == (struct iouser *) -1) + { + uid = gid = 0; + second_gid = -1; + } + else if (cred + && (cred->uids->num || cred->gids->num)) + { + if (idvec_contains (cred->uids, 0)) + { + err = netfs_validate_stat (np, 0); + uid = 0; + gid = err ? -2 : 0; + if (err) + printf ("NFS warning, internal stat failure\n"); + } + else + { + if (cred->uids->num == 0) + uid = -2; + else if (cred->uids->num == 1) + uid = cred->uids->ids[0]; + else + { + err = netfs_validate_stat (np, 0); + if (err) + { + uid = cred->uids->ids[0]; + printf ("NFS warning, internal stat failure\n"); + } + else + { + if (idvec_contains (cred->uids, np->nn_stat.st_uid)) + uid = np->nn_stat.st_uid; + else + uid = cred->uids->ids[0]; + } + } + + if (cred->gids->num == 0) + { + gid = -2; + second_gid = -1; + } + else if (cred->gids->num == 1) + { + gid = cred->gids->ids[0]; + second_gid = -1; + } + else + { + err = netfs_validate_stat (np, 0); + if (err) + { + gid = cred->gids->ids[0]; + printf ("NFS warning, internal stat failure\n"); + } + else + { + if (idvec_contains (cred->gids, np->nn_stat.st_gid)) + gid = np->nn_stat.st_gid; + else + gid = cred->gids->ids[0]; + } + if (second_gid != -1 + && !idvec_contains (cred->gids, second_gid)) + second_gid = -1; + } + } + } + else + uid = gid = second_gid = -1; + + return initialize_rpc (NFS_PROGRAM, NFS_VERSION, rpc_proc, len, bufp, + uid, gid, second_gid); +} + +/* ERROR is an NFS error code; return the correspending Hurd + error. */ +error_t +nfs_error_trans (int error) +{ + switch (error) + { + case NFS_OK: + return 0; + + case NFSERR_PERM: + return EPERM; + + case NFSERR_NOENT: + return ENOENT; + + case NFSERR_IO: + return EIO; + + case NFSERR_NXIO: + return ENXIO; + + case NFSERR_ACCES: + return EACCES; + + case NFSERR_EXIST: + return EEXIST; + + case NFSERR_NODEV: + return ENODEV; + + case NFSERR_NOTDIR: + return ENOTDIR; + + case NFSERR_ISDIR: + return EISDIR; + + case NFSERR_FBIG: + return E2BIG; + + case NFSERR_NOSPC: + return ENOSPC; + + case NFSERR_ROFS: + return EROFS; + + case NFSERR_NAMETOOLONG: + return ENAMETOOLONG; + + case NFSERR_NOTEMPTY: + return ENOTEMPTY; + + case NFSERR_DQUOT: + return EDQUOT; + + case NFSERR_STALE: + return ESTALE; + + case NFSERR_WFLUSH: + /* Not known in v3, but we just give EINVAL for unknown errors + so it's the same. */ + return EINVAL; + + default: + if (protocol_version == 2) + return EINVAL; + else + switch (error) + { + case NFSERR_XDEV: + return EXDEV; + + case NFSERR_INVAL: + case NFSERR_REMOTE: /* not sure about this one */ + default: + return EINVAL; + + case NFSERR_MLINK: + return EMLINK; + + case NFSERR_NOTSUPP: + case NFSERR_BADTYPE: + return EOPNOTSUPP; + + case NFSERR_SERVERFAULT: + return EIO; + + case NFSERR_BADHANDLE: + case NFSERR_NOT_SYNC: + case NFSERR_BAD_COOKIE: + case NFSERR_TOOSMALL: + case NFSERR_JUKEBOX: /* ??? */ + /* These indicate bugs in the client, so EGRATUITOUS is right. */ + return EGRATUITOUS; + } + } +} diff --git a/nfs/nfs.h b/nfs/nfs.h new file mode 100644 index 00000000..18dec001 --- /dev/null +++ b/nfs/nfs.h @@ -0,0 +1,204 @@ +/* Data structures and global variables for NFS client + Copyright (C) 1994,95,96,97,99,2001 Free Software Foundation, Inc. + + 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. + + 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 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef NFS_NFS_H +#define NFS_NFS_H + +#include <sys/stat.h> +#include <sys/types.h> +#include <stdint.h> +#include <pthread.h> +#include <sys/mman.h> +#include "nfs-spec.h" +#include <hurd/netfs.h> + +/* A file handle */ +struct fhandle +{ + size_t size; + + /* Leave enough room for the largest possible fhandle. */ + char data[NFS3_FHSIZE]; +}; + +/* There exists one of there for the private data needed by each client + node. */ +struct netnode +{ + struct fhandle handle; + time_t stat_updated; + struct node *hnext, **hprevp; + + /* These two fields handle translators set internally but + unknown to the server. */ + enum + { + NOT_POSSIBLE, + POSSIBLE, + SYMLINK, + CHRDEV, + BLKDEV, + FIFO, + SOCK, + } dtrans; + union + { + char *name; + dev_t indexes; + } transarg; + +#ifdef notyet + /* This indicates that the length of the file must be at + least this big because we've written this much locally, + even if the server thinks we haven't gone this far. */ + off_t extend_len; +#endif + + struct user_pager_info *fileinfo; + + /* If this node has been renamed by "deletion" then + this is the directory and the name in that directory + which is holding the node */ + struct node *dead_dir; + char *dead_name; +}; + +/* Socket file descriptor for talking to RPC servers. */ +int main_udp_socket; + +/* Our hostname */ +char *hostname; + +/* The current time */ +volatile struct mapped_time_value *mapped_time; + +/* Some tunable parameters */ + +/* How long to keep around stat information */ +extern int stat_timeout; + +/* How long to keep around file contents caches */ +extern int cache_timeout; + +/* How long to keep around positive dir cache entries */ +extern int name_cache_timeout; + +/* How long to keep around negative dir cache entries */ +extern int name_cache_neg_timeout; + +/* How long to wait for replies before re-sending RPC's. */ +extern int initial_transmit_timeout; +extern int max_transmit_timeout; + +/* How many attempts to send before giving up on soft mounts */ +extern int soft_retries; + +/* Whether we are a hard or soft mount */ +extern int mounted_soft; + +/* Maximum amount to read at once */ +extern int read_size; + +/* Maximum amout to write at once */ +extern int write_size; + +/* Service name for portmapper */ +extern char *pmap_service_name; + +/* If pmap_service is null, then this is the port to use for the portmapper; + if the lookup of pmap_service_name fails, use this number. */ +extern short pmap_service_number; + +/* RPC program for the mount agent */ +extern int mount_program; + +/* RPC program version for the mount agent */ +extern int mount_version; + +/* If this is nonzero, it's the port to use for the mount agent if + the portmapper fails or can't be found. */ +extern short mount_port; + +/* If this is nonzero use mount_port without attempting to contact + the portmapper */ +extern int mount_port_override; + +/* RPC program for the NFS server */ +extern int nfs_program; + +/* RPC program version for the NFS server */ +extern int nfs_version; + +/* If this is nonzero, it's the port to be used to find the nfs agent + if the portmapper fails or can't be found */ +extern short nfs_port; + +/* If this is nonzero use nfs_port without attepting to contact the + portmapper. */ +extern int nfs_port_override; + +/* Which NFS protocol version we are using */ +extern int protocol_version; + + +/* Count how many four-byte chunks it takes to hold LEN bytes. */ +#define INTSIZE(len) (((len)+3)>>2) + + +/* nfs.c */ +int hurd_mode_to_nfs_type (mode_t); +int *xdr_encode_fhandle (int *, struct fhandle *); +int *xdr_encode_data (int *, char *, size_t); +int *xdr_encode_string (int *, char *); +int *xdr_encode_sattr_mode (int *, mode_t); +int *xdr_encode_sattr_ids (int *, u_int, u_int); +int *xdr_encode_sattr_size (int *, off_t); +int *xdr_encode_sattr_times (int *, struct timespec *, struct timespec *); +int *xdr_encode_sattr_stat (int *, struct stat *); +int *xdr_encode_create_state (int *, mode_t, uid_t); +int *xdr_decode_fattr (int *, struct stat *); +int *xdr_decode_string (int *, char *); +int *xdr_decode_fhandle (int *, struct node **); +int *nfs_initialize_rpc (int, struct iouser *, size_t, void **, + struct node *, uid_t); +error_t nfs_error_trans (int); + +/* mount.c */ +struct node *mount_root (char *, char *); +extern const char *mounted_hostname; +extern uint16_t mounted_nfs_port; /* host order */ + +/* ops.c */ +int *register_fresh_stat (struct node *, int *); + +/* rpc.c */ +int *initialize_rpc (int, int, int, size_t, void **, uid_t, gid_t, gid_t); +error_t conduct_rpc (void **, int **); +void *timeout_service_thread (void *); +void *rpc_receive_thread (void *); + +/* cache.c */ +void lookup_fhandle (void *, size_t, struct node **); +int *recache_handle (int *, struct node *); + +/* name-cache.c */ +void enter_lookup_cache (char *, size_t, struct node *, char *); +void purge_lookup_cache (struct node *, char *, size_t); +struct node *check_lookup_cache (struct node *, char *); +void purge_lookup_cache_node (struct node *); + +#endif /* NFS_NFS_H */ diff --git a/nfs/ops.c b/nfs/ops.c new file mode 100644 index 00000000..a4d6ac77 --- /dev/null +++ b/nfs/ops.c @@ -0,0 +1,1925 @@ +/* ops.c - Libnetfs callbacks for node operations in NFS client. + Copyright (C) 1994,95,96,97,99,2002,2011 Free Software Foundation, Inc. + + 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. + + 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 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "nfs.h" +#include <hurd/netfs.h> +#include <netinet/in.h> +#include <string.h> +#include <fcntl.h> +#include <stdio.h> +#include <stddef.h> +#include <dirent.h> +#include <unistd.h> +#include <maptime.h> + +/* We have fresh stat information for NP; the file attribute (fattr) + structure is at P. Update our entry. Return the address of the next + int after the fattr structure. */ +int * +register_fresh_stat (struct node *np, int *p) +{ + int *ret; + + ret = xdr_decode_fattr (p, &np->nn_stat); + np->nn->stat_updated = mapped_time->seconds; + + switch (np->nn->dtrans) + { + case NOT_POSSIBLE: + case POSSIBLE: + break; + + case SYMLINK: + np->nn_stat.st_size = strlen (np->nn->transarg.name); + np->nn_stat.st_mode = ((np->nn_stat.st_mode & ~S_IFMT) | S_IFLNK); + break; + + case CHRDEV: + np->nn_stat.st_rdev = np->nn->transarg.indexes; + np->nn_stat.st_mode = ((np->nn_stat.st_mode & ~S_IFMT) | S_IFCHR); + break; + + case BLKDEV: + np->nn_stat.st_rdev = np->nn->transarg.indexes; + np->nn_stat.st_mode = ((np->nn_stat.st_mode & ~S_IFMT) | S_IFBLK); + break; + + case FIFO: + np->nn_stat.st_mode = ((np->nn_stat.st_mode & ~S_IFMT) | S_IFIFO); + break; + + case SOCK: + np->nn_stat.st_mode = ((np->nn_stat.st_mode & ~S_IFMT) | S_IFSOCK); + break; + } + + np->nn_stat.st_fsid = getpid (); + np->nn_stat.st_fstype = FSTYPE_NFS; + np->nn_stat.st_gen = 0; + np->nn_stat.st_author = np->nn_stat.st_uid; + np->nn_stat.st_flags = 0; + np->nn_translated = np->nn_stat.st_mode & S_IFMT; + + return ret; +} + +/* Handle returned wcc information for various calls. In protocol + version 2, this is just register_fresh_stat. In version 3, it + checks to see if stat information is present too. If this follows + an operation that we expect has modified the attributes, MOD should + be set. (This unpacks the post_op_attr XDR type.) */ +int * +process_returned_stat (struct node *np, int *p, int mod) +{ + if (protocol_version == 2) + return register_fresh_stat (np, p); + else + { + int attrs_exist; + + attrs_exist = ntohl (*p); + p++; + if (attrs_exist) + p = register_fresh_stat (np, p); + else if (mod) + /* We know that our values are now wrong */ + np->nn->stat_updated = 0; + return p; + } +} + + +/* Handle returned wcc information for various calls. In protocol + version 2, this is just register_fresh_stat. In version 3, it does + the wcc_data interpretation too. If this follows an operation that + we expect has modified the attributes, MOD should be set. + (This unpacks the wcc_data XDR type.) */ +int * +process_wcc_stat (struct node *np, int *p, int mod) +{ + if (protocol_version == 2) + return register_fresh_stat (np, p); + else + { + int attrs_exist; + + /* First the pre_op_attr */ + attrs_exist = ntohl (*p); + p++; + if (attrs_exist) + { + /* Just skip them for now */ + p += 2 * sizeof (int); /* size */ + p += 2 * sizeof (int); /* mtime */ + p += 2 * sizeof (int); /* atime */ + } + + /* Now the post_op_attr */ + return process_returned_stat (np, p, mod); + } +} + + +/* Implement the netfs_validate_stat callback as described in + <hurd/netfs.h>. */ +error_t +netfs_validate_stat (struct node *np, struct iouser *cred) +{ + int *p; + void *rpcbuf; + error_t err; + + if (mapped_time->seconds - np->nn->stat_updated < stat_timeout) + return 0; + + p = nfs_initialize_rpc (NFSPROC_GETATTR (protocol_version), + (struct iouser *) -1, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + if (!err) + register_fresh_stat (np, p); + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_chown callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_chown (struct iouser *cred, struct node *np, + uid_t uid, gid_t gid) +{ + int *p; + void *rpcbuf; + error_t err; + + p = nfs_initialize_rpc (NFSPROC_SETATTR (protocol_version), + cred, 0, &rpcbuf, np, gid); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_sattr_ids (p, uid, gid); + if (protocol_version == 3) + *(p++) = 0; /* guard_check == 0 */ + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err || protocol_version == 3) + p = process_wcc_stat (np, p, !err); + } + + free (rpcbuf); + + return err; +} + +/* Implement the netfs_attempt_chauthor callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_chauthor (struct iouser *cred, struct node *rp, + uid_t author) +{ + return EOPNOTSUPP; +} + +/* Implement the netfs_attempt_chmod callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_chmod (struct iouser *cred, struct node *np, + mode_t mode) +{ + int *p; + void *rpcbuf; + error_t err; + + if ((mode & S_IFMT) != 0) + { + err = netfs_validate_stat (np, cred); + if (err) + return err; + + /* Has the file type changed? (e.g. from symlink to + directory). */ + if ((mode & S_IFMT) != (np->nn_stat.st_mode & S_IFMT)) + { + char *f = 0; + + if (np->nn->dtrans == NOT_POSSIBLE) + return EOPNOTSUPP; + + if (np->nn->dtrans == SYMLINK) + f = np->nn->transarg.name; + + switch (mode & S_IFMT) + { + default: + return EOPNOTSUPP; + + case S_IFIFO: + np->nn->dtrans = FIFO; + np->nn->stat_updated = 0; + break; + + case S_IFSOCK: + np->nn->dtrans = SOCK; + np->nn->stat_updated = 0; + } + if (f) + free (f); + return 0; + } + } + + p = nfs_initialize_rpc (NFSPROC_SETATTR (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_sattr_mode (p, mode); + if (protocol_version == 3) + *(p++) = 0; /* guard check == 0 */ + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err || protocol_version == 3) + p = process_wcc_stat (np, p, !err); + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_chflags callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_chflags (struct iouser *cred, struct node *np, + int flags) +{ + return EOPNOTSUPP; +} + +/* Implement the netfs_attempt_utimes callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_utimes (struct iouser *cred, struct node *np, + struct timespec *atime, struct timespec *mtime) +{ + int *p; + void *rpcbuf; + error_t err; + struct timeval tv; + struct timespec current; + + /* XXX For version 3 we can actually do this right, but we don't + just yet. */ + if (!atime || !mtime) + { + maptime_read (mapped_time, &tv); + current.tv_sec = tv.tv_sec; + current.tv_nsec = tv.tv_usec * 1000; + } + + p = nfs_initialize_rpc (NFSPROC_SETATTR (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_sattr_times (p, + atime ?: ¤t, + mtime ?: ¤t); + if (protocol_version == 3) + *(p++) = 0; /* guard check == 0 */ + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err || protocol_version == 3) + p = process_wcc_stat (np, p, !err); + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_set_size callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_set_size (struct iouser *cred, struct node *np, + off_t size) +{ + int *p; + void *rpcbuf; + error_t err; + + p = nfs_initialize_rpc (NFSPROC_SETATTR (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_sattr_size (p, size); + if (protocol_version == 3) + *(p++) = 0; /* guard_check == 0 */ + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err || protocol_version == 3) + p = process_wcc_stat (np, p, !err); + } + + /* If we got EACCES, but the user has the file open for writing, + then the NFS protocol has screwed us. There's nothing we can do, + except in the important case of opens with + O_TRUNC|O_CREAT|O_WRONLY|O_EXCL where the new mode does not allow + writing. RCS, for example, uses this to create lock files. So permit + cases where the O_TRUNC isn't doing anything to succeed if the user + does have the file open for writing. */ + if (err == EACCES) + { + int error = netfs_validate_stat (np, cred); + if (!error && np->nn_stat.st_size == size) + err = 0; + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_statfs callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_statfs (struct iouser *cred, struct node *np, + struct statfs *st) +{ + int *p; + void *rpcbuf; + error_t err; + + p = nfs_initialize_rpc (NFS2PROC_STATFS, cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + + if (!err) + { + p++; /* skip IOSIZE field */ + st->f_bsize = ntohl (*p); + p++; + st->f_blocks = ntohl (*p); + p++; + st->f_bfree = ntohl (*p); + p++; + st->f_bavail = ntohl (*p); + p++; + st->f_type = FSTYPE_NFS; + st->f_files = 0; + st->f_ffree = 0; + st->f_fsid = getpid (); + st->f_namelen = 0; + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_sync callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_sync (struct iouser *cred, struct node *np, int wait) +{ + /* We are already completely synchronous. */ + return 0; +} + +/* Implement the netfs_attempt_syncfs callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_syncfs (struct iouser *cred, int wait) +{ + return 0; +} + +/* Implement the netfs_attempt_read callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_read (struct iouser *cred, struct node *np, + off_t offset, size_t *len, void *data) +{ + int *p; + void *rpcbuf; + size_t trans_len; + error_t err; + size_t amt, thisamt; + int eof; + + for (amt = *len; amt;) + { + thisamt = amt; + if (thisamt > read_size) + thisamt = read_size; + + p = nfs_initialize_rpc (NFSPROC_READ (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + *(p++) = htonl (offset); + *(p++) = htonl (thisamt); + if (protocol_version == 2) + *(p++) = 0; + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + + if (!err || protocol_version == 3) + p = process_returned_stat (np, p, !err); + + if (err) + { + free (rpcbuf); + return err; + } + + trans_len = ntohl (*p); + p++; + if (trans_len > thisamt) + trans_len = thisamt; /* ??? */ + + if (protocol_version == 3) + { + eof = ntohl (*p); + p++; + } + else + eof = (trans_len < thisamt); + + memcpy (data, p, trans_len); + free (rpcbuf); + + data += trans_len; + offset += trans_len; + amt -= trans_len; + + if (eof) + { + *len -= amt; + return 0; + } + } + } + return 0; +} + +/* Implement the netfs_attempt_write callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_write (struct iouser *cred, struct node *np, + off_t offset, size_t *len, void *data) +{ + int *p; + void *rpcbuf; + error_t err; + size_t amt, thisamt; + size_t count; + + for (amt = *len; amt;) + { + thisamt = amt; + if (thisamt > write_size) + thisamt = write_size; + + p = nfs_initialize_rpc (NFSPROC_WRITE (protocol_version), + cred, thisamt, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + if (protocol_version == 2) + *(p++) = 0; + *(p++) = htonl (offset); + if (protocol_version == 2) + *(p++) = 0; + if (protocol_version == 3) + *(p++) = htonl (FILE_SYNC); + p = xdr_encode_data (p, data, thisamt); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err || protocol_version == 3) + p = process_wcc_stat (np, p, !err); + if (!err) + { + if (protocol_version == 3) + { + count = ntohl (*p); + p++; + p++; /* ignore COMMITTED */ + /* ignore verf for now */ + p += NFS3_WRITEVERFSIZE / sizeof (int); + } + else + /* assume it wrote the whole thing */ + count = thisamt; + + amt -= count; + data += count; + offset += count; + } + } + + free (rpcbuf); + + if (err == EINTR && amt != *len) + { + *len -= amt; + return 0; + } + + if (err) + { + *len = 0; + return err; + } + } + return 0; +} + +/* See if NAME exists in DIR for CRED. If so, return EEXIST. */ +error_t +verify_nonexistent (struct iouser *cred, struct node *dir, + char *name) +{ + int *p; + void *rpcbuf; + error_t err; + + /* Don't use the lookup cache for this; we want a full sync to + get as close to real exclusive create behavior as possible. */ + + assert (protocol_version == 2); + + p = nfs_initialize_rpc (NFSPROC_LOOKUP (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + + free (rpcbuf); + + if (!err) + return EEXIST; + else + return 0; +} + +/* Implement the netfs_attempt_lookup callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_lookup (struct iouser *cred, struct node *np, + char *name, struct node **newnp) +{ + int *p; + void *rpcbuf; + error_t err; + char dirhandle[NFS3_FHSIZE]; + size_t dirlen; + + /* Check the cache first. */ + *newnp = check_lookup_cache (np, name); + if (*newnp) + { + if (*newnp == (struct node *) -1) + { + *newnp = 0; + return ENOENT; + } + else + return 0; + } + + p = nfs_initialize_rpc (NFSPROC_LOOKUP (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_string (p, name); + + /* Remember the directory handle for later cache use. */ + + dirlen = np->nn->handle.size; + memcpy (dirhandle, np->nn->handle.data, dirlen); + + pthread_mutex_unlock (&np->lock); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err) + { + p = xdr_decode_fhandle (p, newnp); + p = process_returned_stat (*newnp, p, 1); + } + if (err) + *newnp = 0; + if (protocol_version == 3) + { + if (*newnp) + pthread_mutex_unlock (&(*newnp)->lock); + pthread_mutex_lock (&np->lock); + p = process_returned_stat (np, p, 0); /* XXX Do we have to lock np? */ + pthread_mutex_unlock (&np->lock); + if (*newnp) + pthread_mutex_lock (&(*newnp)->lock); + } + } + else + *newnp = 0; + + /* Notify the cache of the hit or miss. */ + enter_lookup_cache (dirhandle, dirlen, *newnp, name); + + free (rpcbuf); + + return err; +} + +/* Implement the netfs_attempt_mkdir callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_mkdir (struct iouser *cred, struct node *np, + char *name, mode_t mode) +{ + int *p; + void *rpcbuf; + error_t err; + uid_t owner; + struct node *newnp; + + if (cred->uids->num) + owner = cred->uids->ids[0]; + else + { + err = netfs_validate_stat (np, cred); + owner = err ? 0 : np->nn_stat.st_uid; + mode &= ~S_ISUID; + } + + purge_lookup_cache (np, name, strlen (name)); + + p = nfs_initialize_rpc (NFSPROC_MKDIR (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_string (p, name); + p = xdr_encode_create_state (p, mode, owner); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + + if (!err) + { + p = xdr_decode_fhandle (p, &newnp); + p = process_returned_stat (newnp, p, 1); + + /* Did we set the owner correctly? If not, try, but ignore failures. */ + if (!netfs_validate_stat (newnp, (struct iouser *) -1) + && newnp->nn_stat.st_uid != owner) + netfs_attempt_chown ((struct iouser *) -1, newnp, owner, + newnp->nn_stat.st_gid); + + /* We don't actually return this. */ + netfs_nput (newnp); + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_rmdir callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_rmdir (struct iouser *cred, struct node *np, + char *name) +{ + int *p; + void *rpcbuf; + error_t err; + + /* Should we do the same sort of thing here as with attempt_unlink? */ + + purge_lookup_cache (np, name, strlen (name)); + + p = nfs_initialize_rpc (NFSPROC_RMDIR (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_string (p, name); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (protocol_version == 3) + p = process_wcc_stat (np, p, !err); + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_link callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_link (struct iouser *cred, struct node *dir, + struct node *np, char *name, int excl) +{ + int *p; + void *rpcbuf; + error_t err = 0; + + if (!excl) + { + /* We have no RPC available that will do an atomic replacement, + so we settle for second best; just doing an unlink and ignoring + any errors. */ + pthread_mutex_lock (&dir->lock); + netfs_attempt_unlink (cred, dir, name); + pthread_mutex_unlock (&dir->lock); + } + + /* If we have postponed a translator setting on an unlinked node, + then here's where we set it, by creating the new node instead of + doing a normal link. */ + + switch (np->nn->dtrans) + { + case POSSIBLE: + case NOT_POSSIBLE: + pthread_mutex_lock (&dir->lock); + p = nfs_initialize_rpc (NFSPROC_LINK (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + { + pthread_mutex_unlock (&dir->lock); + return errno; + } + + pthread_mutex_unlock (&dir->lock); + + pthread_mutex_lock (&np->lock); + p = xdr_encode_fhandle (p, &np->nn->handle); + pthread_mutex_unlock (&np->lock); + + pthread_mutex_lock (&dir->lock); + purge_lookup_cache (dir, name, strlen (name)); + + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + pthread_mutex_unlock (&dir->lock); + + free (rpcbuf); + + break; + + case SYMLINK: + pthread_mutex_lock (&dir->lock); + p = nfs_initialize_rpc (NFSPROC_SYMLINK (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + { + pthread_mutex_unlock (&dir->lock); + return errno; + } + + p = xdr_encode_fhandle (p, &dir->nn->handle); + pthread_mutex_unlock (&dir->lock); + + p = xdr_encode_string (p, name); + + pthread_mutex_lock (&np->lock); + err = netfs_validate_stat (np, cred); + if (err) + { + pthread_mutex_unlock (&np->lock); + free (rpcbuf); + return err; + } + + if (protocol_version == 2) + { + p = xdr_encode_string (p, np->nn->transarg.name); + p = xdr_encode_sattr_stat (p, &np->nn_stat); + } + else + { + p = xdr_encode_sattr_stat (p, &np->nn_stat); + p = xdr_encode_string (p, np->nn->transarg.name); + } + pthread_mutex_unlock (&np->lock); + + pthread_mutex_lock (&dir->lock); + + purge_lookup_cache (dir, name, strlen (name)); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + + if (protocol_version == 2 && !err) + { + free (rpcbuf); + + /* NFSPROC_SYMLINK stupidly does not pass back an + fhandle, so we have to fetch one now. */ + p = nfs_initialize_rpc (NFSPROC_LOOKUP (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + { + pthread_mutex_unlock (&dir->lock); + return errno; + } + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + + pthread_mutex_unlock (&dir->lock); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + if (!err) + { + pthread_mutex_lock (&np->lock); + p = recache_handle (p, np); + p = process_returned_stat (np, p, 1); + pthread_mutex_unlock (&np->lock); + } + if (err) + err = EGRATUITOUS; /* damn */ + } + else if (protocol_version == 3) + { + if (!err) + { + pthread_mutex_unlock (&dir->lock); + pthread_mutex_lock (&np->lock); + p = recache_handle (p, np); + p = process_returned_stat (np, p, 1); + pthread_mutex_unlock (&np->lock); + pthread_mutex_lock (&dir->lock); + } + p = process_wcc_stat (dir, p, !err); + pthread_mutex_unlock (&dir->lock); + } + else + pthread_mutex_unlock (&dir->lock); + } + else + pthread_mutex_unlock (&dir->lock); + + free (rpcbuf); + break; + + case CHRDEV: + case BLKDEV: + case FIFO: + case SOCK: + + if (protocol_version == 2) + { + pthread_mutex_lock (&dir->lock); + err = verify_nonexistent (cred, dir, name); + if (err) + return err; + + p = nfs_initialize_rpc (NFSPROC_CREATE (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + { + pthread_mutex_unlock (&dir->lock); + return errno; + } + + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + pthread_mutex_unlock (&dir->lock); + + pthread_mutex_lock (&np->lock); + err = netfs_validate_stat (np, cred); + if (err) + { + pthread_mutex_unlock (&np->lock); + free (rpcbuf); + return err; + } + + p = xdr_encode_sattr_stat (p, &np->nn_stat); + pthread_mutex_unlock (&np->lock); + + pthread_mutex_lock (&dir->lock); + purge_lookup_cache (dir, name, strlen (name)); + pthread_mutex_unlock (&dir->lock); /* XXX Should this really be after the + _lengthy_ (blocking) conduct_rpc? */ + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + + if (!err) + { + pthread_mutex_lock (&np->lock); + p = recache_handle (p, np); + register_fresh_stat (np, p); + pthread_mutex_unlock (&np->lock); + } + + free (rpcbuf); + } + else /* protocol_version != 2 */ + { + pthread_mutex_lock (&dir->lock); + p = nfs_initialize_rpc (NFS3PROC_MKNOD, cred, 0, &rpcbuf, dir, -1); + if (! p) + { + pthread_mutex_unlock (&dir->lock); + return errno; + } + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + pthread_mutex_unlock (&dir->lock); + + pthread_mutex_lock (&np->lock); + err = netfs_validate_stat (np, cred); + if (err) + { + pthread_mutex_unlock (&np->lock); + free (rpcbuf); + return err; + } + *(p++) = htonl (hurd_mode_to_nfs_type (np->nn_stat.st_mode)); + p = xdr_encode_sattr_stat (p, &np->nn_stat); + if (np->nn->dtrans == BLKDEV || np->nn->dtrans == CHRDEV) + { + *(p++) = htonl (major (np->nn_stat.st_rdev)); + *(p++) = htonl (minor (np->nn_stat.st_rdev)); + } + pthread_mutex_unlock (&np->lock); + + purge_lookup_cache (dir, name, strlen (name)); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + + if (!err) + { + pthread_mutex_lock (&np->lock); + p = recache_handle (p, np); + p = process_returned_stat (np, p, 1); + pthread_mutex_unlock (&np->lock); + } + pthread_mutex_lock (&dir->lock); + p = process_wcc_stat (dir, p, !err); + pthread_mutex_unlock (&dir->lock); + } + free (rpcbuf); + } + break; + } + + if (err) + return err; + + pthread_mutex_lock (&np->lock); + + if (np->nn->dtrans == SYMLINK) + free (np->nn->transarg.name); + np->nn->dtrans = NOT_POSSIBLE; + + /* If there is a dead-dir tag lying around, it's time to delete it now. */ + if (np->nn->dead_dir) + { + struct node *dir = np->nn->dead_dir; + char *name = np->nn->dead_name; + np->nn->dead_dir = 0; + np->nn->dead_name = 0; + pthread_mutex_unlock (&np->lock); + + pthread_mutex_lock (&dir->lock); + netfs_attempt_unlink ((struct iouser *)-1, dir, name); + pthread_mutex_unlock (&dir->lock); + } + else + pthread_mutex_unlock (&np->lock); + + return 0; +} + +/* Implement the netfs_attempt_mkfile callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_mkfile (struct iouser *cred, struct node *dir, + mode_t mode, struct node **newnp) +{ + error_t err; + char *name; + static int n = 0; + + /* This is the best we can do. */ + + name = malloc (50); + if (! name) + return ENOMEM; + + do + { + sprintf (name, ".nfstmpgnu.%d", n++); + err = netfs_attempt_create_file (cred, dir, name, mode, newnp); + if (err == EEXIST) + pthread_mutex_lock (&dir->lock); /* XXX is this right? does create need this + and drop this on error? Doesn't look + like it. */ + } + while (err == EEXIST); + + if (err) + { + free (name); + return err; + } + + assert (!(*newnp)->nn->dead_dir); + assert (!(*newnp)->nn->dead_name); + netfs_nref (dir); + (*newnp)->nn->dead_dir = dir; + (*newnp)->nn->dead_name = name; + if ((*newnp)->nn->dtrans == NOT_POSSIBLE) + (*newnp)->nn->dtrans = POSSIBLE; + return 0; +} + +/* Implement the netfs_attempt_create_file callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_create_file (struct iouser *cred, struct node *np, + char *name, mode_t mode, struct node **newnp) +{ + int *p; + void *rpcbuf; + error_t err; + uid_t owner; + + if (cred->uids->num) + owner = cred->uids->ids[0]; + else + { + err = netfs_validate_stat (np, cred); + owner = err ? 0 : np->nn_stat.st_uid; + mode &= ~S_ISUID; + } + + /* RFC 1094 says that create is always exclusive. But Sun doesn't + actually *implement* the spec. No, of course not. So we have to do + it for them. */ + if (protocol_version == 2) + { + err = verify_nonexistent (cred, np, name); + if (err) + { + pthread_mutex_unlock (&np->lock); + return err; + } + } + + purge_lookup_cache (np, name, strlen (name)); + + p = nfs_initialize_rpc (NFSPROC_CREATE (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + p = xdr_encode_string (p, name); + if (protocol_version == 3) + { + /* We happen to know this is where the XID is. */ + int verf = *(int *)rpcbuf; + + *(p++) = ntohl (EXCLUSIVE); + /* 8 byte verf */ + *(p++) = ntohl (verf); + p++; + } + else + p = xdr_encode_create_state (p, mode, owner); + + err = conduct_rpc (&rpcbuf, &p); + + pthread_mutex_unlock (&np->lock); + + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (!err) + { + p = xdr_decode_fhandle (p, newnp); + p = process_returned_stat (*newnp, p, 1); + } + if (err) + *newnp = 0; + if (protocol_version == 3) + { + if (*newnp) + pthread_mutex_unlock (&(*newnp)->lock); + pthread_mutex_lock (&np->lock); + p = process_wcc_stat (np, p, 1); + pthread_mutex_unlock (&np->lock); + if (*newnp) + pthread_mutex_lock (&(*newnp)->lock); + } + + if (*newnp && !netfs_validate_stat (*newnp, (struct iouser *) -1) + && (*newnp)->nn_stat.st_uid != owner) + netfs_attempt_chown ((struct iouser *) -1, *newnp, owner, (*newnp)->nn_stat.st_gid); + } + else + *newnp = 0; + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_unlink callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_unlink (struct iouser *cred, struct node *dir, + char *name) +{ + int *p; + void *rpcbuf; + error_t err; + struct node *np; + + /* First lookup the node being removed */ + err = netfs_attempt_lookup (cred, dir, name, &np); + if (err) + { + pthread_mutex_lock (&dir->lock); + return err; + } + + /* Restore the locks to sanity. */ + pthread_mutex_unlock (&np->lock); + pthread_mutex_lock (&dir->lock); + + /* Purge the cache of entries for this node, so that we don't + regard cache-held references as live. */ + purge_lookup_cache_node (np); + + /* See if there are any other users of this node than the + one we just got; if so, we must give this file another link + so that when we delete the one we are asked for it doesn't go + away entirely. */ + if (np->references > 1) + { + char *newname = 0; + int n = 0; + + pthread_mutex_unlock (&dir->lock); + + newname = malloc (50); + if (! newname) + { + pthread_mutex_lock (&dir->lock); + netfs_nrele (np); /* XXX Is this the correct thing to do? */ + return ENOMEM; + } + + do + { + sprintf (newname, ".nfs%txgnu.%d", (ptrdiff_t) np, n++); + err = netfs_attempt_link (cred, dir, np, newname, 1); + } + while (err == EEXIST); + + if (err) + { + free (newname); + pthread_mutex_lock (&dir->lock); + netfs_nrele (np); + return err; + } + + /* Write down what name we gave it; we'll delete this when all + our uses vanish. */ + pthread_mutex_lock (&np->lock); + + if (np->nn->dead_dir) + netfs_nrele (np->nn->dead_dir); + netfs_nref (dir); + np->nn->dead_dir = dir; + if (np->nn->dead_name) + free (np->nn->dead_name); + np->nn->dead_name = newname; + if (np->nn->dtrans == NOT_POSSIBLE) + np->nn->dtrans = POSSIBLE; + + netfs_nput (np); + pthread_mutex_lock (&dir->lock); + } + else + netfs_nrele (np); + + p = nfs_initialize_rpc (NFSPROC_REMOVE (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (protocol_version == 3) + p = process_wcc_stat (dir, p, !err); + } + + free (rpcbuf); + + return err; +} + +/* Implement the netfs_attempt_rename callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_rename (struct iouser *cred, struct node *fromdir, + char *fromname, struct node *todir, char *toname, + int excl) +{ + int *p; + void *rpcbuf; + error_t err; + + if (excl) + { + struct node *np; + + /* Just do a lookup/link/unlink sequence. */ + + pthread_mutex_lock (&fromdir->lock); + err = netfs_attempt_lookup (cred, fromdir, fromname, &np); + pthread_mutex_unlock (&fromdir->lock); + if (err) + return err; + + err = netfs_attempt_link (cred, todir, np, toname, 1); + netfs_nput (np); + if (err) + return err; + + pthread_mutex_lock (&fromdir->lock); + err = netfs_attempt_unlink (cred, fromdir, fromname); + pthread_mutex_unlock (&fromdir->lock); + + /* If the unlink failed, then back out the link */ + if (err) + { + pthread_mutex_lock (&todir->lock); + netfs_attempt_unlink (cred, todir, toname); + pthread_mutex_unlock (&todir->lock); + return err; + } + + return 0; + } + + pthread_mutex_lock (&fromdir->lock); + purge_lookup_cache (fromdir, fromname, strlen (fromname)); + p = nfs_initialize_rpc (NFSPROC_RENAME (protocol_version), + cred, 0, &rpcbuf, fromdir, -1); + if (! p) + { + pthread_mutex_unlock (&fromdir->lock); + return errno; + } + + p = xdr_encode_fhandle (p, &fromdir->nn->handle); + p = xdr_encode_string (p, fromname); + pthread_mutex_unlock (&fromdir->lock); + + pthread_mutex_lock (&todir->lock); + purge_lookup_cache (todir, toname, strlen (toname)); + p = xdr_encode_fhandle (p, &todir->nn->handle); + p = xdr_encode_string (p, toname); + pthread_mutex_unlock (&todir->lock); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (protocol_version == 3) /* XXX Should we add `&& !err' ? */ + { + pthread_mutex_lock (&fromdir->lock); + p = process_wcc_stat (fromdir, p, !err); + p = process_wcc_stat (todir, p, !err); + } + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_attempt_readlink callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_readlink (struct iouser *cred, struct node *np, + char *buf) +{ + int *p; + void *rpcbuf; + error_t err; + + if (np->nn->dtrans == SYMLINK) + { + strcpy (buf, np->nn->transarg.name); + return 0; + } + + p = nfs_initialize_rpc (NFSPROC_READLINK (protocol_version), + cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + if (protocol_version == 3) + p = process_returned_stat (np, p, 0); + if (!err) + p = xdr_decode_string (p, buf); + } + + free (rpcbuf); + return err; +} + +/* Implement the netfs_check_open_permissions callback as described in + <hurd/netfs.h>. */ +error_t +netfs_check_open_permissions (struct iouser *cred, struct node *np, + int flags, int newnode) +{ + int modes; + + if (newnode || (flags & (O_READ|O_WRITE|O_EXEC)) == 0) + return 0; + + netfs_report_access (cred, np, &modes); + if ((flags & (O_READ|O_WRITE|O_EXEC)) == (flags & modes)) + return 0; + else + return EACCES; +} + +/* Implement the netfs_report_access callback as described in + <hurd/netfs.h>. */ +error_t +netfs_report_access (struct iouser *cred, + struct node *np, + int *types) +{ + error_t err; + + err = netfs_validate_stat (np, cred); + if (err) + return err; + + if (protocol_version == 2) + { + /* Hope the server means the same thing for the bits as we do. */ + *types = 0; + if (fshelp_access (&np->nn_stat, S_IREAD, cred) == 0) + *types |= O_READ; + if (fshelp_access (&np->nn_stat, S_IWRITE, cred) == 0) + *types |= O_WRITE; + if (fshelp_access (&np->nn_stat, S_IEXEC, cred) == 0) + *types |= O_EXEC; + return 0; + } + else + { + int *p; + void *rpcbuf; + error_t err; + int ret; + int write_check, execute_check; + + if (S_ISDIR (np->nn_stat.st_mode)) + { + write_check = ACCESS3_MODIFY | ACCESS3_DELETE | ACCESS3_EXTEND; + execute_check = ACCESS3_LOOKUP; + } + else + { + write_check = ACCESS3_MODIFY; + execute_check = ACCESS3_EXECUTE; + } + + p = nfs_initialize_rpc (NFS3PROC_ACCESS, cred, 0, &rpcbuf, np, -1); + if (! p) + return errno; + + p = xdr_encode_fhandle (p, &np->nn->handle); + *(p++) = htonl (ACCESS3_READ | write_check | execute_check); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + p = process_returned_stat (np, p, 0); /* XXX Should this be + protected by the + if (!err) ? */ + if (!err) + { + ret = ntohl (*p); + p++; + *types = ((ret & ACCESS3_READ ? O_READ : 0) + | (ret & write_check ? O_WRITE : 0) + | (ret & execute_check ? O_EXEC : 0)); + } + } + return err; + } +} + +/* These definitions have unfortunate side effects, don't use them, + clever though they are. */ +#if 0 +/* Implement the netfs_check_open_permissions callback as described in + <hurd/netfs.h>. */ +error_t +netfs_check_open_permissions (struct iouser *cred, struct node *np, + int flags, int newnode) +{ + char byte; + error_t err; + size_t len; + + /* Sun derived nfs client implementations attempt to reproduce the + server's permission restrictions by hoping they look like Unix, + and using that to give errors at open time. Sadly, that loses + here. So instead what we do is try and do what the user + requested; if we can't, then we fail. Otherwise, we allow the + open, but acknowledge that the server might still give an error + later. (Even with our check, the server can revoke access, thus + violiting Posix semantics; this means that erring on the side of + permitting illegal opens won't harm otherwise correct programs, + because they need to deal with revocation anyway.) We thus here + have the advantage of working correctly with servers that allow + things Unix denies. */ + + if ((flags & O_READ) == 0 + && (flags & O_WRITE) == 0 + && (flags & O_EXEC) == 0) + return 0; + + err = netfs_validate_stat (np, cred); + if (err) + return err; + + switch (np->nn_stat.st_mode & S_IFMT) + { + /* Don't know how to check, so return provisional success. */ + default: + return 0; + + case S_IFREG: + len = 1; + err = netfs_attempt_read (cred, np, 0, &len, &byte); + if (err) + { + if ((flags & O_READ) || (flags & O_EXEC)) + return err; + else + /* If we couldn't read a byte, but the user wasn't actually asking + for read, then we shouldn't inhibit the open now. */ + return 0; + } + + if (len != 1) + /* The file is empty; reads are known to be OK, but writes can't be + tested, so no matter what, return success. */ + return 0; + + if (flags & O_WRITE) + { + err = netfs_attempt_write (cred, np, 0, &len, &byte); + return err; + } + + /* Try as we might, we couldn't get the server to bump us, so + give (provisional) success. */ + return 0; + + case S_IFDIR: + if (flags & O_READ) + { + void *rpcbuf; + int *p; + + /* Issue a readdir request; if it fails, then we can + return failure. Otherwise, succeed. */ + p = nfs_initialize_rpc (NFSPROC_READDIR, cred, 0, &rpcbuf, np, -1); + p = xdr_encode_fhandle (p, &np->nn->handle); + *(p++) = 0; + *(p++) = htonl (50); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + free (rpcbuf); + + if (err) + return err; + } + return 0; + } +} + +/* Implement the netfs_report_access callback as described in + <hurd/netfs.h>. */ +void +netfs_report_access (struct iouser *cred, + struct node *np, + int *types) +{ + char byte; + error_t err; + size_t len; + + /* Much the same logic as netfs_check_open_permissions, except that + here we err on the side of denying access, and that we always + have to check everything. */ + + *types = 0; + + len = 1; + err = netfs_attempt_read (cred, np, 0, &len, &byte); + if (err) + return; + assert (len == 1 || len == 0); + + *types |= O_READ | O_EXEC; + + if (len == 1) + { + err = netfs_attempt_write (cred, np, 0, &len, &byte); + if (!err) + *types |= O_WRITE; + } + else + { + /* Oh, ugh. We have to try and write a byte and then truncate + back. God help us if the file becomes unwritable in-between. + But because of the use of this function (by setuid programs + wanting to see if they should write user's files) we must + check this and not just return a presumptive error. */ + byte = 0; + err = netfs_attempt_write (cred, np, 0, &len, &byte); + if (!err) + *types |= O_WRITE; + netfs_attempt_set_size (cred, np, 0); + } +} +#endif + +/* Fetch the complete contents of DIR into a buffer of directs. Set + *BUFP to that buffer. *BUFP must be freed by the caller when no + longer needed. If an error occurs, don't touch *BUFP and return + the error code. Set BUFSIZEP to the amount of data used inside + *BUFP and TOTALENTRIES to the total number of entries copied. */ +static error_t +fetch_directory (struct iouser *cred, struct node *dir, + void **bufp, size_t *bufsizep, int *totalentries) +{ + void *buf; + int cookie; + int *p; + void *rpcbuf; + struct dirent *entry; + void *bp; + int bufmalloced; + int eof; + error_t err; + int isnext; + + bufmalloced = read_size; + + buf = malloc (bufmalloced); + if (! buf) + return ENOMEM; + + bp = buf; + cookie = 0; + eof = 0; + *totalentries = 0; + + while (!eof) + { + /* Fetch new directory entries */ + p = nfs_initialize_rpc (NFSPROC_READDIR (protocol_version), + cred, 0, &rpcbuf, dir, -1); + if (! p) + { + free (buf); + return errno; + } + + p = xdr_encode_fhandle (p, &dir->nn->handle); + *(p++) = cookie; + *(p++) = ntohl (read_size); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p)); + p++; + } + if (err) + { + free (buf); + return err; + } + + isnext = ntohl (*p); + p++; + + /* Now copy them one at a time. */ + while (isnext) + { + ino_t fileno; + int namlen; + int reclen; + + fileno = ntohl (*p); + p++; + namlen = ntohl (*p); + p++; + + /* There's a hidden +1 here for the null byte and -1 because d_name + has a size of one already in the sizeof. */ + reclen = sizeof (struct dirent) + namlen; + reclen = (reclen + 3) & ~3; /* make it a multiple of four */ + + /* Expand buffer if necessary */ + if (bp + reclen > buf + bufmalloced) + { + char *newbuf; + + newbuf = realloc (buf, bufmalloced *= 2); + assert (newbuf); + if (newbuf != buf) + bp = newbuf + (bp - buf); + buf = newbuf; + } + + /* Fill in new entry */ + entry = (struct dirent *) bp; + entry->d_fileno = fileno; + entry->d_reclen = reclen; + entry->d_type = DT_UNKNOWN; + entry->d_namlen = namlen; + memcpy (entry->d_name, p, namlen); + entry->d_name[namlen] = '\0'; + + p += INTSIZE (namlen); + bp = bp + entry->d_reclen; + + ++*totalentries; + + cookie = *(p++); + isnext = ntohl (*p); + p++; + } + + eof = ntohl (*p); + p++; + free (rpcbuf); + } + + /* Return it all to the user */ + *bufp = buf; + *bufsizep = bufmalloced; + return 0; +} + + +/* Implement the netfs_get_directs callback as described in + <hurd/netfs.h>. */ +error_t +netfs_get_dirents (struct iouser *cred, struct node *np, + int entry, int nentries, char **data, + mach_msg_type_number_t *datacnt, + vm_size_t bufsiz, int *amt) +{ + void *buf; + size_t our_bufsiz, allocsize; + void *bp; + char *userdp; + error_t err; + int totalentries; + int thisentry; + + err = fetch_directory (cred, np, &buf, &our_bufsiz, &totalentries); + if (err) + return err; + + /* Allocate enough space to hold the maximum we might return. */ + if (!bufsiz || bufsiz > our_bufsiz) + allocsize = round_page (our_bufsiz); + else + allocsize = round_page (bufsiz); + if (allocsize > *datacnt) + *data = mmap (0, allocsize, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + + /* Skip ahead to the correct entry. */ + bp = buf; + for (thisentry = 0; thisentry < entry;) + { + struct dirent *entry = (struct dirent *) bp; + bp += entry->d_reclen; + thisentry++; + } + + /* Now copy them one at a time */ + { + int entries_copied; + + for (entries_copied = 0, userdp = *data; + (nentries == -1 || entries_copied < nentries) + && (!bufsiz || userdp - *data < bufsiz) + && thisentry < totalentries;) + { + struct dirent *entry = (struct dirent *) bp; + memcpy (userdp, bp, entry->d_reclen); + bp += entry->d_reclen; + userdp += entry->d_reclen; + entries_copied++; + thisentry++; + } + *amt = entries_copied; + } + + free (buf); + + /* If we allocated the buffer ourselves, but didn't use + all the pages, free the extra. */ + if (allocsize > *datacnt + && round_page (userdp - *data) < round_page (allocsize)) + munmap ((caddr_t) round_page (userdp), + round_page (allocsize) - round_page (userdp - *data)); + + *datacnt = userdp - *data; + return 0; +} + + +/* Implement the netfs_attempt_mksymlink callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_mksymlink (struct iouser *cred, + struct node *np, + char *arg) +{ + if (np->nn->dtrans == NOT_POSSIBLE) + return EOPNOTSUPP; + + if (np->nn->dtrans == SYMLINK) + free (np->nn->transarg.name); + + np->nn->transarg.name = malloc (strlen (arg) + 1); + strcpy (np->nn->transarg.name, arg); + np->nn->dtrans = SYMLINK; + np->nn->stat_updated = 0; + return 0; +} + +/* Implement the netfs_attempt_mkdev callback as described in + <hurd/netfs.h>. */ +error_t +netfs_attempt_mkdev (struct iouser *cred, + struct node *np, + mode_t type, + dev_t indexes) +{ + if (np->nn->dtrans == NOT_POSSIBLE) + return EOPNOTSUPP; + + if (np->nn->dtrans == SYMLINK) + free (np->nn->transarg.name); + + np->nn->transarg.indexes = indexes; + if (type == S_IFBLK) + np->nn->dtrans = BLKDEV; + else + np->nn->dtrans = CHRDEV; + np->nn->stat_updated = 0; + return 0; +} diff --git a/nfs/rpc.c b/nfs/rpc.c new file mode 100644 index 00000000..c0d0290e --- /dev/null +++ b/nfs/rpc.c @@ -0,0 +1,425 @@ +/* rpc.c - SunRPC management for NFS client. + Copyright (C) 1994, 1995, 1996, 1997, 2002 Free Software Foundation + + 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. + + 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 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "nfs.h" + +/* Needed in order to get the RPC header files to include correctly. */ +#undef TRUE +#undef FALSE +#define malloc spoiufasdf /* Avoid bogus definition in rpc/types.h. */ + +#include <rpc/types.h> +#include <rpc/xdr.h> +#include <rpc/auth.h> +#include <rpc/rpc_msg.h> +#include <rpc/auth_unix.h> + +#undef malloc /* Get rid of the sun block. */ + +#include <netinet/in.h> +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <unistd.h> +#include <stdio.h> + +/* One of these exists for each pending RPC. */ +struct rpc_list +{ + struct rpc_list *next, **prevp; + void *reply; +}; + +/* A list of all pending RPCs. */ +static struct rpc_list *outstanding_rpcs; + +/* Wake up this condition when an outstanding RPC has received a reply + or we should check for timeouts. */ +static pthread_cond_t rpc_wakeup = PTHREAD_COND_INITIALIZER; + +/* Lock the global data and the REPLY fields of outstanding RPC's. */ +static pthread_mutex_t outstanding_lock = PTHREAD_MUTEX_INITIALIZER; + + + +/* Generate and return a new transaction ID. */ +static inline int +generate_xid () +{ + static int nextxid; + + if (nextxid == 0) + nextxid = mapped_time->seconds; + + return nextxid++; +} + +/* Set up an RPC for procdeure RPC_PROC for talking to the server + PROGRAM of version VERSION. Allocate storage with malloc and point + *BUF at it; caller must free this when done. Allocate at least LEN + bytes more than the usual amount for the RPC. Initialize the RPC + credential structure with UID, GID, and SECOND_GID; any of these + may be -1 to indicate that it does not apply, however, exactly zero + or two of UID and GID must be -1. The returned address is a pointer + to the start of the payload. If NULL is returned, an error occurred + and the code is set in errno. */ +int * +initialize_rpc (int program, int version, int rpc_proc, + size_t len, void **bufp, + uid_t uid, gid_t gid, gid_t second_gid) +{ + void *buf; + int *p, *lenaddr; + struct rpc_list *hdr; + + buf = malloc (len + 1024); + if (! buf) + { + errno = ENOMEM; + return NULL; + } + + /* First the struct rpc_list bit. */ + hdr = buf; + hdr->reply = 0; + + p = buf + sizeof (struct rpc_list); + + /* RPC header */ + *(p++) = htonl (generate_xid ()); + *(p++) = htonl (CALL); + *(p++) = htonl (RPC_MSG_VERSION); + *(p++) = htonl (program); + *(p++) = htonl (version); + *(p++) = htonl (rpc_proc); + + assert ((uid == -1) == (gid == -1)); + + if (uid == -1) + { + /* No authentication */ + *(p++) = htonl (AUTH_NONE); + *(p++) = 0; + } + else + { + /* Unixy authentication */ + *(p++) = htonl (AUTH_UNIX); + /* The length of the message. We do not yet know what this + is, so, just remember where we should put it when we know */ + lenaddr = p++; + *(p++) = htonl (mapped_time->seconds); + p = xdr_encode_string (p, hostname); + *(p++) = htonl (uid); + *(p++) = htonl (gid); + if (second_gid == -1) + *(p++) = 0; + else + { + *(p++) = htonl (1); + *(p++) = htonl (second_gid); + } + *lenaddr = htonl ((p - (lenaddr + 1)) * sizeof (int)); + } + + /* VERF field */ + *(p++) = htonl (AUTH_NONE); + *(p++) = 0; + + *bufp = buf; + return p; +} + +/* Remove HDR from the list of pending RPC's. The rpc_list's lock + (OUTSTANDING_LOCK) must be held. */ +static inline void +unlink_rpc (struct rpc_list *hdr) +{ + *hdr->prevp = hdr->next; + if (hdr->next) + hdr->next->prevp = hdr->prevp; +} + +/* Insert HDR at the head of the LIST. The rpc_list's lock + (OUTSTANDING_LOCK) must be held. */ +static inline void +link_rpc (struct rpc_list **list, struct rpc_list *hdr) +{ + hdr->next = *list; + if (hdr->next) + hdr->next->prevp = &hdr->next; + hdr->prevp = list; + *list = hdr; +} + +/* Send the specified RPC message. *RPCBUF is the initialized buffer + from a previous initialize_rpc call; *PP, the payload, points past + the filledin args. Set *PP to the address of the reply contents + themselves. The user will be expected to free *RPCBUF (which will + have changed) when done with the reply contents. The old value of + *RPCBUF will be freed by this routine. */ +error_t +conduct_rpc (void **rpcbuf, int **pp) +{ + struct rpc_list *hdr = *rpcbuf; + error_t err; + size_t cc, nc; + int timeout = initial_transmit_timeout; + time_t lasttrans; + int ntransmit = 0; + int *p; + int xid; + int n; + int cancel; + + pthread_mutex_lock (&outstanding_lock); + + link_rpc (&outstanding_rpcs, hdr); + + xid = * (int *) (*rpcbuf + sizeof (struct rpc_list)); + + do + { + /* If we've sent enough, give up. */ + if (mounted_soft && ntransmit == soft_retries) + { + unlink_rpc (hdr); + pthread_mutex_unlock (&outstanding_lock); + return ETIMEDOUT; + } + + /* Issue the RPC. */ + lasttrans = mapped_time->seconds; + ntransmit++; + nc = (void *) *pp - *rpcbuf - sizeof (struct rpc_list); + cc = write (main_udp_socket, *rpcbuf + sizeof (struct rpc_list), nc); + if (cc == -1) + { + unlink_rpc (hdr); + pthread_mutex_unlock (&outstanding_lock); + return errno; + } + else + assert (cc == nc); + + /* Wait for reply. */ + cancel = 0; + while (!hdr->reply + && (mapped_time->seconds - lasttrans < timeout) + && !cancel) + cancel = pthread_hurd_cond_wait_np (&rpc_wakeup, &outstanding_lock); + + if (cancel) + { + unlink_rpc (hdr); + pthread_mutex_unlock (&outstanding_lock); + return EINTR; + } + + /* hdr->reply will have been filled in by rpc_receive_thread, + if it has been filled in, then the rpc has been fulfilled, + otherwise, retransmit and continue to wait. */ + if (!hdr->reply) + { + timeout *= 2; + if (timeout > max_transmit_timeout) + timeout = max_transmit_timeout; + } + } + while (!hdr->reply); + + pthread_mutex_unlock (&outstanding_lock); + + /* Switch to the reply buffer. */ + *rpcbuf = hdr->reply; + free (hdr); + + /* Process the reply, dissecting errors. When we're done and if + there is no error, set *PP to the rpc return contents. */ + p = (int *) *rpcbuf; + + /* If the transmition id does not match that in the message, + something strange happened in rpc_receive_thread. */ + assert (*p == xid); + p++; + + switch (ntohl (*p)) + { + default: + err = EBADRPC; + break; + + case REPLY: + p++; + switch (ntohl (*p)) + { + default: + err = EBADRPC; + break; + + case MSG_DENIED: + p++; + switch (ntohl (*p)) + { + default: + err = EBADRPC; + break; + + case RPC_MISMATCH: + err = ERPCMISMATCH; + break; + + case AUTH_ERROR: + p++; + switch (ntohl (*p)) + { + case AUTH_BADCRED: + case AUTH_REJECTEDCRED: + err = EAUTH; + break; + + case AUTH_TOOWEAK: + err = ENEEDAUTH; + break; + + case AUTH_BADVERF: + case AUTH_REJECTEDVERF: + default: + err = EBADRPC; + break; + } + break; + } + break; + + case MSG_ACCEPTED: + p++; + + /* Process VERF field. */ + p++; /* Skip verf type. */ + n = ntohl (*p); /* Size of verf. */ + p++; + p += INTSIZE (n); /* Skip verf itself. */ + + switch (ntohl (*p)) + { + default: + case GARBAGE_ARGS: + err = EBADRPC; + break; + + case PROG_UNAVAIL: + err = EPROGUNAVAIL; + break; + + case PROG_MISMATCH: + err = EPROGMISMATCH; + break; + + case PROC_UNAVAIL: + err = EPROCUNAVAIL; + break; + + case SUCCESS: + p++; + *pp = p; + err = 0; + break; + } + break; + } + break; + } + return err; +} + +/* Dedicated thread to signal those waiting on rpc_wakeup + once a second. */ +void * +timeout_service_thread (void *arg) +{ + (void) arg; + + while (1) + { + sleep (1); + pthread_mutex_lock (&outstanding_lock); + pthread_cond_broadcast (&rpc_wakeup); + pthread_mutex_unlock (&outstanding_lock); + } + + return NULL; +} + +/* Dedicate thread to receive RPC replies, register them on the queue + of pending wakeups, and deal appropriately. */ +void * +rpc_receive_thread (void *arg) +{ + void *buf; + + (void) arg; + + /* Allocate a receive buffer. */ + buf = malloc (1024 + read_size); + assert (buf); + + while (1) + { + int cc = read (main_udp_socket, buf, 1024 + read_size); + if (cc == -1) + { + error (0, errno, "nfs read"); + continue; + } + else + { + struct rpc_list *r; + int xid = *(int *)buf; + + pthread_mutex_lock (&outstanding_lock); + + /* Find the rpc that we just fulfilled. */ + for (r = outstanding_rpcs; r; r = r->next) + { + if (* (int *) &r[1] == xid) + { + unlink_rpc (r); + r->reply = buf; + pthread_cond_broadcast (&rpc_wakeup); + break; + } + } +#if 0 + if (! r) + fprintf (stderr, "NFS dropping reply xid %d\n", xid); +#endif + pthread_mutex_unlock (&outstanding_lock); + + /* If r is not null then we had a message from a pending + (i.e. known) rpc. Thus, it was fulfilled and if we want + to get another request, a new buffer is needed. */ + if (r) + { + buf = malloc (1024 + read_size); + assert (buf); + } + } + } + + return NULL; +} diff --git a/nfs/storage-info.c b/nfs/storage-info.c new file mode 100644 index 00000000..7427b3d8 --- /dev/null +++ b/nfs/storage-info.c @@ -0,0 +1,104 @@ +/* file_get_storage_info RPC for NFS client filesystem + Copyright (C) 2001,02 Free Software Foundation, Inc. + + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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, USA. */ + +#include "nfs.h" +#include <hurd/netfs.h> +#include <stdio.h> + +error_t +netfs_file_get_storage_info (struct iouser *cred, + struct node *np, + mach_port_t **ports, + mach_msg_type_name_t *ports_type, + mach_msg_type_number_t *num_ports, + int **ints, + mach_msg_type_number_t *num_ints, + off_t **offsets, + mach_msg_type_number_t *num_offsets, + char **data, + mach_msg_type_number_t *data_len) +{ + int name_len, fhpos; + error_t err; + + inline int fmt (size_t buflen) + { + return snprintf (*data, buflen, + "nfsv%u://%s:%u/%n%*c?rsize=%u&wsize=%u", + protocol_version, mounted_hostname, mounted_nfs_port, + &fhpos, (int) (np->nn->handle.size * 2), + 'X', /* filled below */ + read_size, write_size); + } + + /* We return the file size, so make sure we have it up to date now. */ + err = netfs_validate_stat (np, cred); + if (err) + return err; + + /* Format the name, and then do it again if the buffer was too short. */ + name_len = fmt (*data_len); + if (name_len < 0) + return errno; + ++name_len; /* Include the terminating null. */ + if (name_len <= *data_len) + *data_len = name_len; + else + { + *data = mmap (0, name_len, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + if (*data == MAP_FAILED) + return errno; + *data_len = fmt (name_len) + 1; + assert (*data_len == name_len); + } + + /* Now fill in the file handle data in hexadecimal. */ + { + static const char hexdigits[] = "0123456789abcdef"; + size_t i; + for (i = 0; i < np->nn->handle.size; ++i) + { + (*data)[fhpos++] = hexdigits[(uint8_t)np->nn->handle.data[i] >> 4]; + (*data)[fhpos++] = hexdigits[(uint8_t)np->nn->handle.data[i] & 0xf]; + } + } + + /* Now fill in the rest of the canonical-form storage-info data, which + just describes a single run of the file's size, a block-size of one + byte, and our URL as the name for the network store type. */ + + *num_ports = 0; + *ports_type = MACH_MSG_TYPE_COPY_SEND; + + assert (*num_offsets >= 2); /* mig always gives us some */ + *num_offsets = 2; + (*offsets)[0] = 0; + (*offsets)[1] = np->nn_stat.st_size; + + assert (*num_ints >= 6); /* mig always gives us some */ + *num_ints = 1; + (*ints)[0] = STORAGE_NETWORK; + (*ints)[1] = 0; /* XXX readonly if we supported it */ + (*ints)[2] = 1; /* block size */ + (*ints)[3] = 1; /* 1 run in offsets list */ + (*ints)[4] = name_len; + (*ints)[5] = 0; /* misc len */ + + return 0; +} |