/* * logsave.c --- A program which saves the output of a program until * /var/log is mounted. * * Copyright (C) 2003 Theodore Ts'o. * * %Begin-Header% * This file may be redistributed under the terms of the GNU Public * License. * %End-Header% */ #define _XOPEN_SOURCE 600 /* for inclusion of sa_handler in Solaris */ #include "config.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <fcntl.h> #include <time.h> #include <errno.h> #ifdef HAVE_SIGNAL_H #include <signal.h> #endif #ifdef HAVE_GETOPT_H #include <getopt.h> #else extern char *optarg; extern int optind; #endif static int outfd = -1; static int outbufsize = 0; static void *outbuf = 0; static int verbose = 0; static int do_skip = 0; static int skip_mode = 0; static pid_t child_pid = -1; static void usage(char *progname) { printf("Usage: %s [-asv] logfile program\n", progname); exit(1); } #define SEND_LOG 0x01 #define SEND_CONSOLE 0x02 #define SEND_BOTH 0x03 /* * Helper function that does the right thing if write returns a * partial write, or an EAGAIN/EINTR error. */ static int write_all(int fd, const char *buf, size_t count) { ssize_t ret; int c = 0; while (count > 0) { ret = write(fd, buf, count); if (ret < 0) { if ((errno == EAGAIN) || (errno == EINTR)) continue; return -1; } count -= ret; buf += ret; c += ret; } return c; } static void send_output(const char *buffer, int c, int flag) { const char *cp; char *n; int cnt, d, del; if (c == 0) c = strlen(buffer); if (flag & SEND_CONSOLE) { cnt = c; cp = buffer; while (cnt) { del = 0; for (d=0; d < cnt; d++) { if (skip_mode && (cp[d] == '\001' || cp[d] == '\002')) { del = 1; break; } } write_all(1, cp, d); if (del) d++; cnt -= d; cp += d; } } if (!(flag & SEND_LOG)) return; if (outfd > 0) write_all(outfd, buffer, c); else { n = realloc(outbuf, outbufsize + c); if (n) { outbuf = n; memcpy(((char *)outbuf)+outbufsize, buffer, c); outbufsize += c; } } } static int do_read(int fd) { int c; char buffer[4096], *cp, *sep; c = read(fd, buffer, sizeof(buffer)-1); if (c <= 0) return c; if (do_skip) { send_output(buffer, c, SEND_CONSOLE); buffer[c] = 0; cp = buffer; while (*cp) { if (skip_mode) { cp = strchr(cp, '\002'); if (!cp) return 0; cp++; skip_mode = 0; continue; } sep = strchr(cp, '\001'); if (sep) *sep = 0; send_output(cp, 0, SEND_LOG); if (sep) { cp = sep + 1; skip_mode = 1; } else break; } } else send_output(buffer, c, SEND_BOTH); return c; } static void signal_term(int sig) { if (child_pid > 0) kill(child_pid, sig); } static int run_program(char **argv) { int fds[2]; int status, rc, pid; char buffer[80]; #ifdef HAVE_SIGNAL_H struct sigaction sa; #endif if (pipe(fds) < 0) { perror("pipe"); exit(1); } #ifdef HAVE_SIGNAL_H memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = signal_term; sigaction(SIGINT, &sa, 0); sigaction(SIGTERM, &sa, 0); #ifdef SA_RESTART sa.sa_flags = SA_RESTART; #endif #endif pid = fork(); if (pid < 0) { perror("vfork"); exit(1); } if (pid == 0) { dup2(fds[1],1); /* fds[1] replaces stdout */ dup2(fds[1],2); /* fds[1] replaces stderr */ close(fds[0]); /* don't need this here */ close(fds[1]); execvp(argv[0], argv); perror(argv[0]); exit(1); } child_pid = pid; close(fds[1]); while (!(waitpid(pid, &status, WNOHANG ))) { do_read(fds[0]); } child_pid = -1; do_read(fds[0]); close(fds[0]); if ( WIFEXITED(status) ) { rc = WEXITSTATUS(status); if (rc) { send_output(argv[0], 0, SEND_BOTH); sprintf(buffer, " exited with status code %d\n", rc); send_output(buffer, 0, SEND_BOTH); } } else { if (WIFSIGNALED(status)) { send_output(argv[0], 0, SEND_BOTH); sprintf(buffer, "died with signal %d\n", WTERMSIG(status)); send_output(buffer, 0, SEND_BOTH); return 1; } rc = 0; } return rc; } static int copy_from_stdin(void) { int c, bad_read = 0; while (1) { c = do_read(0); if ((c == 0 ) || ((c < 0) && ((errno == EAGAIN) || (errno == EINTR)))) { if (bad_read++ > 3) break; continue; } if (c < 0) { perror("read"); exit(1); } bad_read = 0; } return 0; } int main(int argc, char **argv) { int c, pid, rc; char *outfn, **cpp; int openflags = O_CREAT|O_WRONLY|O_TRUNC; int send_flag = SEND_LOG; int do_stdin; time_t t; while ((c = getopt(argc, argv, "+asv")) != EOF) { switch (c) { case 'a': openflags &= ~O_TRUNC; openflags |= O_APPEND; break; case 's': do_skip = 1; break; case 'v': verbose++; send_flag |= SEND_CONSOLE; break; } } if (optind == argc || optind+1 == argc) usage(argv[0]); outfn = argv[optind]; optind++; argv += optind; argc -= optind; outfd = open(outfn, openflags, 0644); do_stdin = !strcmp(argv[0], "-"); send_output("Log of ", 0, send_flag); if (do_stdin) send_output("stdin", 0, send_flag); else { for (cpp = argv; *cpp; cpp++) { send_output(*cpp, 0, send_flag); send_output(" ", 0, send_flag); } } send_output("\n", 0, send_flag); t = time(0); send_output(ctime(&t), 0, send_flag); send_output("\n", 0, send_flag); if (do_stdin) rc = copy_from_stdin(); else rc = run_program(argv); send_output("\n", 0, send_flag); t = time(0); send_output(ctime(&t), 0, send_flag); send_output("----------------\n", 0, send_flag); if (outbuf) { pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid) { if (verbose) printf("Backgrounding to save %s later\n", outfn); exit(rc); } setsid(); /* To avoid getting killed by init */ while (outfd < 0) { outfd = open(outfn, openflags, 0644); sleep(1); } write_all(outfd, outbuf, outbufsize); free(outbuf); } if (outfd >= 0) close(outfd); exit(rc); }