/* 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 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 #include #include #include #include #undef malloc /* Get rid protection. */ #include #include #include #include #include /* One of these exists for each pending RPC. */ struct rpc_list { struct rpc_list *next, **prevp; void *reply; }; /* A list of all the pending RPCs. */ static struct rpc_list *outstanding_rpcs; /* 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; /* 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 () { 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 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; 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) { *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) { *p++ = htonl (1); *p++ = htonl (second_gid); } else *p++ = 0; *lenaddr = htonl ((p - (lenaddr + 1)) * sizeof (int)); } else { *p++ = htonl (AUTH_NULL); *p++ = 0; } /* 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++; switch (ntohl (*p++)) { default: err = EBADRPC; break; 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_receive_thread () { int cc; void *buf; struct rpc_list *r; int xid; 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); } }