#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <termio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stropts.h>
#include <unistd.h>
#include <sys/wait.h>
#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);
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;
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;
}
(void) sigprocmask(SIG_SETMASK, NULL, &cset);
tset = cset;
(void) sigaddset(&tset, SIGCLD);
(void) sigprocmask(SIG_SETMASK, &tset, NULL);
if ((pid = fork()) == 0) {
pcsync_close(pcpipe0, pcpipe1, pid, pmptr->p_fd);
tmchild(pmptr);
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 {
pmptr->p_childpid = pid;
}
if (pmptr->p_fd > 0) {
(void) close(pmptr->p_fd);
pmptr->p_fd = 0;
}
(void) sigprocmask(SIG_SETMASK, &cset, NULL);
pcsync_close(pcpipe0, pcpipe1, pid, pmptr->p_fd);
}
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);
}
}
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);
}
}
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;
}
void
do_poll(struct pollfd *fdp, int nfds)
{
int i, n;
struct pmtab *pmptr;
n = poll(fdp, (unsigned long)nfds, -1);
#ifdef DEBUG
debug("poll return");
#endif
if (n < 0) {
if (errno == EINTR)
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);
}
}
}
}
void
sigchild(int n __unused)
{
struct pmtab *pmptr;
siginfo_t info;
int status;
pid_t pid;
int rcode;
#ifdef DEBUG
debug("in sigchild");
#endif
for (;;) {
rcode = waitid(P_ALL, 0, &info, WNOHANG|WEXITED);
if (rcode == -1 && errno == EINTR)
continue;
if (rcode == -1 || (pid = info.si_pid) == 0)
break;
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
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;
}
}
}
void
sigterm(int _s __unused)
{
fatal("caught SIGTERM");
}
void
state_change(void)
{
struct pmtab *pmptr;
#ifdef DEBUG
debug("in state_change");
#endif
(void) close(PCpipe[0]);
(void) close(PCpipe[1]);
setup_PCpipe();
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;
}
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
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;
}
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);
}
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);
}
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;
}
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);
}
}
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);
}
}
static void
pcsync_close(int *p0, int *p1, int pid, int fd)
{
char ch;
if (pid == 0) {
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 {
(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]);
}
}