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);
48 void relay(int, int);
49 void coroner(int);
50
51 volatile sig_atomic_t dying = 0;
52
53 __dead void
usage(void)54 usage(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 */
64 void
relay(int device,int pty)65 relay(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
110 int
main(int argc,char * argv[])111 main(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
327 bail_out:
328 if (ppid == 1)
329 sleep(30); /* delay restart when called from init */
330
331 return 0;
332 }
333
334 void
coroner(int useless)335 coroner(int useless)
336 {
337 dying = 1;
338 }
339