/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2017 Joyent Inc */ /* * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley * 4.3 BSD under license from the Regents of the University of * California. */ /* * 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. * */ #include "mt.h" #include "rpc_mt.h" #include #include #include #include #include #include #include #ifdef PORTMAP #include #endif #include #include #include #include #include #include #include extern bool_t __svc_get_door_cred(); extern bool_t __rpc_get_local_cred(); SVCXPRT **svc_xports; static int nsvc_xports; /* total number of svc_xports allocated */ XDR **svc_xdrs; /* common XDR receive area */ int nsvc_xdrs; /* total number of svc_xdrs allocated */ int __rpc_use_pollfd_done; /* to unlimit the number of connections */ #define NULL_SVC ((struct svc_callout *)0) /* * The services list * Each entry represents a set of procedures (an rpc program). * The dispatch routine takes request structs and runs the * appropriate procedure. */ static struct svc_callout { struct svc_callout *sc_next; rpcprog_t sc_prog; rpcvers_t sc_vers; char *sc_netid; void (*sc_dispatch)(); } *svc_head; extern rwlock_t svc_lock; static struct svc_callout *svc_find(); int _svc_prog_dispatch(); void svc_getreq_common(); char *strdup(); extern mutex_t svc_door_mutex; extern cond_t svc_door_waitcv; extern int svc_ndoorfds; extern SVCXPRT_LIST *_svc_xprtlist; extern mutex_t xprtlist_lock; extern void __svc_rm_from_xlist(); #if !defined(_LP64) extern fd_set _new_svc_fdset; #endif /* * If the allocated array of reactor is too small, this value is used as a * margin. This reduces the number of allocations. */ #define USER_FD_INCREMENT 5 static void add_pollfd(int fd, short events); static void remove_pollfd(int fd); static void __svc_remove_input_of_fd(int fd); /* * Data used to handle reactor: * - one file descriptor we listen to, * - one callback we call if the fd pops, * - and a cookie passed as a parameter to the callback. * * The structure is an array indexed on the file descriptor. Each entry is * pointing to the first element of a double-linked list of callback. * only one callback may be associated to a couple (fd, event). */ struct _svc_user_fd_head; typedef struct { struct _svc_user_fd_node *next; struct _svc_user_fd_node *previous; } _svc_user_link; typedef struct _svc_user_fd_node { _svc_user_link lnk; svc_input_id_t id; int fd; unsigned int events; svc_callback_t callback; void* cookie; } _svc_user_fd_node; typedef struct _svc_user_fd_head { struct _svc_user_fd_node *list; unsigned int mask; /* logical OR of all sub-masks */ } _svc_user_fd_head; /* Array of defined reactor - indexed on file descriptor */ static _svc_user_fd_head *svc_userfds = NULL; /* current size of file descriptor */ static int svc_nuserfds = 0; /* Mutex to ensure MT safe operations for user fds callbacks. */ static mutex_t svc_userfds_lock = DEFAULTMUTEX; /* * This structure is used to have constant time alogrithms. There is an array * of this structure as large as svc_nuserfds. When the user is registering a * new callback, the address of the created structure is stored in a cell of * this array. The address of this cell is the returned unique identifier. * * On removing, the id is given by the user, then we know if this cell is * filled or not (with free). If it is free, we return an error. Otherwise, * we can free the structure pointed by fd_node. * * On insertion, we use the linked list created by (first_free, * next_free). In this way with a constant time computation, we can give a * correct index to the user. */ typedef struct _svc_management_user_fd { bool_t free; union { svc_input_id_t next_free; _svc_user_fd_node *fd_node; } data; } _svc_management_user_fd; /* index to the first free elem */ static svc_input_id_t first_free = (svc_input_id_t)-1; /* the size of this array is the same as svc_nuserfds */ static _svc_management_user_fd* user_fd_mgt_array = NULL; /* current size of user_fd_mgt_array */ static int svc_nmgtuserfds = 0; /* Define some macros to access data associated to registration ids. */ #define node_from_id(id) (user_fd_mgt_array[(int)id].data.fd_node) #define is_free_id(id) (user_fd_mgt_array[(int)id].free) #ifndef POLLSTANDARD #define POLLSTANDARD \ (POLLIN|POLLPRI|POLLOUT|POLLRDNORM|POLLRDBAND| \ POLLWRBAND|POLLERR|POLLHUP|POLLNVAL) #endif /* * To free an Id, we set the cell as free and insert its address in the list * of free cell. */ static void _svc_free_id(const svc_input_id_t id) { assert(((int)id >= 0) && ((int)id < svc_nmgtuserfds)); user_fd_mgt_array[(int)id].free = TRUE; user_fd_mgt_array[(int)id].data.next_free = first_free; first_free = id; } /* * To get a free cell, we just have to take it from the free linked list and * set the flag to "not free". This function also allocates new memory if * necessary */ static svc_input_id_t _svc_attribute_new_id(_svc_user_fd_node *node) { int selected_index = (int)first_free; assert(node != NULL); if (selected_index == -1) { /* Allocate new entries */ int L_inOldSize = svc_nmgtuserfds; int i; _svc_management_user_fd *tmp; svc_nmgtuserfds += USER_FD_INCREMENT; tmp = realloc(user_fd_mgt_array, svc_nmgtuserfds * sizeof (_svc_management_user_fd)); if (tmp == NULL) { syslog(LOG_ERR, "_svc_attribute_new_id: out of memory"); svc_nmgtuserfds = L_inOldSize; errno = ENOMEM; return ((svc_input_id_t)-1); } user_fd_mgt_array = tmp; for (i = svc_nmgtuserfds - 1; i >= L_inOldSize; i--) _svc_free_id((svc_input_id_t)i); selected_index = (int)first_free; } node->id = (svc_input_id_t)selected_index; first_free = user_fd_mgt_array[selected_index].data.next_free; user_fd_mgt_array[selected_index].data.fd_node = node; user_fd_mgt_array[selected_index].free = FALSE; return ((svc_input_id_t)selected_index); } /* * Access to a pollfd treatment. Scan all the associated callbacks that have * at least one bit in their mask that masks a received event. * * If event POLLNVAL is received, we check that one callback processes it, if * not, then remove the file descriptor from the poll. If there is one, let * the user do the work. */ void __svc_getreq_user(struct pollfd *pfd) { int fd = pfd->fd; short revents = pfd->revents; bool_t invalHandled = FALSE; _svc_user_fd_node *node; (void) mutex_lock(&svc_userfds_lock); if ((fd < 0) || (fd >= svc_nuserfds)) { (void) mutex_unlock(&svc_userfds_lock); return; } node = svc_userfds[fd].list; /* check if at least one mask fits */ if (0 == (revents & svc_userfds[fd].mask)) { (void) mutex_unlock(&svc_userfds_lock); return; } while ((svc_userfds[fd].mask != 0) && (node != NULL)) { /* * If one of the received events maps the ones the node listens * to */ _svc_user_fd_node *next = node->lnk.next; if (node->callback != NULL) { if (node->events & revents) { if (revents & POLLNVAL) { invalHandled = TRUE; } /* * The lock must be released before calling the * user function, as this function can call * svc_remove_input() for example. */ (void) mutex_unlock(&svc_userfds_lock); node->callback(node->id, node->fd, node->events & revents, node->cookie); /* * Do not use the node structure anymore, as it * could have been deallocated by the previous * callback. */ (void) mutex_lock(&svc_userfds_lock); } } node = next; } if ((revents & POLLNVAL) && !invalHandled) __svc_remove_input_of_fd(fd); (void) mutex_unlock(&svc_userfds_lock); } /* * Check if a file descriptor is associated with a user reactor. * To do this, just check that the array indexed on fd has a non-void linked * list (ie. first element is not NULL) */ bool_t __is_a_userfd(int fd) { /* Checks argument */ if ((fd < 0) || (fd >= svc_nuserfds)) return (FALSE); return ((svc_userfds[fd].mask == 0x0000)? FALSE:TRUE); } /* free everything concerning user fd */ /* used in svc_run.c => no static */ void __destroy_userfd(void) { int one_fd; /* Clean user fd */ if (svc_userfds != NULL) { for (one_fd = 0; one_fd < svc_nuserfds; one_fd++) { _svc_user_fd_node *node; node = svc_userfds[one_fd].list; while (node != NULL) { _svc_user_fd_node *tmp = node; _svc_free_id(node->id); node = node->lnk.next; free(tmp); } } free(user_fd_mgt_array); user_fd_mgt_array = NULL; first_free = (svc_input_id_t)-1; free(svc_userfds); svc_userfds = NULL; svc_nuserfds = 0; } } /* * Remove all the callback associated with a fd => useful when the fd is * closed for instance */ static void __svc_remove_input_of_fd(int fd) { _svc_user_fd_node **pnode; _svc_user_fd_node *tmp; if ((fd < 0) || (fd >= svc_nuserfds)) return; pnode = &svc_userfds[fd].list; while ((tmp = *pnode) != NULL) { *pnode = tmp->lnk.next; _svc_free_id(tmp->id); free(tmp); } svc_userfds[fd].mask = 0; } /* * Allow user to add an fd in the poll list. If it does not succeed, return * -1. Otherwise, return a svc_id */ svc_input_id_t svc_add_input(int user_fd, unsigned int events, svc_callback_t user_callback, void *cookie) { _svc_user_fd_node *new_node; if (user_fd < 0) { errno = EINVAL; return ((svc_input_id_t)-1); } if ((events == 0x0000) || (events & ~(POLLIN|POLLPRI|POLLOUT|POLLRDNORM|POLLRDBAND|\ POLLWRBAND|POLLERR|POLLHUP|POLLNVAL))) { errno = EINVAL; return ((svc_input_id_t)-1); } (void) mutex_lock(&svc_userfds_lock); if ((user_fd < svc_nuserfds) && (svc_userfds[user_fd].mask & events) != 0) { /* Already registrated call-back */ errno = EEXIST; (void) mutex_unlock(&svc_userfds_lock); return ((svc_input_id_t)-1); } /* Handle memory allocation. */ if (user_fd >= svc_nuserfds) { int oldSize = svc_nuserfds; int i; _svc_user_fd_head *tmp; svc_nuserfds = (user_fd + 1) + USER_FD_INCREMENT; tmp = realloc(svc_userfds, svc_nuserfds * sizeof (_svc_user_fd_head)); if (tmp == NULL) { syslog(LOG_ERR, "svc_add_input: out of memory"); svc_nuserfds = oldSize; errno = ENOMEM; (void) mutex_unlock(&svc_userfds_lock); return ((svc_input_id_t)-1); } svc_userfds = tmp; for (i = oldSize; i < svc_nuserfds; i++) { svc_userfds[i].list = NULL; svc_userfds[i].mask = 0; } } new_node = malloc(sizeof (_svc_user_fd_node)); if (new_node == NULL) { syslog(LOG_ERR, "svc_add_input: out of memory"); errno = ENOMEM; (void) mutex_unlock(&svc_userfds_lock); return ((svc_input_id_t)-1); } /* create a new node */ new_node->fd = user_fd; new_node->events = events; new_node->callback = user_callback; new_node->cookie = cookie; if (_svc_attribute_new_id(new_node) == -1) { (void) mutex_unlock(&svc_userfds_lock); free(new_node); return ((svc_input_id_t)-1); } /* Add the new element at the beginning of the list. */ if (svc_userfds[user_fd].list != NULL) svc_userfds[user_fd].list->lnk.previous = new_node; new_node->lnk.next = svc_userfds[user_fd].list; new_node->lnk.previous = NULL; svc_userfds[user_fd].list = new_node; /* refresh global mask for this file desciptor */ svc_userfds[user_fd].mask |= events; /* refresh mask for the poll */ add_pollfd(user_fd, (svc_userfds[user_fd].mask)); (void) mutex_unlock(&svc_userfds_lock); return (new_node->id); } int svc_remove_input(svc_input_id_t id) { _svc_user_fd_node* node; _svc_user_fd_node* next; _svc_user_fd_node* previous; int fd; /* caching optim */ (void) mutex_lock(&svc_userfds_lock); /* Immediately update data for id management */ if (user_fd_mgt_array == NULL || id >= svc_nmgtuserfds || is_free_id(id)) { errno = EINVAL; (void) mutex_unlock(&svc_userfds_lock); return (-1); } node = node_from_id(id); assert(node != NULL); _svc_free_id(id); next = node->lnk.next; previous = node->lnk.previous; fd = node->fd; /* caching optim */ /* Remove this node from the list. */ if (previous != NULL) { previous->lnk.next = next; } else { assert(svc_userfds[fd].list == node); svc_userfds[fd].list = next; } if (next != NULL) next->lnk.previous = previous; /* Remove the node flags from the global mask */ svc_userfds[fd].mask ^= node->events; free(node); if (svc_userfds[fd].mask == 0) { assert(svc_userfds[fd].list == NULL); remove_pollfd(fd); } else { assert(svc_userfds[fd].list != NULL); } /* <=> CLEAN NEEDED TO SHRINK MEMORY USAGE */ (void) mutex_unlock(&svc_userfds_lock); return (0); } /* * Provides default service-side functions for authentication flavors * that do not use all the fields in struct svc_auth_ops. */ /*ARGSUSED*/ static int authany_wrap(AUTH *auth, XDR *xdrs, xdrproc_t xfunc, caddr_t xwhere) { return (*xfunc)(xdrs, xwhere); } struct svc_auth_ops svc_auth_any_ops = { authany_wrap, authany_wrap, }; /* * Return pointer to server authentication structure. */ SVCAUTH * __svc_get_svcauth(SVCXPRT *xprt) { /* LINTED pointer alignment */ return (&SVC_XP_AUTH(xprt)); } /* * A callback routine to cleanup after a procedure is executed. */ void (*__proc_cleanup_cb)() = NULL; void * __svc_set_proc_cleanup_cb(void *cb) { void *tmp = (void *)__proc_cleanup_cb; __proc_cleanup_cb = (void (*)())cb; return (tmp); } /* *************** SVCXPRT related stuff **************** */ static int pollfd_shrinking = 1; /* * Add fd to svc_pollfd */ static void add_pollfd(int fd, short events) { if (fd < FD_SETSIZE) { FD_SET(fd, &svc_fdset); #if !defined(_LP64) FD_SET(fd, &_new_svc_fdset); #endif svc_nfds++; svc_nfds_set++; if (fd >= svc_max_fd) svc_max_fd = fd + 1; } if (fd >= svc_max_pollfd) svc_max_pollfd = fd + 1; if (svc_max_pollfd > svc_pollfd_allocd) { int i = svc_pollfd_allocd; pollfd_t *tmp; do { svc_pollfd_allocd += POLLFD_EXTEND; } while (svc_max_pollfd > svc_pollfd_allocd); tmp = realloc(svc_pollfd, sizeof (pollfd_t) * svc_pollfd_allocd); if (tmp != NULL) { svc_pollfd = tmp; for (; i < svc_pollfd_allocd; i++) POLLFD_CLR(i, tmp); } else { /* * give an error message; undo fdset setting * above; reset the pollfd_shrinking flag. * because of this poll will not be done * on these fds. */ if (fd < FD_SETSIZE) { FD_CLR(fd, &svc_fdset); #if !defined(_LP64) FD_CLR(fd, &_new_svc_fdset); #endif svc_nfds--; svc_nfds_set--; if (fd == (svc_max_fd - 1)) svc_max_fd--; } if (fd == (svc_max_pollfd - 1)) svc_max_pollfd--; pollfd_shrinking = 0; syslog(LOG_ERR, "add_pollfd: out of memory"); _exit(1); } } svc_pollfd[fd].fd = fd; svc_pollfd[fd].events = events; svc_npollfds++; svc_npollfds_set++; } /* * the fd is still active but only the bit in fdset is cleared. * do not subtract svc_nfds or svc_npollfds */ void clear_pollfd(int fd) { if (fd < FD_SETSIZE && FD_ISSET(fd, &svc_fdset)) { FD_CLR(fd, &svc_fdset); #if !defined(_LP64) FD_CLR(fd, &_new_svc_fdset); #endif svc_nfds_set--; } if (fd < svc_pollfd_allocd && POLLFD_ISSET(fd, svc_pollfd)) { POLLFD_CLR(fd, svc_pollfd); svc_npollfds_set--; } } /* * sets the bit in fdset for an active fd so that poll() is done for that */ void set_pollfd(int fd, short events) { if (fd < FD_SETSIZE) { FD_SET(fd, &svc_fdset); #if !defined(_LP64) FD_SET(fd, &_new_svc_fdset); #endif svc_nfds_set++; } if (fd < svc_pollfd_allocd) { svc_pollfd[fd].fd = fd; svc_pollfd[fd].events = events; svc_npollfds_set++; } } /* * remove a svc_pollfd entry; it does not shrink the memory */ static void remove_pollfd(int fd) { clear_pollfd(fd); if (fd == (svc_max_fd - 1)) svc_max_fd--; svc_nfds--; if (fd == (svc_max_pollfd - 1)) svc_max_pollfd--; svc_npollfds--; } /* * delete a svc_pollfd entry; it shrinks the memory * use remove_pollfd if you do not want to shrink */ static void delete_pollfd(int fd) { remove_pollfd(fd); if (pollfd_shrinking && svc_max_pollfd < (svc_pollfd_allocd - POLLFD_SHRINK)) { do { svc_pollfd_allocd -= POLLFD_SHRINK; } while (svc_max_pollfd < (svc_pollfd_allocd - POLLFD_SHRINK)); svc_pollfd = realloc(svc_pollfd, sizeof (pollfd_t) * svc_pollfd_allocd); if (svc_pollfd == NULL) { syslog(LOG_ERR, "delete_pollfd: out of memory"); _exit(1); } } } /* * Activate a transport handle. */ void xprt_register(const SVCXPRT *xprt) { int fd = xprt->xp_fd; #ifdef CALLBACK extern void (*_svc_getreqset_proc)(); #endif /* VARIABLES PROTECTED BY svc_fd_lock: svc_xports, svc_fdset */ (void) rw_wrlock(&svc_fd_lock); if (svc_xports == NULL) { /* allocate some small amount first */ svc_xports = calloc(FD_INCREMENT, sizeof (SVCXPRT *)); if (svc_xports == NULL) { syslog(LOG_ERR, "xprt_register: out of memory"); _exit(1); } nsvc_xports = FD_INCREMENT; #ifdef CALLBACK /* * XXX: This code does not keep track of the server state. * * This provides for callback support. When a client * recv's a call from another client on the server fd's, * it calls _svc_getreqset_proc() which would return * after serving all the server requests. Also look under * clnt_dg.c and clnt_vc.c (clnt_call part of it) */ _svc_getreqset_proc = svc_getreq_poll; #endif } while (fd >= nsvc_xports) { SVCXPRT **tmp_xprts = svc_xports; /* time to expand svc_xprts */ tmp_xprts = realloc(svc_xports, sizeof (SVCXPRT *) * (nsvc_xports + FD_INCREMENT)); if (tmp_xprts == NULL) { syslog(LOG_ERR, "xprt_register : out of memory."); _exit(1); } svc_xports = tmp_xprts; (void) memset(&svc_xports[nsvc_xports], 0, sizeof (SVCXPRT *) * FD_INCREMENT); nsvc_xports += FD_INCREMENT; } svc_xports[fd] = (SVCXPRT *)xprt; add_pollfd(fd, MASKVAL); if (svc_polling) { char dummy; /* * This happens only in one of the MT modes. * Wake up poller. */ (void) write(svc_pipe[1], &dummy, sizeof (dummy)); } /* * If already dispatching door based services, start * dispatching TLI based services now. */ (void) mutex_lock(&svc_door_mutex); if (svc_ndoorfds > 0) (void) cond_signal(&svc_door_waitcv); (void) mutex_unlock(&svc_door_mutex); if (svc_xdrs == NULL) { /* allocate initial chunk */ svc_xdrs = calloc(FD_INCREMENT, sizeof (XDR *)); if (svc_xdrs != NULL) nsvc_xdrs = FD_INCREMENT; else { syslog(LOG_ERR, "xprt_register : out of memory."); _exit(1); } } (void) rw_unlock(&svc_fd_lock); } /* * De-activate a transport handle. */ void __xprt_unregister_private(const SVCXPRT *xprt, bool_t lock_not_held) { int fd = xprt->xp_fd; if (lock_not_held) (void) rw_wrlock(&svc_fd_lock); if ((fd < nsvc_xports) && (svc_xports[fd] == xprt)) { svc_xports[fd] = NULL; delete_pollfd(fd); } if (lock_not_held) (void) rw_unlock(&svc_fd_lock); __svc_rm_from_xlist(&_svc_xprtlist, xprt, &xprtlist_lock); } void xprt_unregister(const SVCXPRT *xprt) { __xprt_unregister_private(xprt, TRUE); } /* ********************** CALLOUT list related stuff ************* */ /* * Add a service program to the callout list. * The dispatch routine will be called when a rpc request for this * program number comes in. */ bool_t svc_reg(const SVCXPRT *xprt, const rpcprog_t prog, const rpcvers_t vers, void (*dispatch)(), const struct netconfig *nconf) { struct svc_callout *prev; struct svc_callout *s, **s2; struct netconfig *tnconf; char *netid = NULL; int flag = 0; /* VARIABLES PROTECTED BY svc_lock: s, prev, svc_head */ if (xprt->xp_netid) { netid = strdup(xprt->xp_netid); flag = 1; } else if (nconf && nconf->nc_netid) { netid = strdup(nconf->nc_netid); flag = 1; } else if ((tnconf = __rpcfd_to_nconf(xprt->xp_fd, xprt->xp_type)) != NULL) { netid = strdup(tnconf->nc_netid); flag = 1; freenetconfigent(tnconf); } /* must have been created with svc_raw_create */ if ((netid == NULL) && (flag == 1)) return (FALSE); (void) rw_wrlock(&svc_lock); if ((s = svc_find(prog, vers, &prev, netid)) != NULL_SVC) { if (netid) free(netid); if (s->sc_dispatch == dispatch) goto rpcb_it; /* it is registering another xptr */ (void) rw_unlock(&svc_lock); return (FALSE); } s = malloc(sizeof (struct svc_callout)); if (s == NULL) { if (netid) free(netid); (void) rw_unlock(&svc_lock); return (FALSE); } s->sc_prog = prog; s->sc_vers = vers; s->sc_dispatch = dispatch; s->sc_netid = netid; s->sc_next = NULL; /* * The ordering of transports is such that the most frequently used * one appears first. So add the new entry to the end of the list. */ for (s2 = &svc_head; *s2 != NULL; s2 = &(*s2)->sc_next) ; *s2 = s; if ((xprt->xp_netid == NULL) && (flag == 1) && netid) if ((((SVCXPRT *)xprt)->xp_netid = strdup(netid)) == NULL) { syslog(LOG_ERR, "svc_reg : strdup failed."); free(netid); free(s); *s2 = NULL; (void) rw_unlock(&svc_lock); return (FALSE); } rpcb_it: (void) rw_unlock(&svc_lock); /* now register the information with the local binder service */ if (nconf) return (rpcb_set(prog, vers, nconf, &xprt->xp_ltaddr)); return (TRUE); /*NOTREACHED*/ } /* * Remove a service program from the callout list. */ void svc_unreg(const rpcprog_t prog, const rpcvers_t vers) { struct svc_callout *prev; struct svc_callout *s; /* unregister the information anyway */ (void) rpcb_unset(prog, vers, NULL); (void) rw_wrlock(&svc_lock); while ((s = svc_find(prog, vers, &prev, NULL)) != NULL_SVC) { if (prev == NULL_SVC) { svc_head = s->sc_next; } else { prev->sc_next = s->sc_next; } s->sc_next = NULL_SVC; if (s->sc_netid) free(s->sc_netid); free(s); } (void) rw_unlock(&svc_lock); } #ifdef PORTMAP /* * Add a service program to the callout list. * The dispatch routine will be called when a rpc request for this * program number comes in. * For version 2 portmappers. */ bool_t svc_register(SVCXPRT *xprt, rpcprog_t prog, rpcvers_t vers, void (*dispatch)(), int protocol) { struct svc_callout *prev; struct svc_callout *s; struct netconfig *nconf; char *netid = NULL; int flag = 0; if (xprt->xp_netid) { netid = strdup(xprt->xp_netid); flag = 1; } else if ((ioctl(xprt->xp_fd, I_FIND, "timod") > 0) && ((nconf = __rpcfd_to_nconf(xprt->xp_fd, xprt->xp_type)) != NULL)) { /* fill in missing netid field in SVCXPRT */ netid = strdup(nconf->nc_netid); flag = 1; freenetconfigent(nconf); } /* must be svc_raw_create */ if ((netid == NULL) && (flag == 1)) return (FALSE); (void) rw_wrlock(&svc_lock); if ((s = svc_find(prog, vers, &prev, netid)) != NULL_SVC) { if (netid) free(netid); if (s->sc_dispatch == dispatch) goto pmap_it; /* it is registering another xptr */ (void) rw_unlock(&svc_lock); return (FALSE); } s = malloc(sizeof (struct svc_callout)); if (s == (struct svc_callout *)0) { if (netid) free(netid); (void) rw_unlock(&svc_lock); return (FALSE); } s->sc_prog = prog; s->sc_vers = vers; s->sc_dispatch = dispatch; s->sc_netid = netid; s->sc_next = svc_head; svc_head = s; if ((xprt->xp_netid == NULL) && (flag == 1) && netid) if ((xprt->xp_netid = strdup(netid)) == NULL) { syslog(LOG_ERR, "svc_register : strdup failed."); free(netid); svc_head = s->sc_next; free(s); (void) rw_unlock(&svc_lock); return (FALSE); } pmap_it: (void) rw_unlock(&svc_lock); /* now register the information with the local binder service */ if (protocol) return (pmap_set(prog, vers, protocol, xprt->xp_port)); return (TRUE); } /* * Remove a service program from the callout list. * For version 2 portmappers. */ void svc_unregister(rpcprog_t prog, rpcvers_t vers) { struct svc_callout *prev; struct svc_callout *s; (void) rw_wrlock(&svc_lock); while ((s = svc_find(prog, vers, &prev, NULL)) != NULL_SVC) { if (prev == NULL_SVC) { svc_head = s->sc_next; } else { prev->sc_next = s->sc_next; } s->sc_next = NULL_SVC; if (s->sc_netid) free(s->sc_netid); free(s); /* unregister the information with the local binder service */ (void) pmap_unset(prog, vers); } (void) rw_unlock(&svc_lock); } #endif /* PORTMAP */ /* * Search the callout list for a program number, return the callout * struct. * Also check for transport as well. Many routines such as svc_unreg * dont give any corresponding transport, so dont check for transport if * netid == NULL */ static struct svc_callout * svc_find(rpcprog_t prog, rpcvers_t vers, struct svc_callout **prev, char *netid) { struct svc_callout *s, *p; /* WRITE LOCK HELD ON ENTRY: svc_lock */ /* assert(RW_WRITE_HELD(&svc_lock)); */ p = NULL_SVC; for (s = svc_head; s != NULL_SVC; s = s->sc_next) { if (((s->sc_prog == prog) && (s->sc_vers == vers)) && ((netid == NULL) || (s->sc_netid == NULL) || (strcmp(netid, s->sc_netid) == 0))) break; p = s; } *prev = p; return (s); } /* ******************* REPLY GENERATION ROUTINES ************ */ /* * Send a reply to an rpc request */ bool_t svc_sendreply(const SVCXPRT *xprt, const xdrproc_t xdr_results, const caddr_t xdr_location) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_ACCEPTED; rply.acpted_rply.ar_verf = xprt->xp_verf; rply.acpted_rply.ar_stat = SUCCESS; rply.acpted_rply.ar_results.where = xdr_location; rply.acpted_rply.ar_results.proc = xdr_results; return (SVC_REPLY((SVCXPRT *)xprt, &rply)); } /* * No procedure error reply */ void svcerr_noproc(const SVCXPRT *xprt) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_ACCEPTED; rply.acpted_rply.ar_verf = xprt->xp_verf; rply.acpted_rply.ar_stat = PROC_UNAVAIL; SVC_REPLY((SVCXPRT *)xprt, &rply); } /* * Can't decode args error reply */ void svcerr_decode(const SVCXPRT *xprt) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_ACCEPTED; rply.acpted_rply.ar_verf = xprt->xp_verf; rply.acpted_rply.ar_stat = GARBAGE_ARGS; SVC_REPLY((SVCXPRT *)xprt, &rply); } /* * Some system error */ void svcerr_systemerr(const SVCXPRT *xprt) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_ACCEPTED; rply.acpted_rply.ar_verf = xprt->xp_verf; rply.acpted_rply.ar_stat = SYSTEM_ERR; SVC_REPLY((SVCXPRT *)xprt, &rply); } /* * Tell RPC package to not complain about version errors to the client. This * is useful when revving broadcast protocols that sit on a fixed address. * There is really one (or should be only one) example of this kind of * protocol: the portmapper (or rpc binder). */ void __svc_versquiet_on(const SVCXPRT *xprt) { /* LINTED pointer alignment */ svc_flags(xprt) |= SVC_VERSQUIET; } void __svc_versquiet_off(const SVCXPRT *xprt) { /* LINTED pointer alignment */ svc_flags(xprt) &= ~SVC_VERSQUIET; } void svc_versquiet(const SVCXPRT *xprt) { __svc_versquiet_on(xprt); } int __svc_versquiet_get(const SVCXPRT *xprt) { /* LINTED pointer alignment */ return (svc_flags(xprt) & SVC_VERSQUIET); } /* * Authentication error reply */ void svcerr_auth(const SVCXPRT *xprt, const enum auth_stat why) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_DENIED; rply.rjcted_rply.rj_stat = AUTH_ERROR; rply.rjcted_rply.rj_why = why; SVC_REPLY((SVCXPRT *)xprt, &rply); } /* * Auth too weak error reply */ void svcerr_weakauth(const SVCXPRT *xprt) { svcerr_auth(xprt, AUTH_TOOWEAK); } /* * Program unavailable error reply */ void svcerr_noprog(const SVCXPRT *xprt) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_ACCEPTED; rply.acpted_rply.ar_verf = xprt->xp_verf; rply.acpted_rply.ar_stat = PROG_UNAVAIL; SVC_REPLY((SVCXPRT *)xprt, &rply); } /* * Program version mismatch error reply */ void svcerr_progvers(const SVCXPRT *xprt, const rpcvers_t low_vers, const rpcvers_t high_vers) { struct rpc_msg rply; rply.rm_direction = REPLY; rply.rm_reply.rp_stat = MSG_ACCEPTED; rply.acpted_rply.ar_verf = xprt->xp_verf; rply.acpted_rply.ar_stat = PROG_MISMATCH; rply.acpted_rply.ar_vers.low = low_vers; rply.acpted_rply.ar_vers.high = high_vers; SVC_REPLY((SVCXPRT *)xprt, &rply); } /* ******************* SERVER INPUT STUFF ******************* */ /* * Get server side input from some transport. * * Statement of authentication parameters management: * This function owns and manages all authentication parameters, specifically * the "raw" parameters (msg.rm_call.cb_cred and msg.rm_call.cb_verf) and * the "cooked" credentials (rqst->rq_clntcred). * However, this function does not know the structure of the cooked * credentials, so it make the following assumptions: * a) the structure is contiguous (no pointers), and * b) the cred structure size does not exceed RQCRED_SIZE bytes. * In all events, all three parameters are freed upon exit from this routine. * The storage is trivially management on the call stack in user land, but * is mallocated in kernel land. */ void svc_getreq(int rdfds) { fd_set readfds; FD_ZERO(&readfds); readfds.fds_bits[0] = rdfds; svc_getreqset(&readfds); } void svc_getreqset(fd_set *readfds) { int i; for (i = 0; i < svc_max_fd; i++) { /* fd has input waiting */ if (FD_ISSET(i, readfds)) svc_getreq_common(i); } } void svc_getreq_poll(struct pollfd *pfdp, const int pollretval) { int i; int fds_found; for (i = fds_found = 0; fds_found < pollretval; i++) { struct pollfd *p = &pfdp[i]; if (p->revents) { /* fd has input waiting */ fds_found++; /* * We assume that this function is only called * via someone select()ing from svc_fdset or * poll()ing from svc_pollset[]. Thus it's safe * to handle the POLLNVAL event by simply turning * the corresponding bit off in svc_fdset. The * svc_pollset[] array is derived from svc_fdset * and so will also be updated eventually. * * XXX Should we do an xprt_unregister() instead? */ /* Handle user callback */ if (__is_a_userfd(p->fd) == TRUE) { (void) rw_rdlock(&svc_fd_lock); __svc_getreq_user(p); (void) rw_unlock(&svc_fd_lock); } else { if (p->revents & POLLNVAL) { (void) rw_wrlock(&svc_fd_lock); remove_pollfd(p->fd); /* XXX */ (void) rw_unlock(&svc_fd_lock); } else { svc_getreq_common(p->fd); } } } } } void svc_getreq_common(const int fd) { SVCXPRT *xprt; enum xprt_stat stat; struct rpc_msg *msg; struct svc_req *r; char *cred_area; (void) rw_rdlock(&svc_fd_lock); /* HANDLE USER CALLBACK */ if (__is_a_userfd(fd) == TRUE) { struct pollfd virtual_fd; virtual_fd.events = virtual_fd.revents = (short)0xFFFF; virtual_fd.fd = fd; __svc_getreq_user(&virtual_fd); (void) rw_unlock(&svc_fd_lock); return; } /* * The transport associated with this fd could have been * removed from svc_timeout_nonblock_xprt_and_LRU, for instance. * This can happen if two or more fds get read events and are * passed to svc_getreq_poll/set, the first fd is seviced by * the dispatch routine and cleans up any dead transports. If * one of the dead transports removed is the other fd that * had a read event then svc_getreq_common() will be called with no * xprt associated with the fd that had the original read event. */ if ((fd >= nsvc_xports) || (xprt = svc_xports[fd]) == NULL) { (void) rw_unlock(&svc_fd_lock); return; } (void) rw_unlock(&svc_fd_lock); /* LINTED pointer alignment */ msg = SVCEXT(xprt)->msg; /* LINTED pointer alignment */ r = SVCEXT(xprt)->req; /* LINTED pointer alignment */ cred_area = SVCEXT(xprt)->cred_area; msg->rm_call.cb_cred.oa_base = cred_area; msg->rm_call.cb_verf.oa_base = &(cred_area[MAX_AUTH_BYTES]); r->rq_clntcred = &(cred_area[2 * MAX_AUTH_BYTES]); /* receive msgs from xprtprt (support batch calls) */ do { bool_t dispatch; if (dispatch = SVC_RECV(xprt, msg)) (void) _svc_prog_dispatch(xprt, msg, r); /* * Check if the xprt has been disconnected in a recursive call * in the service dispatch routine. If so, then break */ (void) rw_rdlock(&svc_fd_lock); if (xprt != svc_xports[fd]) { (void) rw_unlock(&svc_fd_lock); break; } (void) rw_unlock(&svc_fd_lock); /* * Call cleanup procedure if set. */ if (__proc_cleanup_cb != NULL && dispatch) (*__proc_cleanup_cb)(xprt); if ((stat = SVC_STAT(xprt)) == XPRT_DIED) { SVC_DESTROY(xprt); break; } } while (stat == XPRT_MOREREQS); } int _svc_prog_dispatch(SVCXPRT *xprt, struct rpc_msg *msg, struct svc_req *r) { struct svc_callout *s; enum auth_stat why; int prog_found; rpcvers_t low_vers; rpcvers_t high_vers; void (*disp_fn)(); r->rq_xprt = xprt; r->rq_prog = msg->rm_call.cb_prog; r->rq_vers = msg->rm_call.cb_vers; r->rq_proc = msg->rm_call.cb_proc; r->rq_cred = msg->rm_call.cb_cred; /* LINTED pointer alignment */ SVC_XP_AUTH(r->rq_xprt).svc_ah_ops = svc_auth_any_ops; /* LINTED pointer alignment */ SVC_XP_AUTH(r->rq_xprt).svc_ah_private = NULL; /* first authenticate the message */ /* Check for null flavor and bypass these calls if possible */ if (msg->rm_call.cb_cred.oa_flavor == AUTH_NULL) { r->rq_xprt->xp_verf.oa_flavor = _null_auth.oa_flavor; r->rq_xprt->xp_verf.oa_length = 0; } else { bool_t no_dispatch; if ((why = __gss_authenticate(r, msg, &no_dispatch)) != AUTH_OK) { svcerr_auth(xprt, why); return (0); } if (no_dispatch) return (0); } /* match message with a registered service */ prog_found = FALSE; low_vers = (rpcvers_t)(0 - 1); high_vers = 0; (void) rw_rdlock(&svc_lock); for (s = svc_head; s != NULL_SVC; s = s->sc_next) { if (s->sc_prog == r->rq_prog) { prog_found = TRUE; if (s->sc_vers == r->rq_vers) { if ((xprt->xp_netid == NULL) || (s->sc_netid == NULL) || (strcmp(xprt->xp_netid, s->sc_netid) == 0)) { disp_fn = (*s->sc_dispatch); (void) rw_unlock(&svc_lock); disp_fn(r, xprt); return (1); } prog_found = FALSE; } if (s->sc_vers < low_vers) low_vers = s->sc_vers; if (s->sc_vers > high_vers) high_vers = s->sc_vers; } /* found correct program */ } (void) rw_unlock(&svc_lock); /* * if we got here, the program or version * is not served ... */ if (prog_found) { /* LINTED pointer alignment */ if (!version_keepquiet(xprt)) svcerr_progvers(xprt, low_vers, high_vers); } else { svcerr_noprog(xprt); } return (0); } /* ******************* SVCXPRT allocation and deallocation ***************** */ /* * svc_xprt_alloc() - allocate a service transport handle */ SVCXPRT * svc_xprt_alloc(void) { SVCXPRT *xprt = NULL; SVCXPRT_EXT *xt = NULL; SVCXPRT_LIST *xlist = NULL; struct rpc_msg *msg = NULL; struct svc_req *req = NULL; char *cred_area = NULL; if ((xprt = calloc(1, sizeof (SVCXPRT))) == NULL) goto err_exit; if ((xt = calloc(1, sizeof (SVCXPRT_EXT))) == NULL) goto err_exit; xprt->xp_p3 = (caddr_t)xt; /* SVCEXT(xprt) = xt */ if ((xlist = calloc(1, sizeof (SVCXPRT_LIST))) == NULL) goto err_exit; xt->my_xlist = xlist; xlist->xprt = xprt; if ((msg = malloc(sizeof (struct rpc_msg))) == NULL) goto err_exit; xt->msg = msg; if ((req = malloc(sizeof (struct svc_req))) == NULL) goto err_exit; xt->req = req; if ((cred_area = malloc(2*MAX_AUTH_BYTES + RQCRED_SIZE)) == NULL) goto err_exit; xt->cred_area = cred_area; /* LINTED pointer alignment */ (void) mutex_init(&svc_send_mutex(xprt), USYNC_THREAD, (void *)0); return (xprt); err_exit: svc_xprt_free(xprt); return (NULL); } /* * svc_xprt_free() - free a service handle */ void svc_xprt_free(SVCXPRT *xprt) { /* LINTED pointer alignment */ SVCXPRT_EXT *xt = xprt ? SVCEXT(xprt) : NULL; SVCXPRT_LIST *my_xlist = xt ? xt->my_xlist: NULL; struct rpc_msg *msg = xt ? xt->msg : NULL; struct svc_req *req = xt ? xt->req : NULL; char *cred_area = xt ? xt->cred_area : NULL; if (xprt) free(xprt); if (xt) free(xt); if (my_xlist) free(my_xlist); if (msg) free(msg); if (req) free(req); if (cred_area) free(cred_area); } /* * svc_xprt_destroy() - free parent and child xprt list */ void svc_xprt_destroy(SVCXPRT *xprt) { SVCXPRT_LIST *xlist, *xnext = NULL; int type; /* LINTED pointer alignment */ if (SVCEXT(xprt)->parent) /* LINTED pointer alignment */ xprt = SVCEXT(xprt)->parent; /* LINTED pointer alignment */ type = svc_type(xprt); /* LINTED pointer alignment */ for (xlist = SVCEXT(xprt)->my_xlist; xlist != NULL; xlist = xnext) { xnext = xlist->next; xprt = xlist->xprt; switch (type) { case SVC_DGRAM: svc_dg_xprtfree(xprt); break; case SVC_RENDEZVOUS: svc_vc_xprtfree(xprt); break; case SVC_CONNECTION: svc_fd_xprtfree(xprt); break; case SVC_DOOR: svc_door_xprtfree(xprt); break; } } } /* * svc_copy() - make a copy of parent */ SVCXPRT * svc_copy(SVCXPRT *xprt) { /* LINTED pointer alignment */ switch (svc_type(xprt)) { case SVC_DGRAM: return (svc_dg_xprtcopy(xprt)); case SVC_RENDEZVOUS: return (svc_vc_xprtcopy(xprt)); case SVC_CONNECTION: return (svc_fd_xprtcopy(xprt)); } return (NULL); } /* * _svc_destroy_private() - private SVC_DESTROY interface */ void _svc_destroy_private(SVCXPRT *xprt) { /* LINTED pointer alignment */ switch (svc_type(xprt)) { case SVC_DGRAM: _svc_dg_destroy_private(xprt); break; case SVC_RENDEZVOUS: case SVC_CONNECTION: _svc_vc_destroy_private(xprt, TRUE); break; } } /* * svc_get_local_cred() - fetch local user credentials. This always * works over doors based transports. For local transports, this * does not yield correct results unless the __rpc_negotiate_uid() * call has been invoked to enable this feature. */ bool_t svc_get_local_cred(SVCXPRT *xprt, svc_local_cred_t *lcred) { /* LINTED pointer alignment */ if (svc_type(xprt) == SVC_DOOR) return (__svc_get_door_cred(xprt, lcred)); return (__rpc_get_local_cred(xprt, lcred)); } /* ******************* DUPLICATE ENTRY HANDLING ROUTINES ************** */ /* * the dup cacheing routines below provide a cache of received * transactions. rpc service routines can use this to detect * retransmissions and re-send a non-failure response. Uses a * lru scheme to find entries to get rid of entries in the cache, * though only DUP_DONE entries are placed on the lru list. * the routines were written towards development of a generic * SVC_DUP() interface, which can be expanded to encompass the * svc_dg_enablecache() routines as well. the cache is currently * private to the automounter. */ /* dupcache header contains xprt specific information */ struct dupcache { rwlock_t dc_lock; time_t dc_time; int dc_buckets; int dc_maxsz; int dc_basis; struct dupreq *dc_mru; struct dupreq **dc_hashtbl; }; /* * private duplicate cache request routines */ static int __svc_dupcache_check(struct svc_req *, caddr_t *, uint_t *, struct dupcache *, uint32_t, uint32_t); static struct dupreq *__svc_dupcache_victim(struct dupcache *, time_t); static int __svc_dupcache_enter(struct svc_req *, struct dupreq *, struct dupcache *, uint32_t, uint32_t, time_t); static int __svc_dupcache_update(struct svc_req *, caddr_t, uint_t, int, struct dupcache *, uint32_t, uint32_t); #ifdef DUP_DEBUG static void __svc_dupcache_debug(struct dupcache *); #endif /* DUP_DEBUG */ /* default parameters for the dupcache */ #define DUPCACHE_BUCKETS 257 #define DUPCACHE_TIME 900 #define DUPCACHE_MAXSZ INT_MAX /* * __svc_dupcache_init(void *condition, int basis, char *xprt_cache) * initialize the duprequest cache and assign it to the xprt_cache * Use default values depending on the cache condition and basis. * return TRUE on success and FALSE on failure */ bool_t __svc_dupcache_init(void *condition, int basis, char **xprt_cache) { static mutex_t initdc_lock = DEFAULTMUTEX; int i; struct dupcache *dc; (void) mutex_lock(&initdc_lock); if (*xprt_cache != NULL) { /* do only once per xprt */ (void) mutex_unlock(&initdc_lock); syslog(LOG_ERR, "__svc_dupcache_init: multiply defined dup cache"); return (FALSE); } switch (basis) { case DUPCACHE_FIXEDTIME: dc = malloc(sizeof (struct dupcache)); if (dc == NULL) { (void) mutex_unlock(&initdc_lock); syslog(LOG_ERR, "__svc_dupcache_init: memory alloc failed"); return (FALSE); } (void) rwlock_init(&(dc->dc_lock), USYNC_THREAD, NULL); if (condition != NULL) dc->dc_time = *((time_t *)condition); else dc->dc_time = DUPCACHE_TIME; dc->dc_buckets = DUPCACHE_BUCKETS; dc->dc_maxsz = DUPCACHE_MAXSZ; dc->dc_basis = basis; dc->dc_mru = NULL; dc->dc_hashtbl = malloc(dc->dc_buckets * sizeof (struct dupreq *)); if (dc->dc_hashtbl == NULL) { free(dc); (void) mutex_unlock(&initdc_lock); syslog(LOG_ERR, "__svc_dupcache_init: memory alloc failed"); return (FALSE); } for (i = 0; i < DUPCACHE_BUCKETS; i++) dc->dc_hashtbl[i] = NULL; *xprt_cache = (char *)dc; break; default: (void) mutex_unlock(&initdc_lock); syslog(LOG_ERR, "__svc_dupcache_init: undefined dup cache basis"); return (FALSE); } (void) mutex_unlock(&initdc_lock); return (TRUE); } /* * __svc_dup(struct svc_req *req, caddr_t *resp_buf, uint_t *resp_bufsz, * char *xprt_cache) * searches the request cache. Creates an entry and returns DUP_NEW if * the request is not found in the cache. If it is found, then it * returns the state of the request (in progress, drop, or done) and * also allocates, and passes back results to the user (if any) in * resp_buf, and its length in resp_bufsz. DUP_ERROR is returned on error. */ int __svc_dup(struct svc_req *req, caddr_t *resp_buf, uint_t *resp_bufsz, char *xprt_cache) { uint32_t drxid, drhash; int rc; struct dupreq *dr = NULL; time_t timenow = time(NULL); /* LINTED pointer alignment */ struct dupcache *dc = (struct dupcache *)xprt_cache; if (dc == NULL) { syslog(LOG_ERR, "__svc_dup: undefined cache"); return (DUP_ERROR); } /* get the xid of the request */ if (SVC_CONTROL(req->rq_xprt, SVCGET_XID, (void*)&drxid) == FALSE) { syslog(LOG_ERR, "__svc_dup: xid error"); return (DUP_ERROR); } drhash = drxid % dc->dc_buckets; if ((rc = __svc_dupcache_check(req, resp_buf, resp_bufsz, dc, drxid, drhash)) != DUP_NEW) return (rc); if ((dr = __svc_dupcache_victim(dc, timenow)) == NULL) return (DUP_ERROR); if ((rc = __svc_dupcache_enter(req, dr, dc, drxid, drhash, timenow)) == DUP_ERROR) return (rc); return (DUP_NEW); } /* * __svc_dupcache_check(struct svc_req *req, caddr_t *resp_buf, * uint_t *resp_bufsz,truct dupcache *dc, uint32_t drxid, * uint32_t drhash) * Checks to see whether an entry already exists in the cache. If it does * copy back into the resp_buf, if appropriate. Return the status of * the request, or DUP_NEW if the entry is not in the cache */ static int __svc_dupcache_check(struct svc_req *req, caddr_t *resp_buf, uint_t *resp_bufsz, struct dupcache *dc, uint32_t drxid, uint32_t drhash) { struct dupreq *dr = NULL; (void) rw_rdlock(&(dc->dc_lock)); dr = dc->dc_hashtbl[drhash]; while (dr != NULL) { if (dr->dr_xid == drxid && dr->dr_proc == req->rq_proc && dr->dr_prog == req->rq_prog && dr->dr_vers == req->rq_vers && dr->dr_addr.len == req->rq_xprt->xp_rtaddr.len && memcmp(dr->dr_addr.buf, req->rq_xprt->xp_rtaddr.buf, dr->dr_addr.len) == 0) { /* entry found */ if (dr->dr_hash != drhash) { /* sanity check */ (void) rw_unlock((&dc->dc_lock)); syslog(LOG_ERR, "\n__svc_dupdone: hashing error"); return (DUP_ERROR); } /* * return results for requests on lru list, if * appropriate requests must be DUP_DROP or DUP_DONE * to have a result. A NULL buffer in the cache * implies no results were sent during dupdone. * A NULL buffer in the call implies not interested * in results. */ if (((dr->dr_status == DUP_DONE) || (dr->dr_status == DUP_DROP)) && resp_buf != NULL && dr->dr_resp.buf != NULL) { *resp_buf = malloc(dr->dr_resp.len); if (*resp_buf == NULL) { syslog(LOG_ERR, "__svc_dupcache_check: malloc failed"); (void) rw_unlock(&(dc->dc_lock)); return (DUP_ERROR); } (void) memset(*resp_buf, 0, dr->dr_resp.len); (void) memcpy(*resp_buf, dr->dr_resp.buf, dr->dr_resp.len); *resp_bufsz = dr->dr_resp.len; } else { /* no result */ if (resp_buf) *resp_buf = NULL; if (resp_bufsz) *resp_bufsz = 0; } (void) rw_unlock(&(dc->dc_lock)); return (dr->dr_status); } dr = dr->dr_chain; } (void) rw_unlock(&(dc->dc_lock)); return (DUP_NEW); } /* * __svc_dupcache_victim(struct dupcache *dc, time_t timenow) * Return a victim dupreq entry to the caller, depending on cache policy. */ static struct dupreq * __svc_dupcache_victim(struct dupcache *dc, time_t timenow) { struct dupreq *dr = NULL; switch (dc->dc_basis) { case DUPCACHE_FIXEDTIME: /* * The hash policy is to free up a bit of the hash * table before allocating a new entry as the victim. * Freeing up the hash table each time should split * the cost of keeping the hash table clean among threads. * Note that only DONE or DROPPED entries are on the lru * list but we do a sanity check anyway. */ (void) rw_wrlock(&(dc->dc_lock)); while ((dc->dc_mru) && (dr = dc->dc_mru->dr_next) && ((timenow - dr->dr_time) > dc->dc_time)) { /* clean and then free the entry */ if (dr->dr_status != DUP_DONE && dr->dr_status != DUP_DROP) { /* * The LRU list can't contain an * entry where the status is other than * DUP_DONE or DUP_DROP. */ syslog(LOG_ERR, "__svc_dupcache_victim: bad victim"); #ifdef DUP_DEBUG /* * Need to hold the reader/writers lock to * print the cache info, since we already * hold the writers lock, we shall continue * calling __svc_dupcache_debug() */ __svc_dupcache_debug(dc); #endif /* DUP_DEBUG */ (void) rw_unlock(&(dc->dc_lock)); return (NULL); } /* free buffers */ if (dr->dr_resp.buf) { free(dr->dr_resp.buf); dr->dr_resp.buf = NULL; } if (dr->dr_addr.buf) { free(dr->dr_addr.buf); dr->dr_addr.buf = NULL; } /* unhash the entry */ if (dr->dr_chain) dr->dr_chain->dr_prevchain = dr->dr_prevchain; if (dr->dr_prevchain) dr->dr_prevchain->dr_chain = dr->dr_chain; if (dc->dc_hashtbl[dr->dr_hash] == dr) dc->dc_hashtbl[dr->dr_hash] = dr->dr_chain; /* modify the lru pointers */ if (dc->dc_mru == dr) { dc->dc_mru = NULL; } else { dc->dc_mru->dr_next = dr->dr_next; dr->dr_next->dr_prev = dc->dc_mru; } free(dr); dr = NULL; } (void) rw_unlock(&(dc->dc_lock)); /* * Allocate and return new clean entry as victim */ if ((dr = malloc(sizeof (*dr))) == NULL) { syslog(LOG_ERR, "__svc_dupcache_victim: malloc failed"); return (NULL); } (void) memset(dr, 0, sizeof (*dr)); return (dr); default: syslog(LOG_ERR, "__svc_dupcache_victim: undefined dup cache_basis"); return (NULL); } } /* * __svc_dupcache_enter(struct svc_req *req, struct dupreq *dr, * struct dupcache *dc, uint32_t drxid, uint32_t drhash, time_t timenow) * build new duprequest entry and then insert into the cache */ static int __svc_dupcache_enter(struct svc_req *req, struct dupreq *dr, struct dupcache *dc, uint32_t drxid, uint32_t drhash, time_t timenow) { dr->dr_xid = drxid; dr->dr_prog = req->rq_prog; dr->dr_vers = req->rq_vers; dr->dr_proc = req->rq_proc; dr->dr_addr.maxlen = req->rq_xprt->xp_rtaddr.len; dr->dr_addr.len = dr->dr_addr.maxlen; if ((dr->dr_addr.buf = malloc(dr->dr_addr.maxlen)) == NULL) { syslog(LOG_ERR, "__svc_dupcache_enter: malloc failed"); free(dr); return (DUP_ERROR); } (void) memset(dr->dr_addr.buf, 0, dr->dr_addr.len); (void) memcpy(dr->dr_addr.buf, req->rq_xprt->xp_rtaddr.buf, dr->dr_addr.len); dr->dr_resp.buf = NULL; dr->dr_resp.maxlen = 0; dr->dr_resp.len = 0; dr->dr_status = DUP_INPROGRESS; dr->dr_time = timenow; dr->dr_hash = drhash; /* needed for efficient victim cleanup */ /* place entry at head of hash table */ (void) rw_wrlock(&(dc->dc_lock)); dr->dr_chain = dc->dc_hashtbl[drhash]; dr->dr_prevchain = NULL; if (dc->dc_hashtbl[drhash] != NULL) dc->dc_hashtbl[drhash]->dr_prevchain = dr; dc->dc_hashtbl[drhash] = dr; (void) rw_unlock(&(dc->dc_lock)); return (DUP_NEW); } /* * __svc_dupdone(struct svc_req *req, caddr_t resp_buf, uint_t resp_bufsz, * int status, char *xprt_cache) * Marks the request done (DUP_DONE or DUP_DROP) and stores the response. * Only DONE and DROP requests can be marked as done. Sets the lru pointers * to make the entry the most recently used. Returns DUP_ERROR or status. */ int __svc_dupdone(struct svc_req *req, caddr_t resp_buf, uint_t resp_bufsz, int status, char *xprt_cache) { uint32_t drxid, drhash; int rc; /* LINTED pointer alignment */ struct dupcache *dc = (struct dupcache *)xprt_cache; if (dc == NULL) { syslog(LOG_ERR, "__svc_dupdone: undefined cache"); return (DUP_ERROR); } if (status != DUP_DONE && status != DUP_DROP) { syslog(LOG_ERR, "__svc_dupdone: invalid dupdone status"); syslog(LOG_ERR, " must be DUP_DONE or DUP_DROP"); return (DUP_ERROR); } /* find the xid of the entry in the cache */ if (SVC_CONTROL(req->rq_xprt, SVCGET_XID, (void*)&drxid) == FALSE) { syslog(LOG_ERR, "__svc_dup: xid error"); return (DUP_ERROR); } drhash = drxid % dc->dc_buckets; /* update the status of the entry and result buffers, if required */ if ((rc = __svc_dupcache_update(req, resp_buf, resp_bufsz, status, dc, drxid, drhash)) == DUP_ERROR) { syslog(LOG_ERR, "__svc_dupdone: cache entry error"); return (DUP_ERROR); } return (rc); } /* * __svc_dupcache_update(struct svc_req *req, caddr_t resp_buf, * uint_t resp_bufsz, int status, struct dupcache *dc, uint32_t drxid, * uint32_t drhash) * Check if entry exists in the dupcacache. If it does, update its status * and time and also its buffer, if appropriate. Its possible, but unlikely * for DONE requests to not exist in the cache. Return DUP_ERROR or status. */ static int __svc_dupcache_update(struct svc_req *req, caddr_t resp_buf, uint_t resp_bufsz, int status, struct dupcache *dc, uint32_t drxid, uint32_t drhash) { struct dupreq *dr = NULL; time_t timenow = time(NULL); (void) rw_wrlock(&(dc->dc_lock)); dr = dc->dc_hashtbl[drhash]; while (dr != NULL) { if (dr->dr_xid == drxid && dr->dr_proc == req->rq_proc && dr->dr_prog == req->rq_prog && dr->dr_vers == req->rq_vers && dr->dr_addr.len == req->rq_xprt->xp_rtaddr.len && memcmp(dr->dr_addr.buf, req->rq_xprt->xp_rtaddr.buf, dr->dr_addr.len) == 0) { /* entry found */ if (dr->dr_hash != drhash) { /* sanity check */ (void) rw_unlock(&(dc->dc_lock)); syslog(LOG_ERR, "\n__svc_dupdone: hashing error"); return (DUP_ERROR); } /* store the results if bufer is not NULL */ if (resp_buf != NULL) { if ((dr->dr_resp.buf = malloc(resp_bufsz)) == NULL) { (void) rw_unlock(&(dc->dc_lock)); syslog(LOG_ERR, "__svc_dupdone: malloc failed"); return (DUP_ERROR); } (void) memset(dr->dr_resp.buf, 0, resp_bufsz); (void) memcpy(dr->dr_resp.buf, resp_buf, (uint_t)resp_bufsz); dr->dr_resp.len = resp_bufsz; } /* update status and done time */ dr->dr_status = status; dr->dr_time = timenow; /* move the entry to the mru position */ if (dc->dc_mru == NULL) { dr->dr_next = dr; dr->dr_prev = dr; } else { dr->dr_next = dc->dc_mru->dr_next; dc->dc_mru->dr_next->dr_prev = dr; dr->dr_prev = dc->dc_mru; dc->dc_mru->dr_next = dr; } dc->dc_mru = dr; (void) rw_unlock(&(dc->dc_lock)); return (status); } dr = dr->dr_chain; } (void) rw_unlock(&(dc->dc_lock)); syslog(LOG_ERR, "__svc_dupdone: entry not in dup cache"); return (DUP_ERROR); } #ifdef DUP_DEBUG /* * __svc_dupcache_debug(struct dupcache *dc) * print out the hash table stuff * * This function requires the caller to hold the reader * or writer version of the duplicate request cache lock (dc_lock). */ static void __svc_dupcache_debug(struct dupcache *dc) { struct dupreq *dr = NULL; int i; bool_t bval; fprintf(stderr, " HASHTABLE\n"); for (i = 0; i < dc->dc_buckets; i++) { bval = FALSE; dr = dc->dc_hashtbl[i]; while (dr != NULL) { if (!bval) { /* ensures bucket printed only once */ fprintf(stderr, " bucket : %d\n", i); bval = TRUE; } fprintf(stderr, "\txid: %u status: %d time: %ld", dr->dr_xid, dr->dr_status, dr->dr_time); fprintf(stderr, " dr: %x chain: %x prevchain: %x\n", dr, dr->dr_chain, dr->dr_prevchain); dr = dr->dr_chain; } } fprintf(stderr, " LRU\n"); if (dc->dc_mru) { dr = dc->dc_mru->dr_next; /* lru */ while (dr != dc->dc_mru) { fprintf(stderr, "\txid: %u status : %d time : %ld", dr->dr_xid, dr->dr_status, dr->dr_time); fprintf(stderr, " dr: %x next: %x prev: %x\n", dr, dr->dr_next, dr->dr_prev); dr = dr->dr_next; } fprintf(stderr, "\txid: %u status: %d time: %ld", dr->dr_xid, dr->dr_status, dr->dr_time); fprintf(stderr, " dr: %x next: %x prev: %x\n", dr, dr->dr_next, dr->dr_prev); } } #endif /* DUP_DEBUG */