diff options
Diffstat (limited to 'nfs/ops.c')
-rw-r--r-- | nfs/ops.c | 1757 |
1 files changed, 1757 insertions, 0 deletions
diff --git a/nfs/ops.c b/nfs/ops.c new file mode 100644 index 00000000..c8e7615b --- /dev/null +++ b/nfs/ops.c @@ -0,0 +1,1757 @@ +/* Libnetfs callbacks for node operations in NFS client + Copyright (C) 1994, 1995, 1996, 1997, 1999 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" +#include <hurd/netfs.h> +#include <netinet/in.h> +#include <string.h> +#include <fcntl.h> +#include <stdio.h> +#include <dirent.h> +#include <unistd.h> +#include <maptime.h> + +/* We have fresh stat information for NP; the 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; + + 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) +{ + int attrs_exist; + + if (protocol_version == 2) + return register_fresh_stat (np, p); + else + { + attrs_exist = ntohl (*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++); + 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); + p = xdr_encode_fhandle (p, &np->nn->handle); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + err = nfs_error_trans (ntohl (*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); + 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++)); + 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; + 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); + 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++)); + 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); + 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++)); + 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); + 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++)); + 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) + { + err = netfs_validate_stat (np, cred); + if (!err && np->nn_stat.st_size == size) + err = 0; + else + /* Never mind, put the old error back */ + err = EACCES; + } + + 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); + p = xdr_encode_fhandle (p, &np->nn->handle); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + err = nfs_error_trans (ntohl (*p++)); + + if (!err) + { + p++; /* skip IOSIZE field */ + st->f_bsize = ntohl (*p++); + st->f_blocks = ntohl (*p++); + st->f_bfree = ntohl (*p++); + st->f_bavail = ntohl (*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); + 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++)); + + if (!err || protocol_version == 3) + p = process_returned_stat (np, p, !err); + + if (err) + { + free (rpcbuf); + return err; + } + + trans_len = ntohl (*p++); + if (trans_len > thisamt) + trans_len = thisamt; /* ??? */ + + if (protocol_version == 3) + eof = ntohl (*p++); + else + eof = (trans_len < thisamt); + + bcopy (p, data, 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); + 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++)); + if (!err || protocol_version == 3) + p = process_wcc_stat (np, p, !err); + if (!err) + { + if (protocol_version == 3) + { + count = ntohl (*p++); + p++; /* ignore COMMITTED */ + /* ignore verf for now */ + p += NFS3_WRITEVERFSIZE / sizeof (int); + } + else + /* assume it wrote the whole thing */ + count = thisamt; + + free (rpcbuf); + amt -= count; + data += count; + offset += count; + } + } + + if (err == EINTR && amt != *len) + { + *len -= amt; + free (rpcbuf); + return 0; + } + + if (err) + { + *len = 0; + free (rpcbuf); + 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); + 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++)); + + 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); + 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; + bcopy (np->nn->handle.data, dirhandle, dirlen); + + mutex_unlock (&np->lock); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*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) + mutex_unlock (&(*newnp)->lock); + mutex_lock (&np->lock); + p = process_returned_stat (np, p, 0); + mutex_unlock (&np->lock); + if (*newnp) + 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); + 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 = 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, -1) && newnp->nn_stat.st_uid != owner) + netfs_attempt_chown (-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); + 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++)); + 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. */ + mutex_lock (&dir->lock); + netfs_attempt_unlink (cred, dir, name); + 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: + mutex_lock (&dir->lock); + p = nfs_initialize_rpc (NFSPROC_LINK (protocol_version), + cred, 0, &rpcbuf, dir, -1); + mutex_unlock (&dir->lock); + + mutex_lock (&np->lock); + p = xdr_encode_fhandle (p, &np->nn->handle); + mutex_unlock (&np->lock); + + 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++)); + mutex_unlock (&dir->lock); + + free (rpcbuf); + + break; + + case SYMLINK: + mutex_lock (&dir->lock); + p = nfs_initialize_rpc (NFSPROC_SYMLINK (protocol_version), + cred, 0, &rpcbuf, dir, -1); + p = xdr_encode_fhandle (p, &dir->nn->handle); + mutex_unlock (&dir->lock); + + p = xdr_encode_string (p, name); + + mutex_lock (&np->lock); + err = netfs_validate_stat (np, cred); + if (err) + { + 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); + } + mutex_unlock (&np->lock); + + mutex_lock (&dir->lock); + + purge_lookup_cache (dir, name, strlen (name)); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*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); + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + + mutex_unlock (&dir->lock); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + err = nfs_error_trans (ntohl (*p++)); + if (!err) + { + mutex_lock (&np->lock); + p = recache_handle (p, np); + p = process_returned_stat (np, p, 1); + mutex_unlock (&np->lock); + } + if (err) + err = EGRATUITOUS; /* damn */ + } + else if (protocol_version == 3) + { + if (!err) + { + mutex_unlock (&dir->lock); + mutex_lock (&np->lock); + p = recache_handle (p, np); + p = process_returned_stat (np, p, 1); + mutex_unlock (&np->lock); + mutex_lock (&dir->lock); + } + p = process_wcc_stat (dir, p, !err); + mutex_unlock (&dir->lock); + } + else + mutex_unlock (&dir->lock); + } + else + mutex_unlock (&dir->lock); + + free (rpcbuf); + break; + + case CHRDEV: + case BLKDEV: + case FIFO: + case SOCK: + + if (protocol_version == 2) + { + 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); + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + mutex_unlock (&dir->lock); + + mutex_lock (&np->lock); + err = netfs_validate_stat (np, cred); + if (err) + { + mutex_unlock (&np->lock); + free (rpcbuf); + return err; + } + + p = xdr_encode_sattr_stat (p, &np->nn_stat); + mutex_unlock (&np->lock); + + mutex_lock (&dir->lock); + purge_lookup_cache (dir, name, strlen (name)); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + err = nfs_error_trans (ntohl (*p++)); + mutex_unlock (&dir->lock); + + mutex_lock (&np->lock); + p = recache_handle (p, np); + register_fresh_stat (np, p); + mutex_unlock (&np->lock); + free (rpcbuf); + } + else + { + mutex_lock (&dir->lock); + p = nfs_initialize_rpc (NFS3PROC_MKNOD, cred, 0, &rpcbuf, dir, -1); + p = xdr_encode_fhandle (p, &dir->nn->handle); + p = xdr_encode_string (p, name); + mutex_unlock (&dir->lock); + + mutex_lock (&np->lock); + err = netfs_validate_stat (np, cred); + if (err) + { + 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)); + } + mutex_unlock (&np->lock); + + purge_lookup_cache (dir, name, strlen (name)); + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p++)); + if (!err) + { + mutex_lock (&np->lock); + p = recache_handle (p, np); + p = process_returned_stat (np, p, 1); + mutex_unlock (&np->lock); + } + mutex_lock (&dir->lock); + p = process_wcc_stat (dir, p, !err); + mutex_unlock (&dir->lock); + } + free (rpcbuf); + } + break; + } + + if (err) + return err; + + 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; + mutex_unlock (&np->lock); + + mutex_lock (&dir->lock); + netfs_attempt_unlink ((struct iouser *)-1, dir, name); + mutex_unlock (&dir->lock); + } + else + 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); + + do + { + sprintf (name, ".nfstmpgnu.%d", n++); + err = netfs_attempt_create_file (cred, dir, name, mode, newnp); + if (err == EEXIST) + mutex_lock (&dir->lock); + } + 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) + { + 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); + 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); + + mutex_unlock (&np->lock); + + if (!err) + { + err = nfs_error_trans (ntohl (*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) + mutex_unlock (&(*newnp)->lock); + mutex_lock (&np->lock); + p = process_wcc_stat (np, p, 1); + mutex_unlock (&np->lock); + if (*newnp) + mutex_lock (&(*newnp)->lock); + } + + if (*newnp && !netfs_validate_stat (*newnp, -1) + && (*newnp)->nn_stat.st_uid != owner) + netfs_attempt_chown (-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) + { + mutex_lock (&dir->lock); + return err; + } + + /* Restore the locks to sanity. */ + mutex_unlock (&np->lock); + 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; + int n = 0; + + mutex_unlock (&dir->lock); + + newname = malloc (50); + do + { + sprintf (newname, ".nfs%xgnu.%d", (int) np, n++); + err = netfs_attempt_link (cred, dir, np, newname, 1); + } + while (err == EEXIST); + + if (err) + { + free (newname); + 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. */ + 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); + mutex_lock (&dir->lock); + } + else + netfs_nrele (np); + + p = nfs_initialize_rpc (NFSPROC_REMOVE (protocol_version), + cred, 0, &rpcbuf, dir, -1); + 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++)); + 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. */ + + mutex_lock (&fromdir->lock); + err = netfs_attempt_lookup (cred, fromdir, fromname, &np); + mutex_unlock (&fromdir->lock); + if (err) + return err; + + err = netfs_attempt_link (cred, todir, np, toname, 1); + netfs_nput (np); + if (err) + return err; + + mutex_lock (&fromdir->lock); + err = netfs_attempt_unlink (cred, fromdir, fromname); + mutex_unlock (&fromdir->lock); + + /* If the unlink failed, then back out the link */ + if (err) + { + mutex_lock (&todir->lock); + netfs_attempt_unlink (cred, todir, toname); + mutex_unlock (&todir->lock); + return err; + } + + return 0; + } + + mutex_lock (&fromdir->lock); + purge_lookup_cache (fromdir, fromname, strlen (fromname)); + p = nfs_initialize_rpc (NFSPROC_RENAME (protocol_version), + cred, 0, &rpcbuf, fromdir, -1); + p = xdr_encode_fhandle (p, &fromdir->nn->handle); + p = xdr_encode_string (p, fromname); + mutex_unlock (&fromdir->lock); + + 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); + mutex_unlock (&todir->lock); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*p++)); + if (protocol_version == 3) + { + 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); + p = xdr_encode_fhandle (p, &np->nn->handle); + + err = conduct_rpc (&rpcbuf, &p); + if (!err) + { + err = nfs_error_trans (ntohl (*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 be 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); + 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 = process_returned_stat (np, p, 0); + if (!err) + { + ret = ntohl (*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++)); + 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); + 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); + 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++)); + if (err) + { + free (buf); + return err; + } + + isnext = ntohl (*p++); + + /* Now copy them one at a time. */ + while (isnext) + { + ino_t fileno; + int namlen; + int reclen; + + fileno = ntohl (*p++); + namlen = ntohl (*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); + 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; + bcopy (p, entry->d_name, namlen); + entry->d_name[namlen] = '\0'; + + p += INTSIZE (namlen); + bp = bp + entry->d_reclen; + + ++*totalentries; + + cookie = *p++; + isnext = ntohl (*p++); + } + + eof = ntohl (*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; + bcopy (bp, userdp, 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_set_translator callback as described in + <hurd/netfs.h>. */ +error_t +netfs_set_translator (struct iouser *cred, + struct node *np, + char *argz, + size_t argzlen) +{ + return EOPNOTSUPP; +} + +/* 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; +} |