1/*
2 * Copyright (c) 1995 Ugen J.S.Antsilevich
3 *
4 * Redistribution and use in source forms, with and without modification,
5 * are permitted provided that this entire comment appears intact.
6 *
7 * Redistribution in binary form may occur without any restrictions.
8 * Obviously, it would be nice if you gave credit where credit is due
9 * but requiring it would be too onerous.
10 *
11 * This software is provided ``AS IS'' without any warranties of any kind.
12 *
13 * Snoop stuff.
14 */
15
16#include <sys/cdefs.h>
17__FBSDID("$FreeBSD$");
18
19#include <sys/param.h>
20#include <sys/fcntl.h>
21#include <sys/filio.h>
22#include <sys/snoop.h>
23#include <sys/stat.h>
24#include <sys/linker.h>
25#include <sys/module.h>
26
27#include <err.h>
28#include <errno.h>
29#include <locale.h>
30#include <paths.h>
31#include <signal.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sysexits.h>
36#include <termcap.h>
37#include <termios.h>
38#include <time.h>
39#include <unistd.h>
40
41#define MSG_INIT	"Snoop started."
42#define MSG_OFLOW	"Snoop stopped due to overflow. Reconnecting."
43#define MSG_CLOSED	"Snoop stopped due to tty close. Reconnecting."
44#define MSG_CHANGE	"Snoop device change by user request."
45#define MSG_NOWRITE	"Snoop device change due to write failure."
46
47#define DEV_NAME_LEN	1024	/* for /dev/ttyXX++ */
48#define MIN_SIZE	256
49
50#define CHR_SWITCH	24	/* Ctrl+X	 */
51#define CHR_CLEAR	23	/* Ctrl+V	 */
52
53static void	clear(void);
54static void	timestamp(const char *);
55static void	set_tty(void);
56static void	unset_tty(void);
57static void	fatal(int, const char *);
58static int	open_snp(void);
59static void	cleanup(int);
60static void	usage(void) __dead2;
61static void	setup_scr(void);
62static void	attach_snp(void);
63static void	detach_snp(void);
64static void	set_dev(const char *);
65static void	ask_dev(char *, const char *);
66
67int		opt_reconn_close = 0;
68int		opt_reconn_oflow = 0;
69int		opt_interactive = 1;
70int		opt_timestamp = 0;
71int		opt_write = 0;
72int		opt_no_switch = 0;
73const char	*opt_snpdev;
74
75char		dev_name[DEV_NAME_LEN];
76int		snp_io;
77int		std_in = 0, std_out = 1;
78
79int		clear_ok = 0;
80struct termios	otty;
81char		tbuf[1024], gbuf[1024];
82
83static void
84clear(void)
85{
86
87	if (clear_ok)
88		tputs(gbuf, 1, putchar);
89	fflush(stdout);
90}
91
92static void
93timestamp(const char *buf)
94{
95	time_t		t;
96	char		btmp[1024];
97
98	clear();
99	printf("\n---------------------------------------------\n");
100	t = time(NULL);
101	strftime(btmp, 1024, "Time: %d %b %H:%M", localtime(&t));
102	printf("%s\n", btmp);
103	printf("%s\n", buf);
104	printf("---------------------------------------------\n");
105	fflush(stdout);
106}
107
108static void
109set_tty(void)
110{
111	struct termios	ntty;
112
113	ntty = otty;
114	ntty.c_lflag &= ~ICANON;	/* disable canonical operation */
115	ntty.c_lflag &= ~ECHO;
116#ifdef FLUSHO
117	ntty.c_lflag &= ~FLUSHO;
118#endif
119#ifdef PENDIN
120	ntty.c_lflag &= ~PENDIN;
121#endif
122#ifdef IEXTEN
123	ntty.c_lflag &= ~IEXTEN;
124#endif
125	ntty.c_cc[VMIN] = 1;		/* minimum of one character */
126	ntty.c_cc[VTIME] = 0;		/* timeout value */
127
128	ntty.c_cc[VINTR] = 07;		/* ^G */
129	ntty.c_cc[VQUIT] = 07;		/* ^G */
130	tcsetattr(std_in, TCSANOW, &ntty);
131}
132
133static void
134unset_tty(void)
135{
136
137	tcsetattr(std_in, TCSANOW, &otty);
138}
139
140static void
141fatal(int error, const char *buf)
142{
143
144	unset_tty();
145	if (buf)
146		errx(error, "fatal: %s", buf);
147	else
148		exit(error);
149}
150
151static int
152open_snp(void)
153{
154	int		f, mode;
155
156	if (opt_write)
157		mode = O_RDWR;
158	else
159		mode = O_RDONLY;
160
161	if (opt_snpdev == NULL)
162		f = open(_PATH_DEV "snp", mode);
163	else
164		f = open(opt_snpdev, mode);
165	if (f == -1)
166		fatal(EX_OSFILE, "cannot open snoop device");
167
168	return (f);
169}
170
171static void
172cleanup(int signo __unused)
173{
174
175	if (opt_timestamp)
176		timestamp("Logging Exited.");
177	close(snp_io);
178	unset_tty();
179	exit(EX_OK);
180}
181
182static void
183usage(void)
184{
185
186	fprintf(stderr, "usage: watch [-ciotnW] [tty name]\n");
187	exit(EX_USAGE);
188}
189
190static void
191setup_scr(void)
192{
193	char		*cbuf = gbuf, *term;
194
195	if (!opt_interactive)
196		return;
197	if ((term = getenv("TERM")))
198		if (tgetent(tbuf, term) == 1)
199			if (tgetstr("cl", &cbuf))
200				clear_ok = 1;
201	set_tty();
202	clear();
203}
204
205static void
206detach_snp(void)
207{
208	int		fd;
209
210	fd = -1;
211	ioctl(snp_io, SNPSTTY, &fd);
212}
213
214static void
215attach_snp(void)
216{
217	int		snp_tty;
218
219	snp_tty = open(dev_name, O_RDONLY | O_NONBLOCK);
220	if (snp_tty < 0)
221		fatal(EX_DATAERR, "can't open device");
222	if (ioctl(snp_io, SNPSTTY, &snp_tty) != 0)
223		fatal(EX_UNAVAILABLE, "cannot attach to tty");
224	close(snp_tty);
225	if (opt_timestamp)
226		timestamp("Logging Started.");
227}
228
229static void
230set_dev(const char *name)
231{
232	char		buf[DEV_NAME_LEN];
233	struct stat	sb;
234
235	if (strlen(name) > 5 && !strncmp(name, _PATH_DEV, sizeof _PATH_DEV - 1)) {
236		snprintf(buf, sizeof buf, "%s", name);
237	} else {
238		if (strlen(name) == 2)
239			sprintf(buf, "%s%s", _PATH_TTY, name);
240		else
241			sprintf(buf, "%s%s", _PATH_DEV, name);
242	}
243
244	if (*name == '\0' || stat(buf, &sb) < 0)
245		fatal(EX_DATAERR, "bad device name");
246
247	if ((sb.st_mode & S_IFMT) != S_IFCHR)
248		fatal(EX_DATAERR, "must be a character device");
249
250	strncpy(dev_name, buf, DEV_NAME_LEN);
251
252	attach_snp();
253}
254
255void
256ask_dev(char *dbuf, const char *msg)
257{
258	char		buf[DEV_NAME_LEN];
259	int		len;
260
261	clear();
262	unset_tty();
263
264	if (msg)
265		printf("%s\n", msg);
266	if (dbuf)
267		printf("Enter device name [%s]:", dbuf);
268	else
269		printf("Enter device name:");
270
271	if (fgets(buf, DEV_NAME_LEN - 1, stdin)) {
272		len = strlen(buf);
273		if (buf[len - 1] == '\n')
274			buf[len - 1] = '\0';
275		if (buf[0] != '\0' && buf[0] != ' ')
276			strcpy(dbuf, buf);
277	}
278	set_tty();
279}
280
281#define READB_LEN	5
282
283int
284main(int ac, char *av[])
285{
286	int		ch, res, rv, nread;
287	size_t		b_size = MIN_SIZE;
288	char		*buf, chb[READB_LEN];
289	fd_set		fd_s;
290
291	(void) setlocale(LC_TIME, "");
292
293	if (isatty(std_out))
294		opt_interactive = 1;
295	else
296		opt_interactive = 0;
297
298	while ((ch = getopt(ac, av, "Wciotnf:")) != -1)
299		switch (ch) {
300		case 'W':
301			opt_write = 1;
302			break;
303		case 'c':
304			opt_reconn_close = 1;
305			break;
306		case 'i':
307			opt_interactive = 1;
308			break;
309		case 'o':
310			opt_reconn_oflow = 1;
311			break;
312		case 't':
313			opt_timestamp = 1;
314			break;
315		case 'n':
316			opt_no_switch = 1;
317			break;
318		case 'f':
319			opt_snpdev = optarg;
320			break;
321		case '?':
322		default:
323			usage();
324		}
325
326	tcgetattr(std_in, &otty);
327
328	if (modfind("snp") == -1)
329		if (kldload("snp") == -1 || modfind("snp") == -1)
330			warn("snp module not available");
331
332	signal(SIGINT, cleanup);
333
334	snp_io = open_snp();
335	setup_scr();
336
337	if (*(av += optind) == NULL) {
338		if (opt_interactive && !opt_no_switch)
339			ask_dev(dev_name, MSG_INIT);
340		else
341			fatal(EX_DATAERR, "no device name given");
342	} else
343		strncpy(dev_name, *av, DEV_NAME_LEN);
344
345	set_dev(dev_name);
346
347	if (!(buf = (char *) malloc(b_size)))
348		fatal(EX_UNAVAILABLE, "malloc failed");
349
350	FD_ZERO(&fd_s);
351
352	for (;;) {
353		if (opt_interactive)
354			FD_SET(std_in, &fd_s);
355		FD_SET(snp_io, &fd_s);
356		res = select(snp_io + 1, &fd_s, NULL, NULL, NULL);
357		if (opt_interactive && FD_ISSET(std_in, &fd_s)) {
358
359			if ((res = ioctl(std_in, FIONREAD, &nread)) != 0)
360				fatal(EX_OSERR, "ioctl(FIONREAD)");
361			if (nread > READB_LEN)
362				nread = READB_LEN;
363			rv = read(std_in, chb, nread);
364			if (rv == -1 || rv != nread)
365				fatal(EX_IOERR, "read (stdin) failed");
366
367			switch (chb[0]) {
368			case CHR_CLEAR:
369				clear();
370				break;
371			case CHR_SWITCH:
372				if (!opt_no_switch) {
373					detach_snp();
374					ask_dev(dev_name, MSG_CHANGE);
375					set_dev(dev_name);
376					break;
377				}
378			default:
379				if (opt_write) {
380					rv = write(snp_io, chb, nread);
381					if (rv == -1 || rv != nread) {
382						detach_snp();
383						if (opt_no_switch)
384							fatal(EX_IOERR,
385							    "write failed");
386						ask_dev(dev_name, MSG_NOWRITE);
387						set_dev(dev_name);
388					}
389				}
390
391			}
392		}
393		if (!FD_ISSET(snp_io, &fd_s))
394			continue;
395
396		if ((res = ioctl(snp_io, FIONREAD, &nread)) != 0)
397			fatal(EX_OSERR, "ioctl(FIONREAD)");
398
399		switch (nread) {
400		case SNP_OFLOW:
401			if (opt_reconn_oflow)
402				attach_snp();
403			else if (opt_interactive && !opt_no_switch) {
404				ask_dev(dev_name, MSG_OFLOW);
405				set_dev(dev_name);
406			} else
407				cleanup(-1);
408			break;
409		case SNP_DETACH:
410		case SNP_TTYCLOSE:
411			if (opt_reconn_close)
412				attach_snp();
413			else if (opt_interactive && !opt_no_switch) {
414				ask_dev(dev_name, MSG_CLOSED);
415				set_dev(dev_name);
416			} else
417				cleanup(-1);
418			break;
419		default:
420			if (nread < (b_size / 2) && (b_size / 2) > MIN_SIZE) {
421				free(buf);
422				if (!(buf = (char *) malloc(b_size / 2)))
423					fatal(EX_UNAVAILABLE, "malloc failed");
424				b_size = b_size / 2;
425			}
426			if (nread > b_size) {
427				b_size = (nread % 2) ? (nread + 1) : (nread);
428				free(buf);
429				if (!(buf = (char *) malloc(b_size)))
430					fatal(EX_UNAVAILABLE, "malloc failed");
431			}
432			rv = read(snp_io, buf, nread);
433			if (rv == -1 || rv != nread)
434				fatal(EX_IOERR, "read failed");
435			rv = write(std_out, buf, nread);
436			if (rv == -1 || rv != nread)
437				fatal(EX_IOERR, "write failed");
438		}
439	}			/* While */
440	return(0);
441}
442