diff options
Diffstat (limited to 'nfs/rpc.c')
-rw-r--r-- | nfs/rpc.c | 424 |
1 files changed, 339 insertions, 85 deletions
@@ -1,5 +1,5 @@ -/* - Copyright (C) 1994 Free Software Foundation +/* SunRPC management for NFS client + Copyright (C) 1994, 1995, 1996 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 @@ -15,116 +15,370 @@ 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 +#include <rpc/types.h> +#include <rpc/xdr.h> +#include <rpc/auth.h> +#include <rpc/rpc_msg.h> +#include <rpc/auth_unix.h> + +#include <netinet/in.h> +#include <assert.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> + +/* One of these exists for each pending RPC. */ struct rpc_list { struct rpc_list *next, **prevp; - - void *rpc; - size_t rpc_len; void *reply; - size_t reply_buflen; - size_t reply_len; - int reply_received; }; +/* A list of all the pending RPCs. */ static struct rpc_list *outstanding_rpcs; -static struct mutex outstanding_lock = MUTEX_INITIALIZER; + +/* This lock must be held around any modifications to the list + structure of outstanding_rpcs. */ +static spin_lock_t rpc_list_lock = SPIN_LOCK_INITIALIZER; + +/* Wake up this condition when an outstanding RPC has received a reply + or we should check for timeouts. */ static struct condition rpc_wakeup = CONDITION_INITIALIZER; -/* Send an RPC message to target TARGET from buffer BUF of size - LEN, waiting for a reply. The expected reply length is - REPLY_LEN; a buffer that big is provided as REPLY_BUF. - Set REPLY_CONTENTS to the actual protocol-level reply, of - size REPLY_CONTENTS_LEN. AUTH is the authentication used - in constructing BUF.*/ -error_t -rpc_send (struct rpc_target *target, void *buf, size_t len, - void *reply_buf, size_t reply_len, void **reply_contents, - size_t *reply_contents_len, struct rpc_auth *auth) +/* This lock must be held around modifications of the REPLY members of + records on outstanding_rpcs and around uses of rpc_wakeup. */ +static struct mutex outstanding_lock = MUTEX_INITIALIZER; + + + +/* Generate and return a new transaction ID. */ +static int +generate_xid () { - struct rpc_list record; - struct rpcv2_msg_hdr *reply; - struct rpcv2_msg_hdr *request; - struct rpcv2_accepted_reply *accept; - struct rpcv2_rejected_reply *reject; - struct rpcv2_auth *authdata; - struct rpcv2_verf *verf; - error_t error; - - mutex_lock (&outstanding_lock); - - record.rpc = request = buf; - record.len = len; - record.reply = reply_buf; - record.reply_len = reply_len; - record.reply_received = 0; - - record.next = outstanding_rpcs; - if (record.next) - record.next->prevp = &record.next; - outstanding_rpcs = &record; - record.prevp = &outstanding_rpcs; - - rpc_transmit (target, &record); - - while (!record.reply_received) - condition_wait (&rpc_wakeup, &outstanding_lock); + static int nextxid; - /* Examine the reply to see whether any RPC level errors have happened. */ + if (nextxid == 0) + nextxid = mapped_time->seconds; - reply = record.reply; + 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 an RPC. Initialize the RPC + credential structure with UID, GID, and SECOND_GID. (Any of those + may be -1 to indicate that it does not apply; exactly or two of UID + and GID must be -1, however.) */ +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 = malloc (len + 1024); + int *p, *lenaddr; + struct rpc_list *hdr; + + /* First the struct rpc_list bit. */ + hdr = buf; + hdr->reply = 0; - assert (reply->xid == request->xid); - assert (reply->mtype == RPCV2_REPLY); + 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); - if (reply->body.reply.reply_state == RPCV2_MSG_ACCEPTED) - { - /* Ignore VERF */ - verf = &reply->body.reply.rest; - accept = (void *)verf + sizeof (struct rpcv2_verf) + verf->nbytes; + assert ((uid == -1) == (gid == -1)); - /* The message was accepted; return the appropriate error - (or success) to the caller. */ - if (accept->stat == RPCV2_SUCCESS) + if (uid != -1) + { + *p++ = htonl (AUTH_UNIX); + lenaddr = p++; + *p++ = htonl (mapped_time->seconds); + p = xdr_encode_string (p, hostname); + *p++ = htonl (uid); + *p++ = htonl (gid); + if (second_gid != -1) { - /* Ah hah! Return to the user */ - *reply_contents = &accept->reply_data.results; - *reply_contents_len = record.reply_len; - *reply_contents_len -= ((void *)&accept->reply_data.result - - (void *) reply); - error = 0; + *p++ = htonl (1); + *p++ = htonl (second_gid); } else - error = EOPNOTSUPP; + *p++ = 0; + *lenaddr = htonl ((p - (lenaddr + 1)) * sizeof (int)); } - else if (reply->body.reply.reply_state == RPCV2_MSG_DENIED) + else { - /* For some reason we didn't win. */ - reject = &reply->body.reply.rest; - if (reject->stat == RPCV2_RPC_MISMATCH) - error = EOPNOTSUPP; - else if (reject->stat == RPCV2_AUTH_ERROR) - error = EACCES; - else - error = EIEIO; + *p++ = htonl (AUTH_NULL); + *p++ = 0; } - else - error = EIEIO; + + /* VERF field */ + *p++ = htonl (AUTH_NULL); + *p++ = 0; + + *bufp = buf; + return p; +} + +/* Remove HDR from the list of pending RPC's. */ +static void +unlink_rpc (struct rpc_list *hdr) +{ + spin_lock (&rpc_list_lock); + *hdr->prevp = hdr->next; + if (hdr->next) + hdr->next->prevp = hdr->prevp; + spin_unlock (&rpc_list_lock); +} + +/* Send the specified RPC message. *RPCBUF is the initialized buffer + from a previous initialize_rpc call; *PP points past the filled + in 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; + + /* Link it in */ + spin_lock (&rpc_list_lock); + hdr->next = outstanding_rpcs; + if (hdr->next) + hdr->next->prevp = &hdr->next; + hdr->prevp = &outstanding_rpcs; + outstanding_rpcs = hdr; + spin_unlock (&rpc_list_lock); + + xid = * (int *) (*rpcbuf + sizeof (struct rpc_list)); + + do + { + /* If we've sent enough, give up */ + if (mounted_soft && ntransmit == soft_retries) + { + unlink_rpc (hdr); + return ETIMEDOUT; + } + + /* Issue the RPC */ + mutex_lock (&outstanding_lock); + 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) + assert_perror (errno); + else + assert (cc == nc); + + /* Wait for reply */ + cancel = 0; + while (!hdr->reply + && (mapped_time->seconds - lasttrans < timeout) + && !cancel) + cancel = hurd_condition_wait (&rpc_wakeup, &outstanding_lock); + mutex_unlock (&outstanding_lock); + + if (cancel) + { + unlink_rpc (hdr); + return EINTR; + } + + if (!hdr->reply) + { + timeout *=2; + if (timeout > max_transmit_timeout) + timeout = max_transmit_timeout; + } + } + while (!hdr->reply); + + /* Switch to the reply buffer. */ + *rpcbuf = hdr->reply; + free (hdr); + + /* Process the reply, dissecting errors. When we're done, set *PP to + the rpc return contents, if there is no error. */ + p = (int *) *rpcbuf; + + assert (*p == xid); + p++; - *record.prevp = record.next; - if (record.next) - record.next->prevp = record.prevp; + switch (ntohl (*p++)) + { + default: + err = EBADRPC; + break; - mutex_unlock (&outstanding_lock); - return error; + case REPLY: + switch (ntohl (*p++)) + { + default: + err = EBADRPC; + break; + + case MSG_DENIED: + switch (ntohl (*p++)) + { + default: + err = EBADRPC; + break; + + case RPC_MISMATCH: + err = ERPCMISMATCH; + break; + + case AUTH_ERROR: + 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: + + /* Process VERF field. */ + p++; /* skip verf type */ + n = ntohl (*p++); /* size of verf */ + 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: + *pp = p; + err = 0; + break; + } + break; + } + break; + } + return err; +} + +/* Dedicated thread to wakeup rpc_wakeup once a second. */ +void +timeout_service_thread () +{ + while (1) + { + sleep (1); + mutex_lock (&outstanding_lock); + condition_broadcast (&rpc_wakeup); + mutex_unlock (&outstanding_lock); + } } +/* Dedicate thread to receive RPC replies, register them on the queue + of pending wakeups, and deal appropriately. */ void -rpc_transmit (struct rpc_target *target, struct rpc_list *record) +rpc_receive_thread () { - static int xid; + int cc; + void *buf; + struct rpc_list *r; + int xid; - /* Assign a unique transaction ID */ - record->rpc + while (1) + { + buf = malloc (1024 + read_size); + + do + { + cc = read (main_udp_socket, buf, 1024 + read_size); + if (cc == -1) + { + perror ("nfs read"); + r = 0; + } + else + { + xid = *(int *)buf; + spin_lock (&rpc_list_lock); + for (r = outstanding_rpcs; r; r = r->next) + { + if (* (int *) &r[1] == xid) + { + /* Remove it from the list */ + *r->prevp = r->next; + if (r->next) + r->next->prevp = r->prevp; + spin_unlock (&rpc_list_lock); + + mutex_lock (&outstanding_lock); + r->reply = buf; + condition_broadcast (&rpc_wakeup); + mutex_unlock (&outstanding_lock); + break; + } + } + if (!r) + { + spin_unlock (&rpc_list_lock); + fprintf (stderr, "NFS dropping reply xid %d\n", xid); + } + } + } + while (!r); + } +} + + + + |