On 2024-07-23 17:46, Petter Reinholdtsen wrote:
Is this related to the issue reported with patch in <URL: https://mail.gnu.org/archive/html/sysvinit-devel/2017-07/msg00000.html >?
The patch seems to be almost perfect, once adjusted for some things having been moved around since it was written. I'm attaching an updated version of bootlogd.c. Sven, would you please give it a test run and see if this works? If so then I'll get it committed and will plan to publish a new version of SysV init soon.
Jesse
/* * 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 miqu...@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 * */ #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 <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <getopt.h> #include <dirent.h> #include <fcntl.h> #ifdef __linux__ #include <pty.h> #endif #if defined (__linux__) || defined(__GNU__) #include <sys/sysmacros.h> #endif #ifdef __FreeBSD__ #include <termios.h> #include <libutil.h> #endif #include <ctype.h> #ifdef __linux__ #include <sys/mount.h> #endif #include "bootlogd.h" #define MAX_CONSOLES 16 #define KERNEL_COMMAND_LENGTH 4096 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 real_cons { char name[1024]; int fd; }; /* * 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; } /* * chdir with error message on fail. */ static int chdir_int(const char *path) { int ret; if ((ret = chdir(path)) != 0) { const char *msgprefix = "bootlogd: %s"; char msg[PATH_MAX + sizeof(msgprefix)]; snprintf(msg, sizeof(msg), msgprefix, path); perror(msg); } return ret; } /* * 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_int(startdir) < 0 || (dir = opendir(".")) == NULL) { chdir_int(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) { closedir(dir); chdir_int(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_int(olddir); return -1; } else { snprintf(res, rlen, "%s/%s", startdir, ent->d_name); closedir(dir); chdir_int(olddir); return 0; } } } closedir(dir); chdir_int(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; } static int istty(const char *dev) { int fd, ret; fd = open(dev, O_RDONLY|O_NONBLOCK); if (fd < 0) return 0; ret = isatty(fd); close(fd); return ret; } /* * 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) 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; } } } /* Fallback: accept any TTY device */ snprintf(res, rlen, "/dev/%s", s); if ((q = strchr(res, ',')) != NULL) *q = 0; if (istty(res)) return 1; return 0; } /* * Find out the _real_ console(s). Assume that stdin is connected to * the console device (/dev/console). */ int consolenames(struct real_cons *cons, int max_consoles) { #ifdef TIOCGDEV unsigned int kdev; #endif struct stat st, st2; char buf[KERNEL_COMMAND_LENGTH]; char *p; int didmount = 0; int n; int fd; int considx, num_consoles = 0; #ifdef __linux__ /* * Read /proc/cmdline. */ stat("/", &st); if (stat("/proc", &st2) < 0) { perror("bootlogd: /proc"); return 0; } if (st.st_dev == st2.st_dev) { if (mount("proc", "/proc", "proc", 0, NULL) < 0) { perror("bootlogd: mount /proc"); return -1; } didmount = 1; } n = -1; if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) { perror("bootlogd: /proc/cmdline"); } else { buf[0] = 0; if ((n = read(fd, buf, KERNEL_COMMAND_LENGTH - 1)) < 0) perror("bootlogd: /proc/cmdline"); close(fd); } if (didmount) umount("/proc"); if (n < 0) return 0; /* * OK, so find console= in /proc/cmdline. * Parse in reverse, opening as we go. */ p = buf + n; *p-- = 0; while (p >= buf) { if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { *p-- = 0; continue; } if (strncmp(p, "console=", 8) == 0 && isconsole(p + 8, cons[num_consoles].name, sizeof(cons[num_consoles].name))) { /* * Suppress duplicates */ for (considx = 0; considx < num_consoles; considx++) { if (!strcmp(cons[num_consoles].name, cons[considx].name)) { goto dontuse; } } num_consoles++; if (num_consoles >= max_consoles) { break; } } dontuse: p--; } if (num_consoles > 0) return num_consoles; #endif fstat(0, &st); if (major(st.st_rdev) != 5 || minor(st.st_rdev) != 1) { /* * Old kernel, can find real device easily. */ int r = findtty(cons[num_consoles].name, "/dev", sizeof(cons[num_consoles].name), st.st_rdev); if (!r) num_consoles++; } if (num_consoles > 0) return num_consoles; #ifdef TIOCGDEV # ifndef ENOIOCTLCMD # define ENOIOCTLCMD 515 # endif if (ioctl(0, TIOCGDEV, &kdev) == 0) { int r = findtty(cons[num_consoles].name, "/dev", sizeof(cons[num_consoles].name), (dev_t)kdev); if (!r) num_consoles++; } if (num_consoles > 0) return num_consoles; #endif /* * Okay, no console on the command line - * guess the default console. */ for (n = 0; defcons[n]; n++) if (isconsole(defcons[n], cons[0].name, sizeof(cons[0].name))) return 1; fprintf(stderr, "bootlogd: cannot deduce real console device\n"); return 0; } /* * Write data and make sure it's on disk. */ void writelog(FILE *fp, unsigned char *ptr, int len, int print_escape_characters) { int dosync = 0; int i; static int first_run = 1; static int inside_esc = 0; for (i = 0; i < len; i++) { int ignore = 0; /* prepend date to every line */ if (*(ptr-1) == '\n' || first_run) { time_t t; char *s; time(&t); s = ctime(&t); fprintf(fp, "%.24s: ", s); dosync = 1; first_run = 0; } /* remove escape sequences, but do it in a way that allows us to stop * in the middle in case the string was cut off */ if (! print_escape_characters) { if (inside_esc == 1) { /* first '[' is special because if we encounter it again, it should be considered the final byte */ if (*ptr == '[') { /* multi char sequence */ ignore = 1; inside_esc = 2; } else { /* single char sequence */ if (*ptr >= 64 && *ptr <= 95) { ignore = 1; } inside_esc = 0; } } else if (inside_esc == 2) { switch (*ptr) { case '0' ... '9': /* intermediate chars of escape sequence */ case ';': case 32 ... 47: if (inside_esc) { ignore = 1; } break; case 64 ... 126: /* final char of escape sequence */ if (inside_esc) { ignore = 1; inside_esc = 0; } break; } } else { switch (*ptr) { case '\r': ignore = 1; break; case 27: /* ESC */ ignore = 1; inside_esc = 1; break; } } } /* end of if we should filter escape characters */ if (!ignore) { fwrite(ptr, sizeof(char), 1, fp); } ptr++; } if (dosync) { fflush(fp); if (syncalot) { fdatasync(fileno(fp)); } } outptr += len; if (outptr >= endptr) outptr = ringbuf; } /* * Print usage message and exit. */ void usage(void) { fprintf(stderr, "Usage: bootlogd [-v] [-r] [-d] [-e] [-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 *p; char *logfile; char *pidfile; int rotate; int dontfork; int ptm, pts; /* int realfd; -- this is now unused */ int n, m, i; int todo; #ifndef __linux__ /* BSD-style ioctl needs an argument. */ int on = 1; #endif int prev_eio = 0; int considx; struct real_cons cons[MAX_CONSOLES]; int num_consoles, consoles_left; int print_escape_sequence = 0; fp = NULL; logfile = LOGFILE; pidfile = NULL; rotate = 0; dontfork = 0; while ((i = getopt(argc, argv, "cdesl:p:rv")) != EOF) switch(i) { case 'l': logfile = optarg; break; case 'r': rotate = 1; break; case 'v': printf("bootlogd - %s\n", VERSION); exit(0); break; case 'p': pidfile = optarg; break; case 'c': createlogfile = 1; break; case 'd': dontfork = 1; break; case 'e': print_escape_sequence = 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", realcons, strerror(errno)); return 1; } */ if ((num_consoles = consolenames(cons, MAX_CONSOLES)) <= 0) return 1; consoles_left = num_consoles; for (considx = 0; considx < num_consoles; considx++) { if (strcmp(cons[considx].name, "/dev/tty0") == 0) strcpy(cons[considx].name, "/dev/tty1"); if (strcmp(cons[considx].name, "/dev/vc/0") == 0) strcpy(cons[considx].name, "/dev/vc/1"); if ((cons[considx].fd = open_nb(cons[considx].name)) < 0) { fprintf(stderr, "bootlogd: %s: %s\n", cons[considx].name, strerror(errno)); consoles_left--; } } if (!consoles_left) 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; } #ifdef __linux__ (void)ioctl(0, TIOCCONS, NULL); /* 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 #ifdef __linux__ if (ioctl(pts, TIOCCONS, NULL) < 0) #else /* BSD usage of ioctl TIOCCONS. */ if (ioctl(pts, TIOCCONS, &on) < 0) #endif { 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 devices. */ for (considx = 0; considx < num_consoles; considx++) { if (cons[considx].fd < 0) continue; m = n; p = inptr; while (m > 0) { i = write(cons[considx].fd, p, m); if (i >= 0) { m -= i; p += i; prev_eio = 0; continue; } /* Don't try to write the same data * again if we got EIO twice */ if ( (errno == EIO) && (prev_eio) ) { m = 0; } prev_eio = (errno == EIO); /* * Handle EIO (somebody hung * up our filedescriptor) */ cons[considx].fd = write_err(pts, cons[considx].fd, cons[considx].name, errno); if (cons[considx].fd >= 0) continue; /* * If this was the last console, * generate a fake signal */ if (--consoles_left <= 0) got_signal = 1; break; } /* end of while */ } /* end of going through all consoles */ /* * 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; } /* end of got data from read */ } /* end of checking select for new data */ /* * 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, print_escape_sequence); } /* end of while waiting for signal */ if (fp) { if (!didnl) fputc('\n', fp); fclose(fp); } close(pts); close(ptm); for (considx = 0; considx < num_consoles; considx++) { close(cons[considx].fd); } return 0; }