/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ttymon.h" #include "tmstruct.h" #include "tmextern.h" #include "sac.h" static struct pmtab *find_pid(pid_t); static void kill_subprocesses(void); static struct pmtab *find_fd(int); static void pcsync_close(int *, int *, int, int); /* * fork_tmchild - fork child on the device */ static void fork_tmchild(struct pmtab *pmptr) { pid_t pid; sigset_t cset; sigset_t tset; int pcpipe0[2], pcpipe1[2]; int p0; #ifdef DEBUG debug("in fork_tmchild"); #endif pmptr->p_inservice = FALSE; /* * initialize pipe. * Child has pcpipe[0] pipe fd for reading and writing * and closes pcpipe[1]. Parent has pcpipe[1] pipe fd for * reading and writing and closes pcpipe[0]. * * This way if the child process exits the parent's block * read on pipe will return immediately as the other end of * the pipe has closed. Similarly if the parent process exits * child's blocking read on the pipe will return immediately. */ if (((p0 = pipe(pcpipe0)) == -1) || (pipe(pcpipe1) == -1)) { if (p0 == 0) { (void) close(pcpipe0[0]); (void) close(pcpipe0[1]); } log("pipe() failed: %s", strerror(errno)); pmptr->p_status = VALID; pmptr->p_childpid = 0; Retry = TRUE; } /* protect following region from SIGCLD */ (void) sigprocmask(SIG_SETMASK, NULL, &cset); tset = cset; (void) sigaddset(&tset, SIGCLD); (void) sigprocmask(SIG_SETMASK, &tset, NULL); if ((pid = fork()) == 0) { /* * Close all file descriptors except pmptr->p_fd * Wait for the parent process to close its fd */ pcsync_close(pcpipe0, pcpipe1, pid, pmptr->p_fd); /* The CHILD */ tmchild(pmptr); /* tmchild should never return */ fatal("tmchild for <%s> returns unexpected", pmptr->p_device); } else if (pid < 0) { log("fork failed: %s", strerror(errno)); pmptr->p_status = VALID; pmptr->p_childpid = 0; Retry = TRUE; } else { /* * The PARENT - store pid of child and close the device */ pmptr->p_childpid = pid; } if (pmptr->p_fd > 0) { (void) close(pmptr->p_fd); pmptr->p_fd = 0; } (void) sigprocmask(SIG_SETMASK, &cset, NULL); /* * Wait for child to close file descriptors */ pcsync_close(pcpipe0, pcpipe1, pid, pmptr->p_fd); } /* * got_carrier - carrier is detected on the stream * - depends on the flags, different action is taken * - R_FLAG - wait for data * - C_FLAG - if port is not disabled, fork tmchild * - A_FLAG - wait for data * - otherwise - write out prompt, then wait for data */ void got_carrier(struct pmtab *pmptr) { flush_input(pmptr->p_fd); if (pmptr->p_ttyflags & R_FLAG) { #ifdef DEBUG debug("R_FLAG"); #endif return; } else if ((pmptr->p_ttyflags & (C_FLAG|B_FLAG)) && (State != PM_DISABLED) && (!(pmptr->p_flags & X_FLAG))) { fork_tmchild(pmptr); } else if (pmptr->p_ttyflags & A_FLAG) { #ifdef DEBUG debug("A_FLAG"); #endif return; } else if (pmptr->p_timeout) { fork_tmchild(pmptr); } else if (!(pmptr->p_ttyflags & X_FLAG)) { write_prompt(pmptr->p_fd, pmptr, TRUE, TRUE); } } /* * got_data - data is detected on the stream, fork tmchild */ static void got_data(struct pmtab *pmptr) { struct sigaction sigact; if (tm_checklock(pmptr->p_fd) != 0) { pmptr->p_status = LOCKED; (void) close(pmptr->p_fd); pmptr->p_fd = 0; Nlocked++; if (Nlocked == 1) { sigact.sa_flags = 0; sigact.sa_handler = sigalarm; (void) sigemptyset(&sigact.sa_mask); (void) sigaction(SIGALRM, &sigact, NULL); (void) alarm(ALARMTIME); } } else { fork_tmchild(pmptr); } } /* * got_hup - stream hangup is detected, close the device */ static void got_hup(struct pmtab *pmptr) { #ifdef DEBUG debug("in got hup"); #endif (void) close(pmptr->p_fd); pmptr->p_fd = 0; pmptr->p_inservice = 0; Retry = TRUE; } /* * do_poll - poll device * - if POLLHUP received, close the device * - if POLLIN received, fork tmchild. */ void do_poll(struct pollfd *fdp, int nfds) { int i, n; struct pmtab *pmptr; n = poll(fdp, (unsigned long)nfds, -1); /* blocked poll */ #ifdef DEBUG debug("poll return"); #endif if (n < 0) { if (errno == EINTR) /* interrupt by signal */ return; fatal("do_poll: poll failed: %s", strerror(errno)); } for (i = 0; (i < nfds) && (n != 0); i++, fdp++) { if (fdp->revents != 0) { n--; if ((pmptr = find_fd(fdp->fd)) == NULL) { log("do_poll: cannot find fd %d in pmtab", fdp->fd); continue; } else if (fdp->revents & POLLHUP) { got_hup(pmptr); } else if (fdp->revents & POLLIN) { #ifdef DEBUG debug("got POLLIN"); #endif got_data(pmptr); } else if (fdp->revents & POLLERR) { fatal("ttymon[%d]: do_poll: POLLERR on fd %d", getpid(), fdp->fd); } } } } /* * sigchild - handler for SIGCLD * - find the pid of dead child * - clean utmp if U_FLAG is set */ void sigchild(int n __unused) { struct pmtab *pmptr; siginfo_t info; int status; pid_t pid; int rcode; #ifdef DEBUG debug("in sigchild"); #endif /* find all processes that died */ for (;;) { rcode = waitid(P_ALL, 0, &info, WNOHANG|WEXITED); if (rcode == -1 && errno == EINTR) continue; /* If no more children have exited, just return */ if (rcode == -1 || (pid = info.si_pid) == 0) break; /* construct status as returned from waitid() */ status = info.si_status & 0377; switch (info.si_code) { case CLD_EXITED: status <<= 8; break; case CLD_DUMPED: status |= WCOREFLG; break; case CLD_KILLED: break; } if ((pmptr = find_pid(pid)) == NULL) { #ifdef DEBUG log("cannot find dead child (%ld) in pmtab", pid); #endif /* * This may happen if the entry is deleted from pmtab * before the service exits. * We try to cleanup utmp entry */ cleanut(pid, status); } else { if (pmptr->p_flags & U_FLAG) cleanut(pid, status); pmptr->p_status = VALID; pmptr->p_fd = 0; pmptr->p_childpid = 0; pmptr->p_inservice = 0; Retry = TRUE; } } } /* * sigterm - handler for SIGTERM */ void sigterm(int _s __unused) { fatal("caught SIGTERM"); } /* * state_change - this is called when ttymon changes * its internal state between enabled and disabled */ void state_change(void) { struct pmtab *pmptr; #ifdef DEBUG debug("in state_change"); #endif /* * closing PCpipe will cause attached non-service children * to get SIGPOLL and exit */ (void) close(PCpipe[0]); (void) close(PCpipe[1]); /* reopen PCpipe */ setup_PCpipe(); /* * also close all open ports so ttymon can start over * with new internal state */ for (pmptr = PMtab; pmptr; pmptr = pmptr->p_next) { if ((pmptr->p_fd > 0) && (pmptr->p_childpid == 0)) { (void) close(pmptr->p_fd); pmptr->p_fd = 0; } } Retry = TRUE; } /* * re_read - reread pmtab * - kill tmchild if entry changed */ void re_read(void) { sigset_t cset; sigset_t tset; (void) sigprocmask(SIG_SETMASK, NULL, &cset); tset = cset; (void) sigaddset(&tset, SIGCLD); (void) sigprocmask(SIG_SETMASK, &tset, NULL); if (Nlocked > 0) { (void) alarm(0); Nlocked = 0; } read_pmtab(); kill_subprocesses(); (void) sigprocmask(SIG_SETMASK, &cset, NULL); purge(); if (Nentries > Npollfd) { #ifdef DEBUG debug("Nentries > Npollfd, reallocating pollfds"); #endif /* need to malloc more pollfd structure */ free(Pollp); Npollfd = Nentries + 10; if (Npollfd > Maxfds) Npollfd = Maxfds; Pollp = malloc((unsigned)(Npollfd * sizeof (struct pollfd))); if (Pollp == NULL) fatal("malloc for Pollp failed"); } Retry = TRUE; } /* * find_pid(pid) - find the corresponding pmtab entry for the pid */ static struct pmtab * find_pid(pid_t pid) { struct pmtab *pmptr; for (pmptr = PMtab; pmptr; pmptr = pmptr->p_next) { if (pmptr->p_childpid == pid) { return (pmptr); } } return (NULL); } /* * find_fd(fd) - find the corresponding pmtab entry for the fd */ static struct pmtab * find_fd(int fd) { struct pmtab *pmptr; for (pmptr = PMtab; pmptr; pmptr = pmptr->p_next) { if (pmptr->p_fd == fd) { return (pmptr); } } return (NULL); } /* * kill_subprocesses() - if the pmtab entry has been changed, * kill tmchild if it is not in service. * - close the device if there is no tmchild */ static void kill_subprocesses(void) { struct pmtab *pmptr; for (pmptr = PMtab; pmptr; pmptr = pmptr->p_next) { if (pmptr->p_status == VALID) continue; if ((pmptr->p_fd > 0) && (pmptr->p_childpid == 0)) { (void) close(pmptr->p_fd); pmptr->p_fd = 0; } else if ((pmptr->p_fd == 0) && (pmptr->p_childpid > 0) && (pmptr->p_inservice == FALSE)) { (void) kill(pmptr->p_childpid, SIGTERM); } } } static void mark_service(pid_t pid) { struct pmtab *pmptr; #ifdef DEBUG debug("in mark_service"); #endif if ((pmptr = find_pid(pid)) == NULL) { log("mark_service: cannot find child (%ld) in pmtab", pid); return; } pmptr->p_inservice = TRUE; } /* * read_pid(fd) - read pid info from PCpipe */ static void read_pid(int fd) { int ret; pid_t pid; for (;;) { if ((ret = read(fd, &pid, sizeof (pid))) < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) return; fatal("read PCpipe failed: %s", strerror(errno)); } if (ret == 0) return; if (ret != sizeof (pid)) fatal("read return size incorrect, ret = %d", ret); mark_service(pid); } } /* * sipoll_catch() - signal handle of SIGPOLL for ttymon * - it will check both PCpipe and pmpipe */ void sigpoll_catch(int s __unused) { int ret; struct pollfd pfd[2]; #ifdef DEBUG debug("in sigpoll_catch"); #endif pfd[0].fd = PCpipe[0]; pfd[1].fd = Pfd; pfd[0].events = POLLIN; pfd[1].events = POLLIN; if ((ret = poll(pfd, 2, 0)) < 0) fatal("sigpoll_catch: poll failed: %s", strerror(errno)); if (ret > 0) { if (pfd[0].revents & POLLIN) read_pid(pfd[0].fd); if (pfd[1].revents & POLLIN) sacpoll(); } } void sigalarm(int signo __unused) { struct pmtab *pmptr; struct sigaction sigact; int fd; #ifdef DEBUG debug("in sigalarm, Nlocked = %d", Nlocked); #endif for (pmptr = PMtab; pmptr; pmptr = pmptr->p_next) { if ((pmptr->p_status == LOCKED) && (pmptr->p_fd == 0)) { fd = open(pmptr->p_device, O_RDWR | O_NONBLOCK); if (fd == -1) { log("open (%s) failed: %s", pmptr->p_device, strerror(errno)); pmptr->p_status = VALID; Nlocked--; Retry = TRUE; } else { if (tm_checklock(fd) == 0) { Nlocked--; pmptr->p_fd = fd; Retry = TRUE; } else { (void) close(fd); } } } else if ((pmptr->p_status == SESSION) && (pmptr->p_fd == 0)) { fd = open(pmptr->p_device, O_RDWR | O_NONBLOCK); if (fd == -1) { log("open (%s) failed: %s", pmptr->p_device, strerror(errno)); pmptr->p_status = VALID; Nlocked--; Retry = TRUE; } else { if (check_session(fd) == 0) { Nlocked--; pmptr->p_fd = fd; Retry = TRUE; } else { (void) close(fd); } } } else if ((pmptr->p_status == UNACCESS) && (pmptr->p_fd == 0)) { fd = open(pmptr->p_device, O_RDWR | O_NONBLOCK); if (fd == -1) { log("open (%s) failed: %s", pmptr->p_device, strerror(errno)); pmptr->p_status = VALID; Nlocked--; Retry = TRUE; } else { Nlocked--; pmptr->p_fd = fd; Retry = TRUE; } } } if (Nlocked > 0) { sigact.sa_flags = 0; sigact.sa_handler = sigalarm; (void) sigemptyset(&sigact.sa_mask); (void) sigaction(SIGALRM, &sigact, NULL); (void) alarm(ALARMTIME); } else { sigact.sa_flags = 0; sigact.sa_handler = SIG_IGN; (void) sigemptyset(&sigact.sa_mask); (void) sigaction(SIGALRM, &sigact, NULL); } } /* * pcsync_close - For the child process close all open fd's except * the one that is passed to the routine. Coordinate the reads and * writes to the pipes by the parent and child process to ensure * the parent and child processes have closed all the file descriptors * that are not needed any more. */ static void pcsync_close(int *p0, int *p1, int pid, int fd) { char ch; if (pid == 0) { /* Child */ struct pmtab *tp; for (tp = PMtab; tp; tp = tp->p_next) if ((tp->p_fd > 0) && (tp->p_fd != fd)) (void) close(tp->p_fd); (void) close(p0[1]); (void) close(p1[0]); if (read(p0[0], &ch, 1) == 1) (void) write(p1[1], "a", 1); (void) close(p0[0]); (void) close(p1[1]); } else { /* Parent */ (void) close(p0[0]); (void) close(p1[1]); if (write(p0[1], "a", 1) == 1) (void) read(p1[0], &ch, 1); (void) close(p0[1]); (void) close(p1[0]); } }