/*	$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;
}