diff options
-rw-r--r-- | bootlogd.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/bootlogd.c b/bootlogd.c new file mode 100644 index 0000000..570d382 --- /dev/null +++ b/bootlogd.c @@ -0,0 +1,690 @@ +/* + * bootlogd.c Store output from the console during bootup into a file. + * The file is usually located on the /var partition, and + * gets written (and fsynced) as soon as possible. + * + * Version: @(#)bootlogd 2.86pre 12-Jan-2004 miquels@cistron.nl + * + * Bugs: Uses openpty(), only available in glibc. Sorry. + * + * This file is part of the sysvinit suite, + * Copyright (C) 1991-2004 Miquel van Smoorenburg. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * *NOTE* *NOTE* *NOTE* + * This is a PROOF OF CONCEPT IMPLEMENTATION + * + * I have bigger plans for Debian, but for now + * this has to do ;) + * + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <time.h> +#include <stdio.h> +#include <errno.h> +#include <malloc.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <getopt.h> +#include <dirent.h> +#include <fcntl.h> +#include <pty.h> +#include <ctype.h> +#ifdef __linux__ +#include <sys/mount.h> +#endif + +char *Version = "@(#) bootlogd 2.86 03-Jun-2004 miquels@cistron.nl"; + +#define LOGFILE "/var/log/boot" + +char ringbuf[32768]; +char *endptr = ringbuf + sizeof(ringbuf); +char *inptr = ringbuf; +char *outptr = ringbuf; + +int got_signal = 0; +int didnl = 1; +int createlogfile = 0; +int syncalot = 0; + +struct line { + char buf[256]; + int pos; +} line; + +/* + * Console devices as listed on the kernel command line and + * the mapping to actual devices in /dev + */ +struct consdev { + char *cmdline; + char *dev1; + char *dev2; +} consdev[] = { + { "ttyB", "/dev/ttyB%s", NULL }, + { "ttySC", "/dev/ttySC%s", "/dev/ttsc/%s" }, + { "ttyS", "/dev/ttyS%s", "/dev/tts/%s" }, + { "tty", "/dev/tty%s", "/dev/vc/%s" }, + { "hvc", "/dev/hvc%s", "/dev/hvc/%s" }, + { NULL, NULL, NULL }, +}; + +/* + * Devices to try as console if not found on kernel command line. + * Tried from left to right (as opposed to kernel cmdline). + */ +char *defcons[] = { "tty0", "hvc0", "ttyS0", "ttySC0", "ttyB0", NULL }; + +/* + * Catch signals. + */ +void handler(int sig) +{ + got_signal = sig; +} + + +/* + * Scan /dev and find the device name. + */ +static int findtty(char *res, const char *startdir, int rlen, dev_t dev) +{ + DIR *dir; + struct dirent *ent; + struct stat st; + int r = -1; + char *olddir = getcwd(NULL, 0); + + if (chdir(startdir) < 0 || (dir = opendir(".")) == NULL) { + int msglen = strlen(startdir) + 11; + char *msg = malloc(msglen); + snprintf(msg, msglen, "bootlogd: %s", startdir); + perror(msg); + free(msg); + chdir(olddir); + return -1; + } + while ((ent = readdir(dir)) != NULL) { + if (lstat(ent->d_name, &st) != 0) + continue; + if (S_ISDIR(st.st_mode) + && 0 != strcmp(".", ent->d_name) + && 0 != strcmp("..", ent->d_name)) { + char *path = malloc(rlen); + snprintf(path, rlen, "%s/%s", startdir, ent->d_name); + r = findtty(res, path, rlen, dev); + free(path); + if (0 == r) { /* device found, return */ + closedir(dir); + chdir(olddir); + return 0; + } + continue; + } + if (!S_ISCHR(st.st_mode)) + continue; + if (st.st_rdev == dev) { + if ( (int) (strlen(ent->d_name) + strlen(startdir) + 1) >= rlen) { + fprintf(stderr, "bootlogd: console device name too long\n"); + closedir(dir); + chdir(olddir); + return -1; + } else { + snprintf(res, rlen, "%s/%s", startdir, ent->d_name); + closedir(dir); + chdir(olddir); + return 0; + } + } + } + closedir(dir); + + chdir(olddir); + return r; +} + +/* + * For some reason, openpty() in glibc sometimes doesn't + * work at boot-time. It must be a bug with old-style pty + * names, as new-style (/dev/pts) is not available at that + * point. So, we find a pty/tty pair ourself if openpty() + * fails for whatever reason. + */ +int findpty(int *master, int *slave, char *name) +{ + char pty[16]; + char tty[16]; + int i, j; + int found; + + if (openpty(master, slave, name, NULL, NULL) >= 0) + return 0; + + found = 0; + + for (i = 'p'; i <= 'z'; i++) { + for (j = '0'; j <= 'f'; j++) { + if (j == '9' + 1) j = 'a'; + sprintf(pty, "/dev/pty%c%c", i, j); + sprintf(tty, "/dev/tty%c%c", i, j); + if ((*master = open(pty, O_RDWR|O_NOCTTY)) >= 0) { + *slave = open(tty, O_RDWR|O_NOCTTY); + if (*slave >= 0) { + found = 1; + break; + } + } + } + if (found) break; + } + if (!found) return -1; + + if (name) strcpy(name, tty); + + return 0; +} +/* + * See if a console taken from the kernel command line maps + * to a character device we know about, and if we can open it. + */ +int isconsole(char *s, char *res, int rlen) +{ + struct consdev *c; + int l, sl, i, fd; + char *p, *q; + + sl = strlen(s); + + for (c = consdev; c->cmdline; c++) { + l = strlen(c->cmdline); + if (sl <= l) continue; + p = s + l; + if (strncmp(s, c->cmdline, l) != 0 || !isdigit(*p)) + continue; + for (i = 0; i < 2; i++) { + snprintf(res, rlen, i ? c->dev1 : c->dev2, p); + if ((q = strchr(res, ',')) != NULL) *q = 0; + if ((fd = open(res, O_RDONLY|O_NONBLOCK)) >= 0) { + close(fd); + return 1; + } + } + } + return 0; +} + +/* + * Find out the _real_ console. Assume that stdin is connected to + * the console device (/dev/console). + */ +int consolename(char *res, int rlen) +{ +#ifdef TIOCGDEV + unsigned int kdev; +#endif + struct stat st, st2; + char buf[256]; + char *p; + int didmount = 0; + int n, r; + int fd; + + fstat(0, &st); + if (major(st.st_rdev) != 5 || minor(st.st_rdev) != 1) { + /* + * Old kernel, can find real device easily. + */ + int r = findtty(res, "/dev", rlen, st.st_rdev); + if (0 != r) + fprintf(stderr, "bootlogd: cannot find console device " + "%d:%d under /dev\n", major(st.st_rdev), minor(st.st_rdev)); + return r; + } + +#ifdef TIOCGDEV +# ifndef ENOIOCTLCMD +# define ENOIOCTLCMD 515 +# endif + if (ioctl(0, TIOCGDEV, &kdev) == 0) { + int r = findtty(res, "/dev", rlen, (dev_t)kdev); + if (0 != r) + fprintf(stderr, "bootlogd: cannot find console device " + "%d:%d under /dev\n", major(kdev), minor(kdev)); + return r; + } + if (errno != ENOIOCTLCMD) return -1; +#endif + +#ifdef __linux__ + /* + * Read /proc/cmdline. + */ + stat("/", &st); + if (stat("/proc", &st2) < 0) { + perror("bootlogd: /proc"); + return -1; + } + if (st.st_dev == st2.st_dev) { + if (mount("proc", "/proc", "proc", 0, NULL) < 0) { + perror("bootlogd: mount /proc"); + return -1; + } + didmount = 1; + } + + n = 0; + r = -1; + if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) { + perror("bootlogd: /proc/cmdline"); + } else { + buf[0] = 0; + if ((n = read(fd, buf, sizeof(buf) - 1)) >= 0) + r = 0; + else + perror("bootlogd: /proc/cmdline"); + close(fd); + } + if (didmount) umount("/proc"); + + if (r < 0) return r; + + /* + * OK, so find console= in /proc/cmdline. + * Parse in reverse, opening as we go. + */ + p = buf + n; + *p-- = 0; + r = -1; + while (p >= buf) { + if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { + *p-- = 0; + continue; + } + if (strncmp(p, "console=", 8) == 0 && + isconsole(p + 8, res, rlen)) { + r = 0; + break; + } + p--; + } + + if (r == 0) return r; +#endif + + /* + * Okay, no console on the command line - + * guess the default console. + */ + for (n = 0; defcons[n]; n++) + if (isconsole(defcons[n], res, rlen)) + return 0; + + fprintf(stderr, "bootlogd: cannot deduce real console device\n"); + + return -1; +} + + +/* + * Write data and make sure it's on disk. + */ +void writelog(FILE *fp, unsigned char *ptr, int len) +{ + time_t t; + char *s; + char tmp[8]; + int olen = len; + int dosync = 0; + int tlen; + + while (len > 0) { + tmp[0] = 0; + if (didnl) { + time(&t); + s = ctime(&t); + fprintf(fp, "%.24s: ", s); + didnl = 0; + } + switch (*ptr) { + case 27: /* ESC */ + strcpy(tmp, "^["); + break; + case '\r': + line.pos = 0; + break; + case 8: /* ^H */ + if (line.pos > 0) line.pos--; + break; + case '\n': + didnl = 1; + dosync = 1; + break; + case '\t': + line.pos += (line.pos / 8 + 1) * 8; + if (line.pos >= (int)sizeof(line.buf)) + line.pos = sizeof(line.buf) - 1; + break; + case 32 ... 127: + case 161 ... 255: + tmp[0] = *ptr; + tmp[1] = 0; + break; + default: + sprintf(tmp, "\\%03o", *ptr); + break; + } + ptr++; + len--; + + tlen = strlen(tmp); + if (tlen && (line.pos + tlen < (int)sizeof(line.buf))) { + memcpy(line.buf + line.pos, tmp, tlen); + line.pos += tlen; + } + if (didnl) { + fprintf(fp, "%s\n", line.buf); + memset(&line, 0, sizeof(line)); + } + } + + if (dosync) { + fflush(fp); + if (syncalot) { + fdatasync(fileno(fp)); + } + } + + outptr += olen; + if (outptr >= endptr) + outptr = ringbuf; + +} + + +/* + * Print usage message and exit. + */ +void usage(void) +{ + fprintf(stderr, "Usage: bootlogd [-v] [-r] [-d] [-s] [-c] [-p pidfile] [-l logfile]\n"); + exit(1); +} + +int open_nb(char *buf) +{ + int fd, n; + + if ((fd = open(buf, O_WRONLY|O_NONBLOCK|O_NOCTTY)) < 0) + return -1; + n = fcntl(fd, F_GETFL); + n &= ~(O_NONBLOCK); + fcntl(fd, F_SETFL, n); + + return fd; +} + +/* + * We got a write error on the real console. If its an EIO, + * somebody hung up our filedescriptor, so try to re-open it. + */ +int write_err(int pts, int realfd, char *realcons, int e) +{ + int fd; + + if (e != EIO) { +werr: + close(pts); + fprintf(stderr, "bootlogd: writing to console: %s\n", + strerror(e)); + return -1; + } + close(realfd); + if ((fd = open_nb(realcons)) < 0) + goto werr; + + return fd; +} + +int main(int argc, char **argv) +{ + FILE *fp; + struct timeval tv; + fd_set fds; + char buf[1024]; + char realcons[1024]; + char *p; + char *logfile; + char *pidfile; + int rotate; + int dontfork; + int ptm, pts; + int realfd; + int n, m, i; + int todo; + + fp = NULL; + logfile = LOGFILE; + pidfile = NULL; + rotate = 0; + dontfork = 0; + + while ((i = getopt(argc, argv, "cdsl:p:rv")) != EOF) switch(i) { + case 'l': + logfile = optarg; + break; + case 'r': + rotate = 1; + break; + case 'v': + printf("%s\n", Version); + exit(0); + break; + case 'p': + pidfile = optarg; + break; + case 'c': + createlogfile = 1; + break; + case 'd': + dontfork = 1; + break; + case 's': + syncalot = 1; + break; + default: + usage(); + break; + } + if (optind < argc) usage(); + + signal(SIGTERM, handler); + signal(SIGQUIT, handler); + signal(SIGINT, handler); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + + /* + * Open console device directly. + */ + if (consolename(realcons, sizeof(realcons)) < 0) + return 1; + + if (strcmp(realcons, "/dev/tty0") == 0) + strcpy(realcons, "/dev/tty1"); + if (strcmp(realcons, "/dev/vc/0") == 0) + strcpy(realcons, "/dev/vc/1"); + + if ((realfd = open_nb(realcons)) < 0) { + fprintf(stderr, "bootlogd: %s: %s\n", buf, strerror(errno)); + return 1; + } + + /* + * Grab a pty, and redirect console messages to it. + */ + ptm = -1; + pts = -1; + buf[0] = 0; + if (findpty(&ptm, &pts, buf) < 0) { + fprintf(stderr, + "bootlogd: cannot allocate pseudo tty: %s\n", + strerror(errno)); + return 1; + } + + (void)ioctl(0, TIOCCONS, NULL); +#if 1 + /* Work around bug in 2.1/2.2 kernels. Fixed in 2.2.13 and 2.3.18 */ + if ((n = open("/dev/tty0", O_RDWR)) >= 0) { + (void)ioctl(n, TIOCCONS, NULL); + close(n); + } +#endif + if (ioctl(pts, TIOCCONS, NULL) < 0) { + fprintf(stderr, "bootlogd: ioctl(%s, TIOCCONS): %s\n", + buf, strerror(errno)); + return 1; + } + + /* + * Fork and write pidfile if needed. + */ + if (!dontfork) { + pid_t child_pid = fork(); + switch (child_pid) { + case -1: /* I am parent and the attempt to create a child failed */ + fprintf(stderr, "bootlogd: fork failed: %s\n", + strerror(errno)); + exit(1); + break; + case 0: /* I am the child */ + break; + default: /* I am parent and got child's pid */ + exit(0); + break; + } + setsid(); + } + if (pidfile) { + unlink(pidfile); + if ((fp = fopen(pidfile, "w")) != NULL) { + fprintf(fp, "%d\n", (int)getpid()); + fclose(fp); + } + fp = NULL; + } + + /* + * Read the console messages from the pty, and write + * to the real console and the logfile. + */ + while (!got_signal) { + + /* + * We timeout after 5 seconds if we still need to + * open the logfile. There might be buffered messages + * we want to write. + */ + tv.tv_sec = 0; + tv.tv_usec = 500000; + FD_ZERO(&fds); + FD_SET(ptm, &fds); + if (select(ptm + 1, &fds, NULL, NULL, &tv) == 1) { + /* + * See how much space there is left, read. + */ + if ((n = read(ptm, inptr, endptr - inptr)) >= 0) { + /* + * Write data (in chunks if needed) + * to the real output device. + */ + m = n; + p = inptr; + while (m > 0) { + i = write(realfd, p, m); + if (i >= 0) { + m -= i; + p += i; + continue; + } + /* + * Handle EIO (somebody hung + * up our filedescriptor) + */ + realfd = write_err(pts, realfd, + realcons, errno); + if (realfd >= 0) continue; + got_signal = 1; /* Not really */ + break; + } + + /* + * Increment buffer position. Handle + * wraps, and also drag output pointer + * along if we cross it. + */ + inptr += n; + if (inptr - n < outptr && inptr > outptr) + outptr = inptr; + if (inptr >= endptr) + inptr = ringbuf; + if (outptr >= endptr) + outptr = ringbuf; + } + } + + /* + * Perhaps we need to open the logfile. + */ + if (fp == NULL && access(logfile, F_OK) == 0) { + if (rotate) { + snprintf(buf, sizeof(buf), "%s~", logfile); + rename(logfile, buf); + } + fp = fopen(logfile, "a"); + } + if (fp == NULL && createlogfile) + fp = fopen(logfile, "a"); + + if (inptr >= outptr) + todo = inptr - outptr; + else + todo = endptr - outptr; + if (fp && todo) + writelog(fp, (unsigned char *)outptr, todo); + } + + if (fp) { + if (!didnl) fputc('\n', fp); + fclose(fp); + } + + close(pts); + close(ptm); + close(realfd); + + return 0; +} + |