xref: /openbsd/sbin/ldattach/ldattach.c (revision 6bae335d)
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