diff options
Diffstat (limited to 'nfs/rpc.c')
-rw-r--r-- | nfs/rpc.c | 425 |
1 files changed, 425 insertions, 0 deletions
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; +} |