/* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2014 Gary Mills */ /* * Network SNDR/ncall-ip server - based on nfsd */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __NCALL__ #include #include #include #define RDC_POOL_CREATE NC_IOC_POOL_CREATE #define RDC_POOL_RUN NC_IOC_POOL_RUN #define RDC_POOL_WAIT NC_IOC_POOL_WAIT #define RDC_PROGRAM NCALL_PROGRAM #define RDC_SERVICE "ncall" #undef RDC_SVCPOOL_ID /* We are overloading this value */ #define RDC_SVCPOOL_ID NCALL_SVCPOOL_ID #define RDC_SVC_NAME "NCALL" #define RDC_VERS_MIN NCALL_VERS_MIN #define RDC_VERS_MAX NCALL_VERS_MAX #else /* !__NCALL__ */ #include #include #include #define RDC_SERVICE "rdc" #define RDC_SVC_NAME "RDC" #endif /* __NCALL__ */ #define RDCADMIN "/etc/default/sndr" #include struct conn_ind { struct conn_ind *conn_next; struct conn_ind *conn_prev; struct t_call *conn_call; }; struct conn_entry { bool_t closing; struct netconfig nc; }; static char *progname; static struct conn_entry *conn_polled; static int num_conns; /* Current number of connections */ static struct pollfd *poll_array; /* array of poll descriptors for poll */ static size_t num_fds = 0; /* number of transport fds opened */ static void poll_for_action(); static void remove_from_poll_list(int); static int do_poll_cots_action(int, int); static int do_poll_clts_action(int, int); static void add_to_poll_list(int, struct netconfig *); static int bind_to_provider(char *, char *, struct netbuf **, struct netconfig **); static int set_addrmask(int, struct netconfig *, struct netbuf *); static void conn_close_oldest(void); static boolean_t conn_get(int, struct netconfig *, struct conn_ind **); static void cots_listen_event(int, int); static int discon_get(int, struct netconfig *, struct conn_ind **); static int nofile_increase(int); static int is_listen_fd_index(int); #if !defined(_SunOS_5_6) && !defined(_SunOS_5_7) && !defined(_SunOS_5_8) static int sndrsvcpool(int); static int svcwait(int id); #endif /* * RPC protocol block. Useful for passing registration information. */ struct protob { char *serv; /* ASCII service name, e.g. "RDC" */ int versmin; /* minimum version no. to be registered */ int versmax; /* maximum version no. to be registered */ int program; /* program no. to be registered */ struct protob *next; /* next entry on list */ }; static size_t end_listen_fds; static int debugflg = 0; static int max_conns_allowed = -1; static int listen_backlog = 10; static char *trans_provider = (char *)NULL; static int rdcsvc(int, struct netbuf, struct netconfig *); /* used by cots_listen_event() */ static int (*Mysvc)(int, struct netbuf, struct netconfig *) = rdcsvc; /* * Determine valid semantics for rdc. */ #define OK_TPI_TYPE(_nconf) \ (_nconf->nc_semantics == NC_TPI_CLTS || \ _nconf->nc_semantics == NC_TPI_COTS || \ _nconf->nc_semantics == NC_TPI_COTS_ORD) #define BE32_TO_U32(a) \ ((((uint32_t)((uchar_t *)a)[0] & 0xFF) << (uint32_t)24) |\ (((uint32_t)((uchar_t *)a)[1] & 0xFF) << (uint32_t)16) |\ (((uint32_t)((uchar_t *)a)[2] & 0xFF) << (uint32_t)8) |\ ((uint32_t)((uchar_t *)a)[3] & 0xFF)) #ifdef DEBUG /* * Only support UDP in DEBUG mode for now */ static char *defaultproviders[] = { "/dev/tcp", "/dev/tcp6", "/dev/udp", "/dev/udp6", NULL }; #else static char *defaultproviders[] = { "/dev/tcp6", "/dev/tcp", NULL }; #endif /* * Number of elements to add to the poll array on each allocation. */ #define POLL_ARRAY_INC_SIZE 64 #define NOFILE_INC_SIZE 64 #ifdef __NCALL__ const char *rdc_devr = "/dev/ncallip"; #else const char *rdc_devr = "/dev/rdc"; #endif static int rdc_fdr; static int open_rdc(void) { int fd = open(rdc_devr, O_RDONLY); if (fd < 0) return (-1); return (rdc_fdr = fd); } static int sndrsys(int type, void *arg) { int ret = -1; if (!rdc_fdr && open_rdc() < 0) { /* open failed */ syslog(LOG_ERR, "open_rdc() failed: %m\n"); } else { if ((ret = ioctl(rdc_fdr, type, arg)) < 0) { syslog(LOG_ERR, "ioctl(rdc_ioctl) failed: %m\n"); } } return (ret); } int rdc_transport_open(struct netconfig *nconf) { int fd; struct strioctl strioc; if ((nconf == (struct netconfig *)NULL) || (nconf->nc_device == (char *)NULL)) { syslog(LOG_ERR, "No netconfig device"); return (-1); } /* * Open the transport device. */ fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL); if (fd == -1) { if (t_errno == TSYSERR && errno == EMFILE && (nofile_increase(0) == 0)) { /* Try again with a higher NOFILE limit. */ fd = t_open(nconf->nc_device, O_RDWR, NULL); } if (fd == -1) { if (t_errno == TSYSERR) { syslog(LOG_ERR, "t_open failed: %m"); } else { syslog(LOG_ERR, "t_open failed: %s", t_errlist[t_errno]); } return (-1); } } /* * Pop timod because the RPC module must be as close as possible * to the transport. */ if (ioctl(fd, I_POP, 0) < 0) { syslog(LOG_ERR, "I_POP of timod failed: %m"); if (t_close(fd) == -1) { if (t_errno == TSYSERR) { syslog(LOG_ERR, "t_close failed on %d: %m", fd); } else { syslog(LOG_ERR, "t_close failed on %d: %s", fd, t_errlist[t_errno]); } } return (-1); } if (nconf->nc_semantics == NC_TPI_CLTS) { /* * Push rpcmod to filter data traffic to KRPC. */ if (ioctl(fd, I_PUSH, "rpcmod") < 0) { syslog(LOG_ERR, "I_PUSH of rpcmod failed: %m"); (void) t_close(fd); return (-1); } } else { if (ioctl(fd, I_PUSH, "rpcmod") < 0) { syslog(LOG_ERR, "I_PUSH of CONS rpcmod failed: %m"); if (t_close(fd) == -1) { if (t_errno == TSYSERR) { syslog(LOG_ERR, "t_close failed on %d: %m", fd); } else { syslog(LOG_ERR, "t_close failed on %d: %s", fd, t_errlist[t_errno]); } } return (-1); } strioc.ic_cmd = RPC_SERVER; strioc.ic_dp = (char *)0; strioc.ic_len = 0; strioc.ic_timout = -1; /* Tell CONS rpcmod to act like a server stream. */ if (ioctl(fd, I_STR, &strioc) < 0) { syslog(LOG_ERR, "CONS rpcmod set-up ioctl failed: %m"); if (t_close(fd) == -1) { if (t_errno == TSYSERR) { syslog(LOG_ERR, "t_close failed on %d: %m", fd); } else { syslog(LOG_ERR, "t_close failed on %d: %s", fd, t_errlist[t_errno]); } } return (-1); } } /* * Re-push timod so that we will still be doing TLI * operations on the descriptor. */ if (ioctl(fd, I_PUSH, "timod") < 0) { syslog(LOG_ERR, "I_PUSH of timod failed: %m"); if (t_close(fd) == -1) { if (t_errno == TSYSERR) { syslog(LOG_ERR, "t_close failed on %d: %m", fd); } else { syslog(LOG_ERR, "t_close failed on %d: %s", fd, t_errlist[t_errno]); } } return (-1); } return (fd); } void rdcd_log_tli_error(char *tli_name, int fd, struct netconfig *nconf) { int error; /* * Save the error code across syslog(), just in case syslog() * gets its own error and, therefore, overwrites errno. */ error = errno; if (t_errno == TSYSERR) { syslog(LOG_ERR, "%s(file descriptor %d/transport %s) %m", tli_name, fd, nconf->nc_proto); } else { syslog(LOG_ERR, "%s(file descriptor %d/transport %s) TLI error %d", tli_name, fd, nconf->nc_proto, t_errno); } errno = error; } /* * Called to set up service over a particular transport */ void do_one(char *provider, char *proto, struct protob *protobp0, int (*svc)(int, struct netbuf, struct netconfig *)) { struct netbuf *retaddr; struct netconfig *retnconf; struct netbuf addrmask; int vers; int sock; if (provider) { sock = bind_to_provider(provider, protobp0->serv, &retaddr, &retnconf); } else { (void) syslog(LOG_ERR, "Cannot establish %s service over %s: transport setup problem.", protobp0->serv, provider ? provider : proto); return; } if (sock == -1) { if ((Is_ipv6present() && (strcmp(provider, "/dev/tcp6") == 0)) || (!Is_ipv6present() && (strcmp(provider, "/dev/tcp") == 0))) (void) syslog(LOG_ERR, "Cannot establish %s service over %s: transport " "setup problem.", protobp0->serv, provider ? provider : proto); return; } if (set_addrmask(sock, retnconf, &addrmask) < 0) { (void) syslog(LOG_ERR, "Cannot set address mask for %s", retnconf->nc_netid); return; } /* * Register all versions of the programs in the protocol block list */ for (vers = protobp0->versmin; vers <= protobp0->versmax; vers++) { (void) rpcb_unset(protobp0->program, vers, retnconf); (void) rpcb_set(protobp0->program, vers, retnconf, retaddr); } if (retnconf->nc_semantics == NC_TPI_CLTS) { /* Don't drop core if supporting module(s) aren't loaded. */ (void) signal(SIGSYS, SIG_IGN); /* * svc() doesn't block, it returns success or failure. */ if ((*svc)(sock, addrmask, retnconf) < 0) { (void) syslog(LOG_ERR, "Cannot establish %s service " "over : %m. Exiting", protobp0->serv, sock, retnconf->nc_proto); exit(1); } } /* * We successfully set up the server over this transport. * Add this descriptor to the one being polled on. */ add_to_poll_list(sock, retnconf); } /* * Set up the SNDR/ncall-ip service over all the available transports. * Returns -1 for failure, 0 for success. */ int do_all(struct protob *protobp, int (*svc)(int, struct netbuf, struct netconfig *)) { struct netconfig *nconf; NCONF_HANDLE *nc; if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while (nconf = getnetconfig(nc)) { if ((nconf->nc_flag & NC_VISIBLE) && strcmp(nconf->nc_protofmly, "loopback") != 0 && OK_TPI_TYPE(nconf)) do_one(nconf->nc_device, nconf->nc_proto, protobp, svc); } (void) endnetconfig(nc); return (0); } /* * Read the /etc/default/sndr configuration file to determine if the * client has been configured for number of threads, backlog or transport * provider. */ static void read_default(void) { char *defval, *tmp_str; int errno; int tmp; /* Fail silently if error in opening the default rdc config file */ if ((defopen(RDCADMIN)) == 0) { if ((defval = defread("SNDR_THREADS=")) != NULL) { errno = 0; tmp = strtol(defval, (char **)NULL, 10); if (errno == 0) { max_conns_allowed = tmp; } } if ((defval = defread("SNDR_LISTEN_BACKLOG=")) != NULL) { errno = 0; tmp = strtol(defval, (char **)NULL, 10); if (errno == 0) { listen_backlog = tmp; } } if ((defval = defread("SNDR_TRANSPORT=")) != NULL) { errno = 0; tmp_str = strdup(defval); if (errno == 0) { trans_provider = tmp_str; } } /* close defaults file */ (void) defopen(NULL); } } #ifdef lint int sndrd_lintmain(int ac, char **av) #else int main(int ac, char **av) #endif { const char *dir = "/"; int allflag = 0; int pid; int i, rc; struct protob *protobp0, *protobp; char **providerp; char *required; #if !defined(_SunOS_5_6) && !defined(_SunOS_5_7) && !defined(_SunOS_5_8) int maxservers; #endif (void) setlocale(LC_ALL, ""); #ifdef __NCALL__ (void) textdomain("ncall"); #else (void) textdomain("rdc"); #endif progname = basename(av[0]); #ifdef __NCALL__ rc = ncall_check_release(&required); #else rc = rdc_check_release(&required); #endif if (rc < 0) { (void) fprintf(stderr, gettext("%s: unable to determine the current " "Solaris release: %s\n"), progname, strerror(errno)); exit(1); } else if (rc == FALSE) { (void) fprintf(stderr, gettext("%s: incorrect Solaris release (requires %s)\n"), progname, required); exit(1); } openlog(progname, LOG_PID|LOG_CONS, LOG_DAEMON); read_default(); /* * Usage: [-c ] [-t protocol] \ * [-d] [-l ] */ while ((i = getopt(ac, av, "ac:t:dl:")) != EOF) { switch (i) { case 'a': allflag = 1; break; case 'c': max_conns_allowed = atoi(optarg); if (max_conns_allowed <= 0) max_conns_allowed = 16; break; case 'd': debugflg++; break; case 't': trans_provider = optarg; break; case 'l': listen_backlog = atoi(optarg); if (listen_backlog < 0) listen_backlog = 32; break; default: syslog(LOG_ERR, "Usage: %s [-c ] " "[-d] [-t protocol] " "[-l ]\n", progname); exit(1); break; } } if (chroot(dir) < 0) { syslog(LOG_ERR, "chroot failed: %m"); exit(1); } if (chdir(dir) < 0) { syslog(LOG_ERR, "chdir failed: %m"); exit(1); } if (!debugflg) { pid = fork(); if (pid < 0) { syslog(LOG_ERR, "Fork failed\n"); exit(1); } if (pid != 0) exit(0); /* * Close existing file descriptors, open "/dev/null" as * standard input, output, and error, and detach from * controlling terminal. */ #if !defined(_SunOS_5_6) && !defined(_SunOS_5_7) && !defined(_SunOS_5_8) /* use closefrom(3C) from PSARC/2000/193 when possible */ closefrom(0); #else for (i = 0; i < _NFILE; i++) (void) close(i); #endif (void) open("/dev/null", O_RDONLY); (void) open("/dev/null", O_WRONLY); (void) dup(1); (void) setsid(); /* * ignore all signals apart from SIGTERM. */ for (i = 1; i < _sys_nsig; i++) (void) sigset(i, SIG_IGN); (void) sigset(SIGTERM, SIG_DFL); } #if !defined(_SunOS_5_6) && !defined(_SunOS_5_7) && !defined(_SunOS_5_8) /* * Set up kernel RPC thread pool for the SNDR/ncall-ip server. */ maxservers = (max_conns_allowed < 0 ? 16 : max_conns_allowed); if (sndrsvcpool(maxservers)) { (void) syslog(LOG_ERR, "Can't set up kernel %s service: %m. Exiting", progname); exit(1); } /* * Set up blocked thread to do LWP creation on behalf of the kernel. */ if (svcwait(RDC_SVCPOOL_ID)) { (void) syslog(LOG_ERR, "Can't set up %s pool creator: %m, Exiting", progname); exit(1); } #endif /* * Build a protocol block list for registration. */ protobp0 = protobp = (struct protob *)malloc(sizeof (struct protob)); protobp->serv = RDC_SVC_NAME; protobp->versmin = RDC_VERS_MIN; protobp->versmax = RDC_VERS_MAX; protobp->program = RDC_PROGRAM; protobp->next = (struct protob *)NULL; if (allflag) { if (do_all(protobp0, rdcsvc) == -1) exit(1); } else if (trans_provider) do_one(trans_provider, NULL, protobp0, rdcsvc); else { for (providerp = defaultproviders; *providerp != NULL; providerp++) { trans_provider = *providerp; do_one(trans_provider, NULL, protobp0, rdcsvc); } } done: free(protobp); end_listen_fds = num_fds; /* * Poll for non-data control events on the transport descriptors. */ poll_for_action(); syslog(LOG_ERR, "%s fatal server error\n", progname); return (-1); } static int reuseaddr(int fd) { struct t_optmgmt req, resp; struct opthdr *opt; char reqbuf[128]; int *ip; /* LINTED pointer alignment */ opt = (struct opthdr *)reqbuf; opt->level = SOL_SOCKET; opt->name = SO_REUSEADDR; opt->len = sizeof (int); /* LINTED pointer alignment */ ip = (int *)&reqbuf[sizeof (struct opthdr)]; *ip = 1; req.flags = T_NEGOTIATE; req.opt.len = sizeof (struct opthdr) + opt->len; req.opt.buf = (char *)opt; resp.flags = 0; resp.opt.buf = reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { if (t_errno == TSYSERR) { syslog(LOG_ERR, "reuseaddr() t_optmgmt failed: %m\n"); } else { syslog(LOG_ERR, "reuseaddr() t_optmgmt failed: %s\n", t_errlist[t_errno]); } return (-1); } return (0); } /* * poll on the open transport descriptors for events and errors. */ void poll_for_action(void) { int nfds; int i; /* * Keep polling until all transports have been closed. When this * happens, we return. */ while ((int)num_fds > 0) { nfds = poll(poll_array, num_fds, INFTIM); switch (nfds) { case 0: continue; case -1: /* * Some errors from poll could be * due to temporary conditions, and we try to * be robust in the face of them. Other * errors (should never happen in theory) * are fatal (eg. EINVAL, EFAULT). */ switch (errno) { case EINTR: continue; case EAGAIN: case ENOMEM: (void) sleep(10); continue; default: (void) syslog(LOG_ERR, "poll failed: %m. Exiting"); exit(1); } default: break; } /* * Go through the poll list looking for events. */ for (i = 0; i < num_fds && nfds > 0; i++) { if (poll_array[i].revents) { nfds--; /* * We have a message, so try to read it. * Record the error return in errno, * so that syslog(LOG_ERR, "...%m") * dumps the corresponding error string. */ if (conn_polled[i].nc.nc_semantics == NC_TPI_CLTS) { errno = do_poll_clts_action( poll_array[i].fd, i); } else { errno = do_poll_cots_action( poll_array[i].fd, i); } if (errno == 0) continue; /* * Most returned error codes mean that there is * fatal condition which we can only deal with * by closing the transport. */ if (errno != EAGAIN && errno != ENOMEM) { (void) syslog(LOG_ERR, "Error (%m) reading descriptor %d" "/transport %s. Closing it.", poll_array[i].fd, conn_polled[i].nc.nc_proto); (void) t_close(poll_array[i].fd); remove_from_poll_list(poll_array[i].fd); } else if (errno == ENOMEM) (void) sleep(5); } } } (void) syslog(LOG_ERR, "All transports have been closed with errors. Exiting."); } /* * Allocate poll/transport array entries for this descriptor. */ static void add_to_poll_list(int fd, struct netconfig *nconf) { static int poll_array_size = 0; /* * If the arrays are full, allocate new ones. */ if (num_fds == poll_array_size) { struct pollfd *tpa; struct conn_entry *tnp; if (poll_array_size != 0) { tpa = poll_array; tnp = conn_polled; } else tpa = (struct pollfd *)0; poll_array_size += POLL_ARRAY_INC_SIZE; /* * Allocate new arrays. */ poll_array = (struct pollfd *) malloc(poll_array_size * sizeof (struct pollfd) + 256); conn_polled = (struct conn_entry *) malloc(poll_array_size * sizeof (struct conn_entry) + 256); if (poll_array == (struct pollfd *)NULL || conn_polled == (struct conn_entry *)NULL) { syslog(LOG_ERR, "malloc failed for poll array"); exit(1); } /* * Copy the data of the old ones into new arrays, and * free the old ones. * num_fds is guaranteed to be less than * poll_array_size, so this memcpy is safe. */ if (tpa) { (void) memcpy((void *)poll_array, (void *)tpa, num_fds * sizeof (struct pollfd)); (void) memcpy((void *)conn_polled, (void *)tnp, num_fds * sizeof (struct conn_entry)); free((void *)tpa); free((void *)tnp); } } /* * Set the descriptor and event list. All possible events are * polled for. */ poll_array[num_fds].fd = fd; poll_array[num_fds].events = POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI; /* * Copy the transport data over too. */ conn_polled[num_fds].nc = *nconf; /* structure copy */ conn_polled[num_fds].closing = 0; /* * Set the descriptor to non-blocking. Avoids a race * between data arriving on the stream and then having it * flushed before we can read it. */ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { (void) syslog(LOG_ERR, "fcntl(file desc. %d/transport %s, F_SETFL, " "O_NONBLOCK): %m. Exiting", num_fds, nconf->nc_proto); exit(1); } /* * Count this descriptor. */ ++num_fds; } static void remove_from_poll_list(int fd) { int i; int num_to_copy; for (i = 0; i < num_fds; i++) { if (poll_array[i].fd == fd) { --num_fds; num_to_copy = num_fds - i; (void) memcpy((void *)&poll_array[i], (void *)&poll_array[i+1], num_to_copy * sizeof (struct pollfd)); (void) memset((void *)&poll_array[num_fds], 0, sizeof (struct pollfd)); (void) memcpy((void *)&conn_polled[i], (void *)&conn_polled[i+1], num_to_copy * sizeof (struct conn_entry)); (void) memset((void *)&conn_polled[num_fds], 0, sizeof (struct conn_entry)); return; } } syslog(LOG_ERR, "attempt to remove nonexistent fd from poll list"); } static void conn_close_oldest(void) { int fd; int i1; /* * Find the oldest connection that is not already in the * process of shutting down. */ for (i1 = end_listen_fds; /* no conditional expression */; i1++) { if (i1 >= num_fds) return; if (conn_polled[i1].closing == 0) break; } #ifdef DEBUG (void) printf("too many connections (%d), releasing oldest (%d)\n", num_conns, poll_array[i1].fd); #else syslog(LOG_WARNING, "too many connections (%d), releasing oldest (%d)", num_conns, poll_array[i1].fd); #endif fd = poll_array[i1].fd; if (conn_polled[i1].nc.nc_semantics == NC_TPI_COTS) { /* * For politeness, send a T_DISCON_REQ to the transport * provider. We close the stream anyway. */ (void) t_snddis(fd, (struct t_call *)0); num_conns--; remove_from_poll_list(fd); (void) t_close(fd); } else { /* * For orderly release, we do not close the stream * until the T_ORDREL_IND arrives to complete * the handshake. */ if (t_sndrel(fd) == 0) conn_polled[i1].closing = 1; } } static boolean_t conn_get(int fd, struct netconfig *nconf, struct conn_ind **connp) { struct conn_ind *conn; struct conn_ind *next_conn; conn = (struct conn_ind *)malloc(sizeof (*conn)); if (conn == NULL) { syslog(LOG_ERR, "malloc for listen indication failed"); return (FALSE); } /* LINTED pointer alignment */ conn->conn_call = (struct t_call *)t_alloc(fd, T_CALL, T_ALL); if (conn->conn_call == NULL) { free((char *)conn); rdcd_log_tli_error("t_alloc", fd, nconf); return (FALSE); } if (t_listen(fd, conn->conn_call) == -1) { rdcd_log_tli_error("t_listen", fd, nconf); (void) t_free((char *)conn->conn_call, T_CALL); free((char *)conn); return (FALSE); } if (conn->conn_call->udata.len > 0) { syslog(LOG_WARNING, "rejecting inbound connection(%s) with %d bytes " "of connect data", nconf->nc_proto, conn->conn_call->udata.len); conn->conn_call->udata.len = 0; (void) t_snddis(fd, conn->conn_call); (void) t_free((char *)conn->conn_call, T_CALL); free((char *)conn); return (FALSE); } if ((next_conn = *connp) != NULL) { next_conn->conn_prev->conn_next = conn; conn->conn_next = next_conn; conn->conn_prev = next_conn->conn_prev; next_conn->conn_prev = conn; } else { conn->conn_next = conn; conn->conn_prev = conn; *connp = conn; } return (TRUE); } static int discon_get(int fd, struct netconfig *nconf, struct conn_ind **connp) { struct conn_ind *conn; struct t_discon discon; discon.udata.buf = (char *)0; discon.udata.maxlen = 0; if (t_rcvdis(fd, &discon) == -1) { rdcd_log_tli_error("t_rcvdis", fd, nconf); return (-1); } conn = *connp; if (conn == NULL) return (0); do { if (conn->conn_call->sequence == discon.sequence) { if (conn->conn_next == conn) *connp = (struct conn_ind *)0; else { if (conn == *connp) { *connp = conn->conn_next; } conn->conn_next->conn_prev = conn->conn_prev; conn->conn_prev->conn_next = conn->conn_next; } free((char *)conn); break; } conn = conn->conn_next; } while (conn != *connp); return (0); } static void cots_listen_event(int fd, int conn_index) { struct t_call *call; struct conn_ind *conn; struct conn_ind *conn_head; int event; struct netconfig *nconf = &conn_polled[conn_index].nc; int new_fd; struct netbuf addrmask; int ret = 0; conn_head = NULL; (void) conn_get(fd, nconf, &conn_head); while ((conn = conn_head) != NULL) { conn_head = conn->conn_next; if (conn_head == conn) conn_head = NULL; else { conn_head->conn_prev = conn->conn_prev; conn->conn_prev->conn_next = conn_head; } call = conn->conn_call; free(conn); /* * If we have already accepted the maximum number of * connections allowed on the command line, then drop * the oldest connection (for any protocol) before * accepting the new connection. Unless explicitly * set on the command line, max_conns_allowed is -1. */ if (max_conns_allowed != -1 && num_conns >= max_conns_allowed) conn_close_oldest(); /* * Create a new transport endpoint for the same proto as * the listener. */ new_fd = rdc_transport_open(nconf); if (new_fd == -1) { call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); syslog(LOG_ERR, "Cannot establish transport over %s", nconf->nc_device); continue; } /* Bind to a generic address/port for the accepting stream. */ if (t_bind(new_fd, NULL, NULL) == -1) { rdcd_log_tli_error("t_bind", new_fd, nconf); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); continue; } while (t_accept(fd, new_fd, call) == -1) { if (t_errno != TLOOK) { rdcd_log_tli_error("t_accept", fd, nconf); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } while (event = t_look(fd)) { switch (event) { case T_LISTEN: (void) conn_get(fd, nconf, &conn_head); continue; case T_DISCONNECT: (void) discon_get(fd, nconf, &conn_head); continue; default: syslog(LOG_ERR, "unexpected event 0x%x during " "accept processing (%s)", event, nconf->nc_proto); call->udata.len = 0; (void) t_snddis(fd, call); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } } } if (set_addrmask(new_fd, nconf, &addrmask) < 0) { (void) syslog(LOG_ERR, "Cannot set address mask for %s", nconf->nc_netid); (void) t_snddis(new_fd, NULL); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); continue; } /* Tell kRPC about the new stream. */ ret = (*Mysvc)(new_fd, addrmask, nconf); if (ret < 0) { syslog(LOG_ERR, "unable to register with kernel rpc: %m"); free(addrmask.buf); (void) t_snddis(new_fd, NULL); (void) t_free((char *)call, T_CALL); (void) t_close(new_fd); goto do_next_conn; } free(addrmask.buf); (void) t_free((char *)call, T_CALL); /* * Poll on the new descriptor so that we get disconnect * and orderly release indications. */ num_conns++; add_to_poll_list(new_fd, nconf); /* Reset nconf in case it has been moved. */ nconf = &conn_polled[conn_index].nc; do_next_conn:; } } static int do_poll_cots_action(int fd, int conn_index) { char buf[256]; int event; int i1; int flags; struct conn_entry *connent = &conn_polled[conn_index]; struct netconfig *nconf = &(connent->nc); const char *errorstr; while (event = t_look(fd)) { switch (event) { case T_LISTEN: cots_listen_event(fd, conn_index); break; case T_DATA: /* * Receive a private notification from CONS rpcmod. */ i1 = t_rcv(fd, buf, sizeof (buf), &flags); if (i1 == -1) { syslog(LOG_ERR, "t_rcv failed"); break; } if (i1 < sizeof (int)) break; i1 = BE32_TO_U32(buf); if (i1 == 1 || i1 == 2) { /* * This connection has been idle for too long, * so release it as politely as we can. If we * have already initiated an orderly release * and we get notified that the stream is * still idle, pull the plug. This prevents * hung connections from continuing to consume * resources. */ if (nconf->nc_semantics == NC_TPI_COTS || connent->closing != 0) { (void) t_snddis(fd, (struct t_call *)0); goto fdclose; } /* * For NC_TPI_COTS_ORD, the stream is closed * and removed from the poll list when the * T_ORDREL is received from the provider. We * don't wait for it here because it may take * a while for the transport to shut down. */ if (t_sndrel(fd) == -1) { syslog(LOG_ERR, "unable to send orderly release %m"); } connent->closing = 1; } else syslog(LOG_ERR, "unexpected event from CONS rpcmod %d", i1); break; case T_ORDREL: /* Perform an orderly release. */ if (t_rcvrel(fd) == 0) { /* T_ORDREL on listen fd's should be ignored */ if (!is_listen_fd_index(fd)) { (void) t_sndrel(fd); goto fdclose; } break; } else if (t_errno == TLOOK) { break; } else { rdcd_log_tli_error("t_rcvrel", fd, nconf); /* * check to make sure we do not close * listen fd */ if (!is_listen_fd_index(fd)) break; else goto fdclose; } case T_DISCONNECT: if (t_rcvdis(fd, (struct t_discon *)NULL) == -1) rdcd_log_tli_error("t_rcvdis", fd, nconf); /* * T_DISCONNECT on listen fd's should be ignored. */ if (!is_listen_fd_index(fd)) break; else goto fdclose; default: if (t_errno == TSYSERR) { if ((errorstr = strerror(errno)) == NULL) { (void) snprintf(buf, sizeof (buf), "Unknown error num %d", errno); errorstr = (const char *)buf; } } else if (event == -1) errorstr = t_strerror(t_errno); else errorstr = ""; #ifdef DEBUG syslog(LOG_ERR, "unexpected TLI event (0x%x) on " "connection-oriented transport(%s, %d):%s", event, nconf->nc_proto, fd, errorstr); #endif fdclose: num_conns--; remove_from_poll_list(fd); (void) t_close(fd); return (0); } } return (0); } /* * Called to read and interpret the event on a connectionless descriptor. * Returns 0 if successful, or a UNIX error code if failure. */ static int do_poll_clts_action(int fd, int conn_index) { int error; int ret; int flags; struct netconfig *nconf = &conn_polled[conn_index].nc; static struct t_unitdata *unitdata = NULL; static struct t_uderr *uderr = NULL; static int oldfd = -1; struct nd_hostservlist *host = NULL; struct strbuf ctl[1], data[1]; /* * We just need to have some space to consume the * message in the event we can't use the TLI interface to do the * job. * * We flush the message using getmsg(). For the control part * we allocate enough for any TPI header plus 32 bytes for address * and options. For the data part, there is nothing magic about * the size of the array, but 256 bytes is probably better than * 1 byte, and we don't expect any data portion anyway. * * If the array sizes are too small, we handle this because getmsg() * (called to consume the message) will return MOREDATA|MORECTL. * Thus we just call getmsg() until it's read the message. */ char ctlbuf[sizeof (union T_primitives) + 32]; char databuf[256]; /* * If this is the same descriptor as the last time * do_poll_clts_action was called, we can save some * de-allocation and allocation. */ if (oldfd != fd) { oldfd = fd; if (unitdata) { (void) t_free((char *)unitdata, T_UNITDATA); unitdata = NULL; } if (uderr) { (void) t_free((char *)uderr, T_UDERROR); uderr = NULL; } } /* * Allocate a unitdata structure for receiving the event. */ if (unitdata == NULL) { /* LINTED pointer alignment */ unitdata = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ALL); if (unitdata == NULL) { if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, " "T_UNITDATA) failed: %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/" "transport %s, T_UNITDATA) failed TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } } try_again: flags = 0; /* * The idea is we wait for T_UNITDATA_IND's. Of course, * we don't get any, because rpcmod filters them out. * However, we need to call t_rcvudata() to let TLI * tell us we have a T_UDERROR_IND. * * algorithm is: * t_rcvudata(), expecting TLOOK. * t_look(), expecting T_UDERR. * t_rcvuderr(), expecting success (0). * expand destination address into ASCII, * and dump it. */ ret = t_rcvudata(fd, unitdata, &flags); if (ret == 0 || t_errno == TBUFOVFLW) { (void) syslog(LOG_WARNING, "t_rcvudata(file descriptor %d/" "transport %s) got unexpected data, %d bytes", fd, nconf->nc_proto, unitdata->udata.len); /* * Even though we don't expect any data, in case we do, * keep reading until there is no more. */ if (flags & T_MORE) goto try_again; return (0); } switch (t_errno) { case TNODATA: return (0); case TSYSERR: /* * System errors are returned to caller. * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_rcvudata(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); case TLOOK: break; default: (void) syslog(LOG_ERR, "t_rcvudata(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } ret = t_look(fd); switch (ret) { case 0: return (0); case -1: /* * System errors are returned to caller. */ if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_look(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_look(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; case T_UDERR: break; default: (void) syslog(LOG_WARNING, "t_look(file descriptor %d/" "transport %s) returned %d not T_UDERR (%d)", fd, nconf->nc_proto, ret, T_UDERR); } if (uderr == NULL) { /* LINTED pointer alignment */ uderr = (struct t_uderr *)t_alloc(fd, T_UDERROR, T_ALL); if (uderr == NULL) { if (t_errno == TSYSERR) { /* * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/transport %s, " "T_UDERROR) failed: %m", fd, nconf->nc_proto); return (error); } (void) syslog(LOG_ERR, "t_alloc(file descriptor %d/" "transport %s, T_UDERROR) failed TLI error: %d", fd, nconf->nc_proto, t_errno); goto flush_it; } } ret = t_rcvuderr(fd, uderr); if (ret == 0) { /* * Save the datagram error in errno, so that the * %m argument to syslog picks up the error string. */ errno = uderr->error; /* * Log the datagram error, then log the host that * probably triggerred. Cannot log both in the * same transaction because of packet size limitations * in /dev/log. */ (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "%s response over " "generated error: %m", progname, fd, nconf->nc_proto); /* * Try to map the client's address back to a * name. */ ret = netdir_getbyaddr(nconf, &host, &uderr->addr); if (ret != -1 && host && host->h_cnt > 0 && host->h_hostservs) { (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "Bad %s response was sent to client with " "host name: %s; service port: %s", progname, host->h_hostservs->h_host, host->h_hostservs->h_serv); } else { int i, j; char *buf; char *hex = "0123456789abcdef"; /* * Mapping failed, print the whole thing * in ASCII hex. */ buf = (char *)malloc(uderr->addr.len * 2 + 1); for (i = 0, j = 0; i < uderr->addr.len; i++, j += 2) { buf[j] = hex[((uderr->addr.buf[i]) >> 4) & 0xf]; buf[j+1] = hex[uderr->addr.buf[i] & 0xf]; } buf[j] = '\0'; (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, "Bad %s response was sent to client with " "transport address: 0x%s", progname, buf); free((void *)buf); } if (ret == 0 && host != NULL) netdir_free((void *)host, ND_HOSTSERVLIST); return (0); } switch (t_errno) { case TNOUDERR: goto flush_it; case TSYSERR: /* * System errors are returned to caller. * Save the error code across * syslog(), just in case * syslog() gets its own error * and therefore overwrites errno. */ error = errno; (void) syslog(LOG_ERR, "t_rcvuderr(file descriptor %d/transport %s) %m", fd, nconf->nc_proto); return (error); default: (void) syslog(LOG_ERR, "t_rcvuderr(file descriptor %d/transport %s) TLI error %d", fd, nconf->nc_proto, t_errno); goto flush_it; } flush_it: /* * If we get here, then we could not cope with whatever message * we attempted to read, so flush it. If we did read a message, * and one isn't present, that is all right, because fd is in * nonblocking mode. */ (void) syslog(LOG_ERR, "Flushing one input message from ", fd, nconf->nc_proto); /* * Read and discard the message. Do this this until there is * no more control/data in the message or until we get an error. */ do { ctl->maxlen = sizeof (ctlbuf); ctl->buf = ctlbuf; data->maxlen = sizeof (databuf); data->buf = databuf; flags = 0; ret = getmsg(fd, ctl, data, &flags); if (ret == -1) return (errno); } while (ret != 0); return (0); } /* * Establish service thread. */ static int rdcsvc(int fd, struct netbuf addrmask, struct netconfig *nconf) { #ifdef __NCALL__ struct ncall_svc_args nsa; #else /* !__NCALL__ */ struct rdc_svc_args nsa; _rdc_ioctl_t rdc_args = { 0, }; #endif /* __NCALL__ */ nsa.fd = fd; nsa.nthr = (max_conns_allowed < 0 ? 16 : max_conns_allowed); (void) strncpy(nsa.netid, nconf->nc_netid, sizeof (nsa.netid)); nsa.addrmask.len = addrmask.len; nsa.addrmask.maxlen = addrmask.maxlen; nsa.addrmask.buf = addrmask.buf; #ifdef __NCALL__ return (sndrsys(NC_IOC_SERVER, &nsa)); #else /* !__NCALL__ */ rdc_args.arg0 = (long)&nsa; return (sndrsys(RDC_ENABLE_SVR, &rdc_args)); #endif /* __NCALL__ */ } static int nofile_increase(int limit) { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { syslog(LOG_ERR, "nofile_increase() getrlimit of NOFILE failed: %m"); return (-1); } if (limit > 0) rl.rlim_cur = limit; else rl.rlim_cur += NOFILE_INC_SIZE; if (rl.rlim_cur > rl.rlim_max && rl.rlim_max != RLIM_INFINITY) rl.rlim_max = rl.rlim_cur; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { syslog(LOG_ERR, "nofile_increase() setrlimit of NOFILE to %d failed: %m", rl.rlim_cur); return (-1); } return (0); } int rdcd_bindit(struct netconfig *nconf, struct netbuf **addr, struct nd_hostserv *hs, int backlog) { int fd; struct t_bind *ntb; struct t_bind tb; struct nd_addrlist *addrlist; struct t_optmgmt req, resp; struct opthdr *opt; char reqbuf[128]; if ((fd = rdc_transport_open(nconf)) == -1) { syslog(LOG_ERR, "cannot establish transport service over %s", nconf->nc_device); return (-1); } addrlist = (struct nd_addrlist *)NULL; if (netdir_getbyname(nconf, hs, &addrlist) != 0) { if (strncmp(nconf->nc_netid, "udp", 3) != 0) { syslog(LOG_ERR, "Cannot get address for transport " "%s host %s service %s", nconf->nc_netid, hs->h_host, hs->h_serv); } (void) t_close(fd); return (-1); } if (strcmp(nconf->nc_proto, "tcp") == 0) { /* * If we're running over TCP, then set the * SO_REUSEADDR option so that we can bind * to our preferred address even if previously * left connections exist in FIN_WAIT states. * This is somewhat bogus, but otherwise you have * to wait 2 minutes to restart after killing it. */ if (reuseaddr(fd) == -1) { syslog(LOG_WARNING, "couldn't set SO_REUSEADDR option on transport"); } } if (nconf->nc_semantics == NC_TPI_CLTS) tb.qlen = 0; else tb.qlen = backlog; /* LINTED pointer alignment */ ntb = (struct t_bind *)t_alloc(fd, T_BIND, T_ALL); if (ntb == (struct t_bind *)NULL) { syslog(LOG_ERR, "t_alloc failed: t_errno %d, %m", t_errno); (void) t_close(fd); netdir_free((void *)addrlist, ND_ADDRLIST); return (-1); } tb.addr = *(addrlist->n_addrs); /* structure copy */ if (t_bind(fd, &tb, ntb) == -1) { syslog(LOG_ERR, "t_bind failed: t_errno %d, %m", t_errno); (void) t_free((char *)ntb, T_BIND); netdir_free((void *)addrlist, ND_ADDRLIST); (void) t_close(fd); return (-1); } /* make sure we bound to the right address */ if (tb.addr.len != ntb->addr.len || memcmp(tb.addr.buf, ntb->addr.buf, tb.addr.len) != 0) { syslog(LOG_ERR, "t_bind to wrong address"); (void) t_free((char *)ntb, T_BIND); netdir_free((void *)addrlist, ND_ADDRLIST); (void) t_close(fd); return (-1); } *addr = &ntb->addr; netdir_free((void *)addrlist, ND_ADDRLIST); if (strcmp(nconf->nc_proto, "tcp") == 0 || strcmp(nconf->nc_proto, "tcp6") == 0) { /* * Disable the Nagle algorithm on TCP connections. * Connections accepted from this listener will * inherit the listener options. */ /* LINTED pointer alignment */ opt = (struct opthdr *)reqbuf; opt->level = IPPROTO_TCP; opt->name = TCP_NODELAY; opt->len = sizeof (int); /* LINTED pointer alignment */ *(int *)((char *)opt + sizeof (*opt)) = 1; req.flags = T_NEGOTIATE; req.opt.len = sizeof (*opt) + opt->len; req.opt.buf = (char *)opt; resp.flags = 0; resp.opt.buf = reqbuf; resp.opt.maxlen = sizeof (reqbuf); if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { syslog(LOG_ERR, "couldn't set NODELAY option for " "proto %s: t_errno = %d, %m", nconf->nc_proto, t_errno); } } return (fd); } /* ARGSUSED */ static int bind_to_provider(char *provider, char *serv, struct netbuf **addr, struct netconfig **retnconf) { struct netconfig *nconf; NCONF_HANDLE *nc; struct nd_hostserv hs; hs.h_host = HOST_SELF; hs.h_serv = RDC_SERVICE; /* serv_name_to_port_name(serv); */ if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { syslog(LOG_ERR, "setnetconfig failed: %m"); return (-1); } while (nconf = getnetconfig(nc)) { if (OK_TPI_TYPE(nconf) && strcmp(nconf->nc_device, provider) == 0) { *retnconf = nconf; return (rdcd_bindit(nconf, addr, &hs, listen_backlog)); } } (void) endnetconfig(nc); if ((Is_ipv6present() && (strcmp(provider, "/dev/tcp6") == 0)) || (!Is_ipv6present() && (strcmp(provider, "/dev/tcp") == 0))) syslog(LOG_ERR, "couldn't find netconfig entry for provider %s", provider); return (-1); } /* * For listen fd's index is always less than end_listen_fds. * It's value is equal to the number of open file descriptors after the * last listen end point was opened but before any connection was accepted. */ static int is_listen_fd_index(int index) { return (index < end_listen_fds); } /* * Create an address mask appropriate for the transport. * The mask is used to obtain the host-specific part of * a network address when comparing addresses. * For an internet address the host-specific part is just * the 32 bit IP address and this part of the mask is set * to all-ones. The port number part of the mask is zeroes. */ static int set_addrmask(int fd, struct netconfig *nconf, struct netbuf *mask) { struct t_info info; /* * Find the size of the address we need to mask. */ if (t_getinfo(fd, &info) < 0) { t_error("t_getinfo"); return (-1); } mask->len = mask->maxlen = info.addr; if (info.addr <= 0) { syslog(LOG_ERR, "set_addrmask: address size: %ld", info.addr); return (-1); } mask->buf = (char *)malloc(mask->len); if (mask->buf == NULL) { syslog(LOG_ERR, "set_addrmask: no memory"); return (-1); } (void) memset(mask->buf, 0, mask->len); /* reset all mask bits */ if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { /* * Set the mask so that the port is ignored. */ /* LINTED pointer alignment */ ((struct sockaddr_in *)mask->buf)->sin_addr.s_addr = (in_addr_t)~0; /* LINTED pointer alignment */ ((struct sockaddr_in *)mask->buf)->sin_family = (sa_family_t)~0; } #ifdef NC_INET6 else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { /* LINTED pointer alignment */ (void) memset(&((struct sockaddr_in6 *)mask->buf)->sin6_addr, (uchar_t)~0, sizeof (struct in6_addr)); /* LINTED pointer alignment */ ((struct sockaddr_in6 *)mask->buf)->sin6_family = (sa_family_t)~0; } #endif else { /* * Set all mask bits. */ (void) memset(mask->buf, (uchar_t)~0, mask->len); } return (0); } #if !defined(_SunOS_5_6) && !defined(_SunOS_5_7) && !defined(_SunOS_5_8) static int sndrsvcpool(int maxservers) { struct svcpool_args npa; npa.id = RDC_SVCPOOL_ID; npa.maxthreads = maxservers; npa.redline = 0; npa.qsize = 0; npa.timeout = 0; npa.stksize = 0; npa.max_same_xprt = 0; return (sndrsys(RDC_POOL_CREATE, &npa)); } /* * The following stolen from cmd/fs.d/nfs/lib/thrpool.c */ #include /* * Thread to call into the kernel and do work on behalf of SNDR/ncall-ip. */ static void * svcstart(void *arg) { int id = (int)arg; int err; while ((err = sndrsys(RDC_POOL_RUN, &id)) != 0) { /* * Interrupted by a signal while in the kernel. * this process is still alive, try again. */ if (err == EINTR) continue; else break; } /* * If we weren't interrupted by a signal, but did * return from the kernel, this thread's work is done, * and it should exit. */ thr_exit(NULL); return (NULL); } /* * User-space "creator" thread. This thread blocks in the kernel * until new worker threads need to be created for the service * pool. On return to userspace, if there is no error, create a * new thread for the service pool. */ static void * svcblock(void *arg) { int id = (int)arg; /* CONSTCOND */ while (1) { thread_t tid; int err; /* * Call into the kernel, and hang out there * until a thread needs to be created. */ if (err = sndrsys(RDC_POOL_WAIT, &id)) { if (err == ECANCELED || err == EBUSY) /* * If we get back ECANCELED, the service * pool is exiting, and we may as well * clean up this thread. If EBUSY is * returned, there's already a thread * looping on this pool, so we should * give up. */ break; else continue; } (void) thr_create(NULL, NULL, svcstart, (void *)id, THR_BOUND | THR_DETACHED, &tid); } thr_exit(NULL); return (NULL); } static int svcwait(int id) { thread_t tid; /* * Create a bound thread to wait for kernel LWPs that * need to be created. */ if (thr_create(NULL, NULL, svcblock, (void *)id, THR_BOUND | THR_DETACHED, &tid)) return (1); return (0); } #endif /* Solaris 9+ */