| /* Parts of this file are derived from the original Sun (ONC) RPC |
| * code, under the following copyright: |
| */ |
| /* |
| * Sun RPC is a product of Sun Microsystems, Inc. and is provided for |
| * unrestricted use provided that this legend is included on all tape |
| * media and as a part of the software program in whole or part. Users |
| * may copy or modify Sun RPC without charge, but are not authorized |
| * to license or distribute it to anyone else except as part of a product or |
| * program developed by the user. |
| * |
| * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE |
| * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. |
| * |
| * Sun RPC is provided with no support and without any obligation on the |
| * part of Sun Microsystems, Inc. to assist in its use, correction, |
| * modification or enhancement. |
| * |
| * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE |
| * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC |
| * OR ANY PART THEREOF. |
| * |
| * In no event will Sun Microsystems, Inc. be liable for any lost revenue |
| * or profits or other special, indirect and consequential damages, even if |
| * Sun has been advised of the possibility of such damages. |
| * |
| * Sun Microsystems, Inc. |
| * 2550 Garcia Avenue |
| * Mountain View, California 94043 |
| */ |
| /* |
| * svc.c, Server-side remote procedure call interface. |
| * |
| * There are two sets of procedures here. The xprt routines are |
| * for handling transport handles. The svc routines handle the |
| * list of service routines. |
| * |
| * Copyright (C) 1984, Sun Microsystems, Inc. |
| */ |
| |
| #include <rpc/rpc.h> |
| #include <sys/select.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <rpc/rpc_router_ioctl.h> |
| #include <debug.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| |
| extern XDR *xdr_init_common(const char *name, int is_client); |
| extern void xdr_destroy_common(XDR *xdr); |
| extern int r_control(int handle, const uint32 cmd, void *arg); |
| extern void grabPartialWakeLock(); |
| extern void releaseWakeLock(); |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| |
| typedef struct registered_server_struct { |
| /* MUST BE AT OFFSET ZERO! The client code assumes this when it overwrites |
| the XDR for server entries which represent a callback client. Those |
| server entries do not have their own XDRs. |
| */ |
| XDR *xdr; |
| /* Because the xdr is NULL for callback clients (as opposed to true |
| servers), we keep track of the program number and version number in this |
| structure as well. |
| */ |
| rpcprog_t x_prog; /* program number */ |
| rpcvers_t x_vers; /* program version */ |
| |
| int active; |
| struct registered_server_struct *next; |
| SVCXPRT *xprt; |
| __dispatch_fn_t dispatch; |
| } registered_server; |
| |
| struct SVCXPRT { |
| fd_set fdset; |
| int max_fd; |
| pthread_attr_t thread_attr; |
| pthread_t svc_thread; |
| pthread_mutexattr_t lock_attr; |
| pthread_mutex_t lock; |
| registered_server *servers; |
| volatile int num_servers; |
| }; |
| |
| static pthread_mutex_t xprt_lock = PTHREAD_MUTEX_INITIALIZER; |
| int xprt_refcount; |
| SVCXPRT *the_xprt; /* FIXME: have a list or something */ |
| |
| /* |
| This routine is only of interest if a service implementor does not |
| call svc_run(), but instead implements custom asynchronous event |
| processing. It is called when the select() system call has |
| determined that an RPC request has arrived on some RPC socket(s); |
| rdfds is the resultant read file descriptor bit mask. The routine |
| returns when all sockets associated with the value of rdfds have |
| been serviced. |
| */ |
| |
| void svc_dispatch(registered_server *svc, SVCXPRT *xprt); |
| |
| static void* svc_context(void *__u) |
| { |
| SVCXPRT *xprt = (SVCXPRT *)__u; |
| int n; |
| struct timeval tv; |
| volatile fd_set rfds; |
| while(xprt->num_servers) { |
| rfds = xprt->fdset; |
| tv.tv_sec = 1; tv.tv_usec = 0; |
| n = select(xprt->max_fd + 1, (fd_set *)&rfds, NULL, NULL, &tv); |
| if (n < 0) { |
| E("select() error %s (%d)\n", strerror(errno), errno); |
| continue; |
| } |
| if (n) { |
| grabPartialWakeLock(); |
| for (n = 0; n <= xprt->max_fd; n++) { |
| if (FD_ISSET(n, &rfds)) { |
| /* the file descriptor points to the service instance; we |
| simply look that service by its file descriptor, and |
| call its service function. */ |
| registered_server *trav = xprt->servers; |
| for (; trav; trav = trav->next) |
| if (trav->xdr->fd == n) { |
| /* read the entire RPC */ |
| if (trav->xdr->xops->read(trav->xdr) == 0) { |
| E("%08x:%08x ONCRPC read error: aborting!\n", |
| trav->xdr->x_prog, trav->xdr->x_vers); |
| abort(); |
| } |
| svc_dispatch(trav, xprt); |
| break; |
| } |
| } /* if fd is set */ |
| } /* for each fd */ |
| releaseWakeLock(); |
| } |
| } |
| D("RPC-server thread exiting!\n"); |
| return NULL; |
| } |
| |
| SVCXPRT *svcrtr_create (void) |
| { |
| SVCXPRT *xprt; |
| pthread_mutex_lock(&xprt_lock); |
| if (the_xprt) { |
| D("The RPC transport has already been created.\n"); |
| xprt = the_xprt; |
| } else { |
| xprt = calloc(1, sizeof(SVCXPRT)); |
| if (xprt) { |
| FD_ZERO(&xprt->fdset); |
| xprt->max_fd = 0; |
| pthread_attr_init(&xprt->thread_attr); |
| pthread_attr_setdetachstate(&xprt->thread_attr, |
| PTHREAD_CREATE_DETACHED); |
| pthread_mutexattr_init(&xprt->lock_attr); |
| // pthread_mutexattr_settype(&xprt->lock_attr, |
| // PTHREAD_MUTEX_RECURSIVE); |
| pthread_mutex_init(&xprt->lock, &xprt->lock_attr); |
| } |
| } |
| pthread_mutex_unlock(&xprt_lock); |
| return xprt; |
| } |
| |
| void svc_destroy(SVCXPRT *xprt) |
| { |
| /* the last call to xprt_unregister() does the job */ |
| } |
| |
| /* NOTE: this function must always be called with the xprt->lock held! */ |
| static registered_server* svc_find_nosync(SVCXPRT *xprt, |
| rpcprog_t prog, rpcvers_t vers, |
| registered_server **prev) |
| { |
| registered_server *trav; |
| trav = xprt->servers; |
| if (prev) *prev = NULL; |
| for (; trav; trav = trav->next) { |
| if (trav->x_prog == prog && trav->x_vers == vers) |
| break; |
| if (prev) *prev = trav; |
| } |
| return trav; |
| } |
| |
| registered_server* svc_find(SVCXPRT *xprt, |
| rpcprog_t prog, rpcvers_t vers) |
| { |
| pthread_mutex_lock(&xprt->lock); |
| registered_server *svc = svc_find_nosync(xprt, prog, vers, NULL); |
| pthread_mutex_unlock(&xprt->lock); |
| return svc; |
| } |
| |
| bool_t svc_register (SVCXPRT *xprt, rpcprog_t prog, rpcvers_t vers, |
| __dispatch_fn_t dispatch, |
| rpcprot_t protocol) |
| { |
| struct rpcrouter_ioctl_server_args args; |
| registered_server* svc; |
| |
| pthread_mutex_lock(&xprt->lock); |
| |
| D("registering for service %08x:%d\n", (uint32_t)prog, (int)vers); |
| |
| svc = svc_find_nosync(xprt, prog, vers, NULL); |
| |
| if (svc) { |
| E("service is already registered!\n"); |
| pthread_mutex_unlock(&xprt->lock); |
| return svc->dispatch == dispatch; |
| } |
| |
| svc = malloc(sizeof(registered_server)); |
| |
| /* If the program number of the RPC server ANDs with 0x01000000, then it is |
| not a true RPC server, but a callback client for an existing RPC client. |
| For example, if you have an RPC client with the program number |
| 0x30000000, then its callback client will have a program number |
| 0x31000000. RPC calls on program number 0x31000000 will arrive on the |
| RPC client 0x30000000. |
| */ |
| |
| if (prog & 0x01000000) { |
| D("RPC server %08x:%d is a callback client, " |
| "creating dummy service entry!\n", (uint32_t)prog, (int)vers); |
| svc->xdr = NULL; |
| svc->x_prog = prog; |
| svc->x_vers = vers; |
| } else { |
| V("RPC server %08x:%d is a real server.\n", (uint32_t)prog, (int)vers); |
| svc->xdr = xdr_init_common("/dev/oncrpc/00000000:0", |
| 0 /* not a client XDR */); |
| if (svc->xdr == NULL) { |
| E("failed to initialize service (permissions?)!\n"); |
| free(svc); |
| pthread_mutex_unlock(&xprt->lock); |
| return FALSE; |
| } |
| |
| args.prog = prog; |
| args.vers = vers; |
| V("RPC server %08x:%d: registering with kernel.\n", |
| (uint32_t)prog, (int)vers); |
| if (r_control(svc->xdr->fd, |
| RPC_ROUTER_IOCTL_REGISTER_SERVER, |
| &args) < 0) { |
| E("ioctl(RPC_ROUTER_IOCTL_REGISTER_SERVER) failed: %s!\n", |
| strerror(errno)); |
| xdr_destroy_common(svc->xdr); |
| free(svc); |
| pthread_mutex_unlock(&xprt->lock); |
| return FALSE; |
| } |
| |
| FD_SET(svc->xdr->fd, &xprt->fdset); |
| if (svc->xdr->fd > xprt->max_fd) xprt->max_fd = svc->xdr->fd; |
| svc->x_prog = svc->xdr->x_prog = prog; |
| svc->x_vers = svc->xdr->x_vers = vers; |
| } |
| |
| svc->dispatch = dispatch; |
| svc->next = xprt->servers; |
| xprt->servers = svc; |
| xprt->num_servers++; |
| V("RPC server %08x:%d: after registering, there are %d servers.\n", |
| (uint32_t)prog, (int)vers, xprt->num_servers); |
| svc->xprt = xprt; |
| if (xprt->num_servers == 1) { |
| D("creating RPC-server thread (detached)!\n"); |
| pthread_create(&xprt->svc_thread, |
| &xprt->thread_attr, |
| svc_context, xprt); |
| } |
| pthread_mutex_unlock(&xprt->lock); |
| return TRUE; |
| } |
| |
| void svc_unregister (SVCXPRT *xprt, rpcprog_t prog, rpcvers_t vers) { |
| registered_server *prev, *found; |
| pthread_mutex_lock(&xprt->lock); |
| found = svc_find_nosync(xprt, prog, vers, &prev); |
| D("unregistering RPC server %08x:%d\n", (unsigned)prog, (unsigned)vers); |
| if (found) { |
| struct rpcrouter_ioctl_server_args args; |
| if (prev) { |
| V("RPC server %08x:%d is not the first in the list\n", |
| (unsigned)prog, (unsigned)vers); |
| prev->next = found->next; |
| } else { |
| V("RPC server %08x:%d the first in the list\n", |
| (unsigned)prog, (unsigned)vers); |
| xprt->servers = found->next; |
| } |
| |
| /* Is is an RPC server or a callback client? */ |
| if (found->xdr) { |
| if (!(prog & 0x01000000)) { |
| V("RPC server %08x:%d is not a callback server.\n", |
| (unsigned)prog, (unsigned)vers); |
| /* don't bother decreasing the xprt->max_fd to the previous |
| * minimum. |
| */ |
| args.prog = prog; |
| args.vers = vers; |
| if (r_control(found->xdr->fd, |
| RPC_ROUTER_IOCTL_UNREGISTER_SERVER, |
| &args) < 0) { |
| E("ioctl(RPC_ROUTER_IOCTL_UNREGISTER_SERVER) " |
| "failed: %s!\n", |
| strerror(errno)); |
| } |
| FD_CLR(found->xdr->fd, &xprt->fdset); |
| } |
| V("RPC server %08x:%d: destroying XDR\n", |
| (unsigned)prog, (unsigned)vers); |
| xdr_destroy_common(found->xdr); |
| } |
| else V("RPC server %08x:%d does not have an associated XDR\n", |
| (unsigned)prog, (unsigned)vers); |
| |
| free(found); |
| /* When this goes to zero, the RPC-server thread will exit. We do not |
| * need to wait for the thread to exit, because it is detached. |
| */ |
| xprt->num_servers--; |
| V("RPC server %08x:%d: after unregistering, %d servers left.\n", |
| (unsigned)prog, (unsigned)vers, xprt->num_servers); |
| } |
| pthread_mutex_unlock(&xprt->lock); |
| } |
| |
| /* |
| RPC_OFFSET + |
| 0 00000000 RPC xid network-byte order |
| 1 00000000 RPC call |
| 2 00000002 rpc version |
| 3 3000005d prog num |
| 4 00000000 prog vers |
| 5 00000001 proc num |
| |
| 6 00000000 cred |
| 7 00000000 cred |
| 8 00000000 verf |
| 9 00000000 verf |
| |
| a 0001fcc1 parms... |
| b 00354230 |
| c 00000000 |
| */ |
| |
| void svc_dispatch(registered_server *svc, SVCXPRT *xprt) |
| { |
| struct svc_req req; |
| |
| /* Read enough of the packet to be able to find the program number, the |
| program-version number, and the procedure call. Notice that anything |
| arriving on this channel must be an RPC call. Also, the program and |
| program-version numbers must match what's in the XDR of the service. */ |
| |
| D("reading on fd %d for %08x:%d\n", |
| svc->xdr->fd, svc->x_prog, svc->x_vers); |
| |
| uint32 prog = ntohl(((uint32 *)(svc->xdr->in_msg))[RPC_OFFSET+3]); |
| uint32 vers = ntohl(((uint32 *)(svc->xdr->in_msg))[RPC_OFFSET+4]); |
| uint32 proc = ntohl(((uint32 *)(svc->xdr->in_msg))[RPC_OFFSET+5]); |
| |
| if (ntohl(((uint32 *)svc->xdr->in_msg)[RPC_OFFSET+1]) != RPC_MSG_CALL) { |
| E("ERROR: expecting an RPC call on server channel!\n"); |
| return; |
| } |
| |
| if (prog != svc->x_prog) { |
| E("ERROR: prog num %08x does not match expected %08x!\n", |
| (unsigned)prog, (unsigned)svc->x_prog); |
| return; |
| } |
| |
| if (vers != svc->xdr->x_vers) { |
| E("ERROR: prog vers %08x does not match expected %08x!\n", |
| vers, svc->xdr->x_vers); |
| return; |
| } |
| |
| req.rq_prog = prog; |
| req.rq_vers = vers; |
| req.rq_proc = proc; |
| req.rq_xprt = xprt; |
| |
| D("START: SVC DISPATCH %08x:%08x --> %08x\n", |
| (uint32_t)prog, (int)vers, proc); |
| /* The RPC header (XID, call type, RPC version, program number, program |
| version, proc number) is 6 long words; the default credentials are 4 |
| long words. This the offset (RPC_OFFSET + 10)<<2 is where the first |
| arguments start. |
| */ |
| svc->xdr->in_next = (RPC_OFFSET + 6 + 4)*sizeof(uint32); |
| |
| svc->active = getpid(); |
| svc->xdr->x_op = XDR_DECODE; |
| svc->dispatch(&req, (SVCXPRT *)svc); |
| svc->active = 0; |
| D("DONE: SVC DISPATCH %08x:%08x --> %08x\n", |
| (uint32_t)prog, (int)vers, proc); |
| } |
| |
| void xprt_register(SVCXPRT *xprt) |
| { |
| pthread_mutex_lock(&xprt_lock); |
| if (!the_xprt || (xprt && (xprt == the_xprt))) { |
| xprt_refcount++; |
| the_xprt = xprt; |
| D("registering RPC transport (refcount %d)\n", xprt_refcount); |
| } |
| else E("a different RPC transport has already been registered!\n"); |
| pthread_mutex_unlock(&xprt_lock); |
| } |
| |
| void xprt_unregister (SVCXPRT *xprt) |
| { |
| pthread_mutex_lock(&xprt_lock); |
| if (xprt && xprt == the_xprt) { |
| if (xprt_refcount == 1) { |
| xprt_refcount = 0; |
| D("Destroying RPC transport (servers %d)\n", |
| the_xprt->num_servers); |
| |
| pthread_attr_destroy(&xprt->thread_attr); |
| pthread_mutexattr_destroy(&xprt->lock_attr); |
| pthread_mutex_destroy(&xprt->lock); |
| /* Make sure the thread has existed before we free the xprt |
| structure. The thread is created as detached, so we do not wait |
| for it after we set the terminate flag in svc_unregister, but we |
| do have to wait for it to exit when we call svc_destroy. |
| */ |
| pthread_join(xprt->svc_thread, NULL); |
| free(xprt); |
| the_xprt = NULL; |
| } |
| else xprt_refcount--; |
| D("unregistering RPC transport (refcount %d)\n", xprt_refcount); |
| } |
| else E("no RPC transport has been registered!\n"); |
| pthread_mutex_unlock(&xprt_lock); |
| } |
| |
| /* The functions that follow all take a pointer to the SVCXPRT instead of the |
| XDR of the server that they refer to. The xprt pointer is actually a |
| pointer to a registered_server, which identified the RPC server in |
| question. |
| */ |
| |
| bool_t svc_getargs(SVCXPRT *xprt, xdrproc_t xdr_args, caddr_t args_ptr) |
| { |
| registered_server *serv = (registered_server *)xprt; |
| if (serv->active) { |
| bool_t result = (bool_t) (*xdr_args)(serv->xdr, args_ptr); |
| XDR_MSG_DONE (serv->xdr); |
| return result; |
| } |
| return FALSE; |
| } /* svc_getargs */ |
| |
| bool_t svc_freeargs (SVCXPRT * xprt, xdrproc_t xdr_args, caddr_t args_ptr) |
| { |
| registered_server *serv = (registered_server *)xprt; |
| if (serv->active) { |
| serv->xdr->x_op = XDR_FREE; |
| return (*xdr_args)((XDR *)serv->xdr, args_ptr); |
| } |
| return FALSE; |
| } |
| |
| /* Send a reply to an rpc request */ |
| bool_t |
| svc_sendreply (SVCXPRT *xprt, xdrproc_t xdr_results, |
| caddr_t xdr_location) |
| { |
| registered_server *serv = (registered_server *)xprt; |
| if (serv->active) { |
| opaque_auth verf; |
| verf.oa_flavor = AUTH_NONE; |
| verf.oa_length = 0; |
| |
| serv->xdr->x_op = XDR_ENCODE; |
| |
| if (!xdr_reply_msg_start(serv->xdr, &verf) || |
| !xdr_results(serv->xdr, xdr_location)) |
| return FALSE; |
| |
| ((uint32 *)(serv->xdr->out_msg))[RPC_OFFSET] = |
| ((uint32 *)(serv->xdr->in_msg))[RPC_OFFSET]; //RPC xid |
| D("%08x:%d sending RPC reply (XID %d)\n", |
| serv->xdr->x_prog, |
| serv->xdr->x_vers, |
| ntohl(((uint32 *)(serv->xdr->out_msg))[RPC_OFFSET])); |
| XDR_MSG_SEND(serv->xdr); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| /* Service error functions. */ |
| |
| #define SVCERR_XDR_SEND(xdr, reply) \ |
| ( XDR_MSG_START(xdr, RPC_MSG_REPLY) && \ |
| xdr_send_reply_header(xdr, &reply) && \ |
| XDR_MSG_SEND(xdr) ) |
| |
| void svcerr_decode (SVCXPRT *xprt) |
| { |
| registered_server *serv = (registered_server *)xprt; |
| if (serv->active) { |
| rpc_reply_header reply; |
| reply.stat = RPC_MSG_ACCEPTED; |
| reply.u.ar.verf = serv->xdr->verf; |
| reply.u.ar.stat = RPC_GARBAGE_ARGS; |
| |
| if (!SVCERR_XDR_SEND(serv->xdr, reply)) |
| /* Couldn't send the reply - just give up */ |
| XDR_MSG_ABORT(serv->xdr); |
| } |
| } /* svcerr_decode */ |
| |
| void svcerr_systemerr (SVCXPRT *xprt) |
| { |
| registered_server *serv = (registered_server *)xprt; |
| if (serv->active) { |
| rpc_reply_header reply; |
| reply.stat = RPC_MSG_ACCEPTED; |
| reply.u.ar.verf = serv->xdr->verf; |
| reply.u.ar.stat = RPC_SYSTEM_ERR; |
| |
| if (!SVCERR_XDR_SEND(serv->xdr, reply)) |
| /* Couldn't send the reply - just give up */ |
| XDR_MSG_ABORT(serv->xdr); |
| } |
| } /* svcerr_systemerr */ |
| |
| void svcerr_noproc(SVCXPRT *xprt) |
| { |
| registered_server *serv = (registered_server *)xprt; |
| if (serv->active) { |
| rpc_reply_header reply; |
| reply.stat = RPC_MSG_ACCEPTED; |
| reply.u.ar.verf = serv->xdr->verf; |
| reply.u.ar.stat = RPC_PROC_UNAVAIL; |
| |
| if (!SVCERR_XDR_SEND(serv->xdr, reply)) |
| /* Couldn't send the reply - just give up */ |
| XDR_MSG_ABORT(serv->xdr); |
| } |
| } /* svcerr_noproc */ |