1/* $OpenBSD: ldattach.c,v 1.20 2023/04/19 12:58:15 jsg Exp $ */ 2 3/* 4 * Copyright (c) 2007, 2008 Marc Balmer <mbalmer@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19/* 20 * Attach a line disciplines to a tty(4) device either from the commandline 21 * or from init(8) (using entries in /etc/ttys). Optionally pass the data 22 * received on the tty(4) device to the master device of a pty(4) pair. 23 */ 24 25#include <sys/types.h> 26#include <sys/ioctl.h> 27#include <sys/limits.h> 28#include <sys/socket.h> 29#include <sys/stat.h> 30 31#include <err.h> 32#include <errno.h> 33#include <fcntl.h> 34#include <paths.h> 35#include <poll.h> 36#include <signal.h> 37#include <stdio.h> 38#include <stdlib.h> 39#include <string.h> 40#include <syslog.h> 41#include <termios.h> 42#include <unistd.h> 43#include <util.h> 44 45#include "atomicio.h" 46 47__dead void usage(void); 48void relay(int, int); 49void coroner(int); 50 51volatile sig_atomic_t dying = 0; 52 53__dead void 54usage(void) 55{ 56 extern char *__progname; 57 58 fprintf(stderr, "usage: %s [-27dehmop] [-s baudrate] " 59 "[-t cond] discipline device\n", __progname); 60 exit(1); 61} 62 63/* relay data between two file descriptors */ 64void 65relay(int device, int pty) 66{ 67 struct pollfd pfd[2]; 68 int nfds, n, nread; 69 char buf[128]; 70 71 pfd[0].fd = device; 72 pfd[1].fd = pty; 73 74 while (!dying) { 75 pfd[0].events = POLLRDNORM; 76 pfd[1].events = POLLRDNORM; 77 nfds = poll(pfd, 2, INFTIM); 78 if (nfds == -1) { 79 syslog(LOG_ERR, "polling error"); 80 exit(1); 81 } 82 if (nfds == 0) /* should not happen */ 83 continue; 84 85 if (pfd[1].revents & POLLHUP) { /* slave device not connected */ 86 sleep(1); 87 continue; 88 } 89 90 for (n = 0; n < 2; n++) { 91 if (!(pfd[n].revents & POLLRDNORM)) 92 continue; 93 94 nread = read(pfd[n].fd, buf, sizeof(buf)); 95 if (nread == -1) { 96 syslog(LOG_ERR, "error reading from %s: %m", 97 n ? "pty" : "device"); 98 exit(1); 99 } 100 if (nread == 0) { 101 syslog(LOG_ERR, "eof during read from %s: %m", 102 n ? "pty" : "device"); 103 exit(1); 104 } 105 atomicio(vwrite, pfd[1 - n].fd, buf, nread); 106 } 107 } 108} 109 110int 111main(int argc, char *argv[]) 112{ 113 struct termios tty; 114 struct tstamps tstamps; 115 const char *errstr; 116 sigset_t sigset; 117 pid_t ppid; 118 int ch, fd, master = -1, slave, pty = 0, ldisc, nodaemon = 0; 119 int bits = 0, parity = 0, stop = 0, flowcl = 0, hupcl = 1; 120 speed_t speed = 0; 121 char devn[32], ptyn[32], *dev, *disc; 122 123 tstamps.ts_set = tstamps.ts_clr = 0; 124 125 if ((ppid = getppid()) == 1) 126 nodaemon = 1; 127 128 while ((ch = getopt(argc, argv, "27dehmops:t:")) != -1) { 129 switch (ch) { 130 case '2': 131 stop = 2; 132 break; 133 case '7': 134 bits = 7; 135 break; 136 case 'd': 137 nodaemon = 1; 138 break; 139 case 'e': 140 parity = 'e'; 141 break; 142 case 'h': 143 flowcl = 1; 144 break; 145 case 'm': 146 hupcl = 0; 147 break; 148 case 'o': 149 parity = 'o'; 150 break; 151 case 'p': 152 pty = 1; 153 break; 154 case 's': 155 speed = (speed_t)strtonum(optarg, 0, UINT_MAX, &errstr); 156 if (errstr) { 157 if (ppid != 1) 158 errx(1, "speed is %s: %s", errstr, 159 optarg); 160 else 161 goto bail_out; 162 } 163 break; 164 case 't': 165 if (!strcasecmp(optarg, "dcd")) 166 tstamps.ts_set |= TIOCM_CAR; 167 else if (!strcasecmp(optarg, "!dcd")) 168 tstamps.ts_clr |= TIOCM_CAR; 169 else if (!strcasecmp(optarg, "cts")) 170 tstamps.ts_set |= TIOCM_CTS; 171 else if (!strcasecmp(optarg, "!cts")) 172 tstamps.ts_clr |= TIOCM_CTS; 173 else { 174 if (ppid != 1) 175 errx(1, "'%s' not supported for " 176 "timestamping", optarg); 177 else 178 goto bail_out; 179 } 180 break; 181 default: 182 if (ppid != -1) 183 usage(); 184 } 185 } 186 argc -= optind; 187 argv += optind; 188 189 if (ppid != 1 && argc != 2) 190 usage(); 191 192 disc = *argv++; 193 dev = *argv; 194 if (strncmp(_PATH_DEV, dev, sizeof(_PATH_DEV) - 1)) { 195 (void)snprintf(devn, sizeof(devn), "%s%s", _PATH_DEV, dev); 196 dev = devn; 197 } 198 199 if (!strcmp(disc, "nmea")) { 200 ldisc = NMEADISC; 201 if (speed == 0) 202 speed = B4800; /* default is 4800 baud for nmea */ 203 } else if (!strcmp(disc, "msts")) { 204 ldisc = MSTSDISC; 205 } else if (!strcmp(disc, "endrun")) { 206 ldisc = ENDRUNDISC; 207 } else { 208 syslog(LOG_ERR, "unknown line discipline %s", disc); 209 goto bail_out; 210 } 211 212 if ((fd = open(dev, O_RDWR)) == -1) { 213 syslog(LOG_ERR, "can't open %s", dev); 214 goto bail_out; 215 } 216 217 /* 218 * Get the current line attributes, modify only values that are 219 * either requested on the command line or that are needed by 220 * the line discipline (e.g. nmea has a default baudrate of 221 * 4800 instead of 9600). 222 */ 223 if (tcgetattr(fd, &tty) == -1) { 224 if (ppid != 1) 225 warnx("tcgetattr"); 226 goto bail_out; 227 } 228 229 230 if (bits == 7) { 231 tty.c_cflag &= ~CS8; 232 tty.c_cflag |= CS7; 233 } else if (bits == 8) { 234 tty.c_cflag &= ~CS7; 235 tty.c_cflag |= CS8; 236 } 237 238 if (parity != 0) 239 tty.c_cflag |= PARENB; 240 if (parity == 'o') 241 tty.c_cflag |= PARODD; 242 else 243 tty.c_cflag &= ~PARODD; 244 245 if (stop == 2) 246 tty.c_cflag |= CSTOPB; 247 else 248 tty.c_cflag &= ~CSTOPB; 249 250 if (flowcl) 251 tty.c_cflag |= CRTSCTS; 252 253 if (hupcl == 0) 254 tty.c_cflag &= ~HUPCL; 255 256 if (speed != 0) 257 cfsetspeed(&tty, speed); 258 259 /* setup common to all line disciplines */ 260 if (ioctl(fd, TIOCSDTR, 0) == -1) 261 warn("TIOCSDTR"); 262 if (ioctl(fd, TIOCSETD, &ldisc) == -1) { 263 syslog(LOG_ERR, "can't attach %s line discipline on %s", disc, 264 dev); 265 goto bail_out; 266 } 267 268 /* line discpline specific setup */ 269 switch (ldisc) { 270 case NMEADISC: 271 case MSTSDISC: 272 case ENDRUNDISC: 273 if (ioctl(fd, TIOCSTSTAMP, &tstamps) == -1) { 274 warnx("TIOCSTSTAMP"); 275 goto bail_out; 276 } 277 tty.c_cflag |= CLOCAL; 278 tty.c_iflag = 0; 279 tty.c_lflag = 0; 280 tty.c_oflag = 0; 281 tty.c_cc[VMIN] = 1; 282 tty.c_cc[VTIME] = 0; 283 break; 284 } 285 286 /* finally set the line attributes */ 287 if (tcsetattr(fd, TCSADRAIN, &tty) == -1) { 288 if (ppid != 1) 289 warnx("tcsetattr"); 290 goto bail_out; 291 } 292 293 /* 294 * open a pty(4) pair to pass the data if the -p option has been 295 * given on the commandline. 296 */ 297 if (pty) { 298 if (openpty(&master, &slave, ptyn, NULL, NULL)) 299 errx(1, "can't open a pty"); 300 close(slave); 301 printf("%s\n", ptyn); 302 fflush(stdout); 303 } 304 if (nodaemon) 305 openlog("ldattach", LOG_PID | LOG_CONS | LOG_PERROR, 306 LOG_DAEMON); 307 else { 308 openlog("ldattach", LOG_PID | LOG_CONS, LOG_DAEMON); 309 if (daemon(0, 0)) 310 errx(1, "can't daemonize"); 311 } 312 313 syslog(LOG_INFO, "attach %s on %s", disc, dev); 314 signal(SIGHUP, coroner); 315 signal(SIGTERM, coroner); 316 317 if (master != -1) { 318 syslog(LOG_INFO, "passing data to %s", ptyn); 319 relay(fd, master); 320 } else { 321 sigemptyset(&sigset); 322 323 while (!dying) 324 sigsuspend(&sigset); 325 } 326 327bail_out: 328 if (ppid == 1) 329 sleep(30); /* delay restart when called from init */ 330 331 return 0; 332} 333 334void 335coroner(int useless) 336{ 337 dying = 1; 338} 339