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 #define _XOPEN_SOURCE 600 /* for inclusion of sa_handler in Solaris */
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <string.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <fcntl.h>
22 #include <time.h>
23 #include <errno.h>
24 #ifdef HAVE_SIGNAL_H
25 #include <signal.h>
26 #endif
27 #ifdef HAVE_GETOPT_H
28 #include <getopt.h>
29 #else
30 extern char *optarg;
31 extern int optind;
32 #endif
33 
34 static int	outfd = -1;
35 static int	outbufsize = 0;
36 static void	*outbuf = 0;
37 static int	verbose = 0;
38 static int	do_skip = 0;
39 static int	skip_mode = 0;
40 static pid_t	child_pid = -1;
41 
usage(char * progname)42 static void usage(char *progname)
43 {
44 	printf("Usage: %s [-asv] logfile program\n", progname);
45 	exit(1);
46 }
47 
48 #define SEND_LOG	0x01
49 #define SEND_CONSOLE	0x02
50 #define SEND_BOTH	0x03
51 
52 /*
53  * Helper function that does the right thing if write returns a
54  * partial write, or an EGAIN/EINTR error.
55  */
write_all(int fd,const char * buf,size_t count)56 static int write_all(int fd, const char *buf, size_t count)
57 {
58 	ssize_t ret;
59 	int c = 0;
60 
61 	while (count > 0) {
62 		ret = write(fd, buf, count);
63 		if (ret < 0) {
64 			if ((errno == EAGAIN) || (errno == EINTR))
65 				continue;
66 			return -1;
67 		}
68 		count -= ret;
69 		buf += ret;
70 		c += ret;
71 	}
72 	return c;
73 }
74 
send_output(const char * buffer,int c,int flag)75 static void send_output(const char *buffer, int c, int flag)
76 {
77 	const char	*cp;
78 	char		*n;
79 	int		cnt, d, del;
80 
81 	if (c == 0)
82 		c = strlen(buffer);
83 
84 	if (flag & SEND_CONSOLE) {
85 		cnt = c;
86 		cp = buffer;
87 		while (cnt) {
88 			del = 0;
89 			for (d=0; d < cnt; d++) {
90 				if (skip_mode &&
91 				    (cp[d] == '\001' || cp[d] == '\002')) {
92 					del = 1;
93 					break;
94 				}
95 			}
96 			write_all(1, cp, d);
97 			if (del)
98 				d++;
99 			cnt -= d;
100 			cp += d;
101 		}
102 	}
103 	if (!(flag & SEND_LOG))
104 		return;
105 	if (outfd > 0)
106 		write_all(outfd, buffer, c);
107 	else {
108 		n = realloc(outbuf, outbufsize + c);
109 		if (n) {
110 			outbuf = n;
111 			memcpy(((char *)outbuf)+outbufsize, buffer, c);
112 			outbufsize += c;
113 		}
114 	}
115 }
116 
do_read(int fd)117 static int do_read(int fd)
118 {
119 	int	c;
120 	char	buffer[4096], *cp, *sep;
121 
122 	c = read(fd, buffer, sizeof(buffer)-1);
123 	if (c <= 0)
124 		return c;
125 	if (do_skip) {
126 		send_output(buffer, c, SEND_CONSOLE);
127 		buffer[c] = 0;
128 		cp = buffer;
129 		while (*cp) {
130 			if (skip_mode) {
131 				cp = strchr(cp, '\002');
132 				if (!cp)
133 					return 0;
134 				cp++;
135 				skip_mode = 0;
136 				continue;
137 			}
138 			sep = strchr(cp, '\001');
139 			if (sep)
140 				*sep = 0;
141 			send_output(cp, 0, SEND_LOG);
142 			if (sep) {
143 				cp = sep + 1;
144 				skip_mode = 1;
145 			} else
146 				break;
147 		}
148 	} else
149 		send_output(buffer, c, SEND_BOTH);
150 	return c;
151 }
152 
signal_term(int sig)153 static void signal_term(int sig)
154 {
155 	if (child_pid > 0)
156 		kill(child_pid, sig);
157 }
158 
run_program(char ** argv)159 static int run_program(char **argv)
160 {
161 	int	fds[2];
162 	int	status, rc, pid;
163 	char	buffer[80];
164 #ifdef HAVE_SIGNAL_H
165 	struct sigaction	sa;
166 #endif
167 
168 	if (pipe(fds) < 0) {
169 		perror("pipe");
170 		exit(1);
171 	}
172 
173 #ifdef HAVE_SIGNAL_H
174 	memset(&sa, 0, sizeof(struct sigaction));
175 	sa.sa_handler = signal_term;
176 	sigaction(SIGINT, &sa, 0);
177 	sigaction(SIGTERM, &sa, 0);
178 #ifdef SA_RESTART
179 	sa.sa_flags = SA_RESTART;
180 #endif
181 #endif
182 
183 	pid = fork();
184 	if (pid < 0) {
185 		perror("vfork");
186 		exit(1);
187 	}
188 	if (pid == 0) {
189 		dup2(fds[1],1);		/* fds[1] replaces stdout */
190 		dup2(fds[1],2);  	/* fds[1] replaces stderr */
191 		close(fds[0]);	/* don't need this here */
192 		close(fds[1]);
193 
194 		execvp(argv[0], argv);
195 		perror(argv[0]);
196 		exit(1);
197 	}
198 	child_pid = pid;
199 	close(fds[1]);
200 
201 	while (!(waitpid(pid, &status, WNOHANG ))) {
202 		do_read(fds[0]);
203 	}
204 	child_pid = -1;
205 	do_read(fds[0]);
206 	close(fds[0]);
207 
208 	if ( WIFEXITED(status) ) {
209 		rc = WEXITSTATUS(status);
210 		if (rc) {
211 			send_output(argv[0], 0, SEND_BOTH);
212 			sprintf(buffer, " died with exit status %d\n", rc);
213 			send_output(buffer, 0, SEND_BOTH);
214 		}
215 	} else {
216 		if (WIFSIGNALED(status)) {
217 			send_output(argv[0], 0, SEND_BOTH);
218 			sprintf(buffer, "died with signal %d\n",
219 				WTERMSIG(status));
220 			send_output(buffer, 0, SEND_BOTH);
221 			rc = 1;
222 		}
223 		rc = 0;
224 	}
225 	return rc;
226 }
227 
copy_from_stdin(void)228 static int copy_from_stdin(void)
229 {
230 	int	c, bad_read = 0;
231 
232 	while (1) {
233 		c = do_read(0);
234 		if ((c == 0 ) ||
235 		    ((c < 0) && ((errno == EAGAIN) || (errno == EINTR)))) {
236 			if (bad_read++ > 3)
237 				break;
238 			continue;
239 		}
240 		if (c < 0) {
241 			perror("read");
242 			exit(1);
243 		}
244 		bad_read = 0;
245 	}
246 	return 0;
247 }
248 
249 
250 
main(int argc,char ** argv)251 int main(int argc, char **argv)
252 {
253 	int	c, pid, rc;
254 	char	*outfn, **cpp;
255 	int	openflags = O_CREAT|O_WRONLY|O_TRUNC;
256 	int	send_flag = SEND_LOG;
257 	int	do_stdin;
258 	time_t	t;
259 
260 	while ((c = getopt(argc, argv, "+asv")) != EOF) {
261 		switch (c) {
262 		case 'a':
263 			openflags &= ~O_TRUNC;
264 			openflags |= O_APPEND;
265 			break;
266 		case 's':
267 			do_skip = 1;
268 			break;
269 		case 'v':
270 			verbose++;
271 			send_flag |= SEND_CONSOLE;
272 			break;
273 		}
274 	}
275 	if (optind == argc || optind+1 == argc)
276 		usage(argv[0]);
277 	outfn = argv[optind];
278 	optind++;
279 	argv += optind;
280 	argc -= optind;
281 
282 	outfd = open(outfn, openflags, 0644);
283 	do_stdin = !strcmp(argv[0], "-");
284 
285 	send_output("Log of ", 0, send_flag);
286 	if (do_stdin)
287 		send_output("stdin", 0, send_flag);
288 	else {
289 		for (cpp = argv; *cpp; cpp++) {
290 			send_output(*cpp, 0, send_flag);
291 			send_output(" ", 0, send_flag);
292 		}
293 	}
294 	send_output("\n", 0, send_flag);
295 	t = time(0);
296 	send_output(ctime(&t), 0, send_flag);
297 	send_output("\n", 0, send_flag);
298 
299 	if (do_stdin)
300 		rc = copy_from_stdin();
301 	else
302 		rc = run_program(argv);
303 
304 	send_output("\n", 0, send_flag);
305 	t = time(0);
306 	send_output(ctime(&t), 0, send_flag);
307 	send_output("----------------\n", 0, send_flag);
308 
309 	if (outbuf) {
310 		pid = fork();
311 		if (pid < 0) {
312 			perror("fork");
313 			exit(1);
314 		}
315 		if (pid) {
316 			if (verbose)
317 				printf("Backgrounding to save %s later\n",
318 				       outfn);
319 			exit(rc);
320 		}
321 		setsid();	/* To avoid getting killed by init */
322 		while (outfd < 0) {
323 			outfd = open(outfn, openflags, 0644);
324 			sleep(1);
325 		}
326 		write_all(outfd, outbuf, outbufsize);
327 		free(outbuf);
328 	}
329 	if (outfd >= 0)
330 		close(outfd);
331 
332 	exit(rc);
333 }
334