1/*
2 * logsave.c --- A program which saves the output of a program until
3 *	/var/log is mounted.
4 *
5 * Copyright (C) 2003 Theodore Ts'o.
6 *
7 * %Begin-Header%
8 * This file may be redistributed under the terms of the GNU Public
9 * License.
10 * %End-Header%
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <unistd.h>
16#include <string.h>
17#include <sys/types.h>
18#include <sys/wait.h>
19#include <fcntl.h>
20#include <time.h>
21#include <errno.h>
22#ifdef HAVE_GETOPT_H
23#include <getopt.h>
24#else
25extern char *optarg;
26extern int optind;
27#endif
28
29int	outfd = -1;
30int	outbufsize = 0;
31void	*outbuf = 0;
32int	verbose = 0;
33int	do_skip = 0;
34int	skip_mode = 0;
35
36static void usage(char *progname)
37{
38	printf("Usage: %s [-v] [-d dir] logfile program\n", progname);
39	exit(1);
40}
41
42#define SEND_LOG	0x01
43#define SEND_CONSOLE	0x02
44#define SEND_BOTH	0x03
45
46static void send_output(const char *buffer, int c, int flag)
47{
48	char	*n;
49
50	if (c == 0)
51		c = strlen(buffer);
52
53	if (flag & SEND_CONSOLE)
54		write(1, buffer, c);
55	if (!(flag & SEND_LOG))
56		return;
57	if (outfd > 0)
58		write(outfd, buffer, c);
59	else {
60		n = realloc(outbuf, outbufsize + c);
61		if (n) {
62			outbuf = n;
63			memcpy(((char *)outbuf)+outbufsize, buffer, c);
64			outbufsize += c;
65		}
66	}
67}
68
69static int do_read(int fd)
70{
71	int	c;
72	char	buffer[4096], *cp, *sep;
73
74	c = read(fd, buffer, sizeof(buffer)-1);
75	if (c <= 0)
76		return c;
77	if (do_skip) {
78		send_output(buffer, c, SEND_CONSOLE);
79		buffer[c] = 0;
80		cp = buffer;
81		while (*cp) {
82			if (skip_mode) {
83				cp = strchr(cp, '\002');
84				if (!cp)
85					return 0;
86				cp++;
87				skip_mode = 0;
88				continue;
89			}
90			sep = strchr(cp, '\001');
91			if (sep)
92				*sep = 0;
93			send_output(cp, 0, SEND_LOG);
94			if (sep) {
95				cp = sep + 1;
96				skip_mode = 1;
97			} else
98				break;
99		}
100	} else
101		send_output(buffer, c, SEND_BOTH);
102	return c;
103}
104
105static int run_program(char **argv)
106{
107	int	fds[2];
108	int	status, rc, pid;
109	char	buffer[80];
110
111	if (pipe(fds) < 0) {
112		perror("pipe");
113		exit(1);
114	}
115
116	pid = fork();
117	if (pid < 0) {
118		perror("vfork");
119		exit(1);
120	}
121	if (pid == 0) {
122		dup2(fds[1],1);		/* fds[1] replaces stdout */
123		dup2(fds[1],2);  	/* fds[1] replaces stderr */
124		close(fds[0]);	/* don't need this here */
125
126		execvp(argv[0], argv);
127		perror(argv[0]);
128		exit(1);
129	}
130	close(fds[1]);
131
132	while (!(waitpid(pid, &status, WNOHANG ))) {
133		do_read(fds[0]);
134	}
135	do_read(fds[0]);
136	close(fds[0]);
137
138	if ( WIFEXITED(status) ) {
139		rc = WEXITSTATUS(status);
140		if (rc) {
141			send_output(argv[0], 0, SEND_BOTH);
142			sprintf(buffer, " died with exit status %d\n", rc);
143			send_output(buffer, 0, SEND_BOTH);
144		}
145	} else {
146		if (WIFSIGNALED(status)) {
147			send_output(argv[0], 0, SEND_BOTH);
148			sprintf(buffer, "died with signal %d\n",
149				WTERMSIG(status));
150			send_output(buffer, 0, SEND_BOTH);
151			rc = 1;
152		}
153		rc = 0;
154	}
155	return rc;
156}
157
158static int copy_from_stdin(void)
159{
160	int	c, bad_read = 0;
161
162	while (1) {
163		c = do_read(0);
164		if ((c == 0 ) ||
165		    ((c < 0) && ((errno == EAGAIN) || (errno == EINTR)))) {
166			if (bad_read++ > 3)
167				break;
168			continue;
169		}
170		if (c < 0) {
171			perror("read");
172			exit(1);
173		}
174		bad_read = 0;
175	}
176	return 0;
177}
178
179
180
181int main(int argc, char **argv)
182{
183	int	c, pid, rc;
184	char	*outfn, **cpp;
185	int	openflags = O_CREAT|O_WRONLY|O_TRUNC;
186	int	send_flag = SEND_LOG;
187	int	do_stdin;
188	time_t	t;
189
190	while ((c = getopt(argc, argv, "+asv")) != EOF) {
191		switch (c) {
192		case 'a':
193			openflags &= ~O_TRUNC;
194			openflags |= O_APPEND;
195			break;
196		case 's':
197			do_skip = 1;
198			break;
199		case 'v':
200			verbose++;
201			send_flag |= SEND_CONSOLE;
202			break;
203		}
204	}
205	if (optind == argc || optind+1 == argc)
206		usage(argv[0]);
207	outfn = argv[optind];
208	optind++;
209	argv += optind;
210	argc -= optind;
211
212	outfd = open(outfn, openflags, 0644);
213	do_stdin = !strcmp(argv[0], "-");
214
215	send_output("Log of ", 0, send_flag);
216	if (do_stdin)
217		send_output("stdin", 0, send_flag);
218	else {
219		for (cpp = argv; *cpp; cpp++) {
220			send_output(*cpp, 0, send_flag);
221			send_output(" ", 0, send_flag);
222		}
223	}
224	send_output("\n", 0, send_flag);
225	t = time(0);
226	send_output(ctime(&t), 0, send_flag);
227	send_output("\n", 0, send_flag);
228
229	if (do_stdin)
230		rc = copy_from_stdin();
231	else
232		rc = run_program(argv);
233
234	send_output("\n", 0, send_flag);
235	t = time(0);
236	send_output(ctime(&t), 0, send_flag);
237	send_output("----------------\n", 0, send_flag);
238
239	if (outbuf) {
240		pid = fork();
241		if (pid < 0) {
242			perror("fork");
243			exit(1);
244		}
245		if (pid) {
246			if (verbose)
247				printf("Backgrounding to save %s later\n",
248				       outfn);
249			exit(rc);
250		}
251		setsid();	/* To avoid getting killed by init */
252		while (outfd < 0) {
253			outfd = open(outfn, openflags, 0644);
254			sleep(1);
255		}
256		write(outfd, outbuf, outbufsize);
257		free(outbuf);
258	}
259	close(outfd);
260
261	exit(rc);
262}
263