/* $OpenBSD: ldattach.c,v 1.14 2009/10/31 02:53:11 ckuethe Exp $ */ /* * Copyright (c) 2007, 2008 Marc Balmer <mbalmer@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Attach a line disciplines to a tty(4) device either from the commandline * or from init(8) (using entries in /etc/ttys). Optionally pass the data * received on the tty(4) device to the master device of a pty(4) pair. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/limits.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/ttycom.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <paths.h> #include <poll.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <termios.h> #include <unistd.h> #include <util.h> #include "atomicio.h" __dead void usage(void); void relay(int, int); void coroner(int); volatile sig_atomic_t dying = 0; __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-27dehmop] [-s baudrate] " "[-t cond] discipline device\n", __progname); exit(1); } /* relay data between two file descriptors */ void relay(int device, int pty) { struct pollfd pfd[2]; int nfds, n, nread; char buf[128]; pfd[0].fd = device; pfd[1].fd = pty; while (!dying) { pfd[0].events = POLLRDNORM; pfd[1].events = POLLRDNORM; nfds = poll(pfd, 2, INFTIM); if (nfds == -1) { syslog(LOG_ERR, "polling error"); exit(1); } if (nfds == 0) /* should not happen */ continue; if (pfd[1].revents & POLLHUP) { /* slave device not connected */ sleep(1); continue; } for (n = 0; n < 2; n++) { if (!(pfd[n].revents & POLLRDNORM)) continue; nread = read(pfd[n].fd, buf, sizeof(buf)); if (nread == -1) { syslog(LOG_ERR, "error reading from %s: %m", n ? "pty" : "device"); exit(1); } if (nread == 0) { syslog(LOG_ERR, "eof during read from %s: %m", n ? "pty" : "device"); exit(1); } atomicio(vwrite, pfd[1 - n].fd, buf, nread); } } } int main(int argc, char *argv[]) { struct termios tty; struct tstamps tstamps; const char *errstr; sigset_t sigset; pid_t ppid; int ch, fd, master = -1, slave, pty = 0, ldisc, nodaemon = 0; int bits = 0, parity = 0, stop = 0, flowcl = 0, hupcl = 1; speed_t speed = 0; char devn[32], ptyn[32], *dev, *disc; tstamps.ts_set = tstamps.ts_clr = 0; if ((ppid = getppid()) == 1) nodaemon = 1; while ((ch = getopt(argc, argv, "27dehmops:t:")) != -1) { switch (ch) { case '2': stop = 2; break; case '7': bits = 7; break; case 'd': nodaemon = 1; break; case 'e': parity = 'e'; break; case 'h': flowcl = 1; break; case 'm': hupcl = 0; break; case 'o': parity = 'o'; break; case 'p': pty = 1; break; case 's': speed = (speed_t)strtonum(optarg, 0, UINT_MAX, &errstr); if (errstr) { if (ppid != 1) errx(1, "speed is %s: %s", errstr, optarg); else goto bail_out; } break; case 't': if (!strcasecmp(optarg, "dcd")) tstamps.ts_set |= TIOCM_CAR; else if (!strcasecmp(optarg, "!dcd")) tstamps.ts_clr |= TIOCM_CAR; else if (!strcasecmp(optarg, "cts")) tstamps.ts_set |= TIOCM_CTS; else if (!strcasecmp(optarg, "!cts")) tstamps.ts_clr |= TIOCM_CTS; else { if (ppid != 1) errx(1, "'%s' not supported for " "timestamping", optarg); else goto bail_out; } break; default: if (ppid != -1) usage(); } } argc -= optind; argv += optind; if (ppid != 1 && argc != 2) usage(); disc = *argv++; dev = *argv; if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) { (void)snprintf(devn, sizeof(devn), "%s%s", _PATH_DEV, dev); dev = devn; } if (!strcmp(disc, "slip")) { bits = 8; /* make sure we use 8 databits */ ldisc = SLIPDISC; } else if (!strcmp(disc, "nmea")) { ldisc = NMEADISC; if (speed == 0) speed = B4800; /* default is 4800 baud for nmea */ } else if (!strcmp(disc, "msts")) { ldisc = MSTSDISC; } else if (!strcmp(disc, "endrun")) { ldisc = ENDRUNDISC; } else { syslog(LOG_ERR, "unknown line discipline %s", disc); goto bail_out; } if ((fd = open(dev, O_RDWR)) < 0) { syslog(LOG_ERR, "can't open %s", dev); goto bail_out; } /* * Get the current line attributes, modify only values that are * either requested on the command line or that are needed by * the line discipline (e.g. nmea has a default baudrate of * 4800 instead of 9600). */ if (tcgetattr(fd, &tty) < 0) { if (ppid != 1) warnx("tcgetattr"); goto bail_out; } if (bits == 7) { tty.c_cflag &= ~CS8; tty.c_cflag |= CS7; } else if (bits == 8) { tty.c_cflag &= ~CS7; tty.c_cflag |= CS8; } if (parity != 0) tty.c_cflag |= PARENB; if (parity == 'o') tty.c_cflag |= PARODD; else tty.c_cflag &= ~PARODD; if (stop == 2) tty.c_cflag |= CSTOPB; else tty.c_cflag &= ~CSTOPB; if (flowcl) tty.c_cflag |= CRTSCTS; if (hupcl == 0) tty.c_cflag &= ~HUPCL; if (speed != 0) cfsetspeed(&tty, speed); /* setup common to all line disciplines */ if (ioctl(fd, TIOCSDTR, 0) < 0) warn("TIOCSDTR"); if (ioctl(fd, TIOCSETD, &ldisc) < 0) { syslog(LOG_ERR, "can't attach %s line discipline on %s", disc, dev); goto bail_out; } /* line discpline specific setup */ switch (ldisc) { case NMEADISC: case ENDRUNDISC: if (ioctl(fd, TIOCSTSTAMP, &tstamps) < 0) { warnx("TIOCSTSTAMP"); goto bail_out; } tty.c_cflag |= CLOCAL; /* FALLTHROUGH */ case SLIPDISC: tty.c_iflag = 0; tty.c_lflag = 0; tty.c_oflag = 0; tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 0; break; } /* finally set the line attributes */ if (tcsetattr(fd, TCSADRAIN, &tty) < 0) { if (ppid != 1) warnx("tcsetattr"); goto bail_out; } /* * open a pty(4) pair to pass the data if the -p option has been * given on the commandline. */ if (pty) { if (openpty(&master, &slave, ptyn, NULL, NULL)) errx(1, "can't open a pty"); close(slave); printf("%s\n", ptyn); fflush(stdout); } if (nodaemon) openlog("ldattach", LOG_PID | LOG_CONS | LOG_PERROR, LOG_DAEMON); else { openlog("ldattach", LOG_PID | LOG_CONS, LOG_DAEMON); if (daemon(0, 0)) errx(1, "can't daemonize"); } syslog(LOG_INFO, "attach %s on %s", disc, dev); signal(SIGHUP, coroner); signal(SIGTERM, coroner); if (master != -1) { syslog(LOG_INFO, "passing data to %s", ptyn); relay(fd, master); } else { sigemptyset(&sigset); while (!dying) sigsuspend(&sigset); } bail_out: if (ppid == 1) sleep(30); /* delay restart when called from init */ return 0; } /* ARGSUSED */ void coroner(int useless) { dying = 1; }