On 2024-07-11 05:44, Sven Reschke wrote:
I have a rented root server, where I have a yocto build running, which
utilizes sysvinit.
After logging in, the /var/log/boot file is empty and the process is
taking up to 95% of cpu usage. Also the process doesn't react on SIGINT.
After some debugging, I saw, that the process is trying to open the
/dev/ttyS0 device, but this fails everytime with an EIO.
There's no counter for a maximal retry of EIO errors, so the process is
stuck trying to open the not available serial console...
I guess the serial console shouldn't return EIO in the first place (at
least not the whole time), but on the other hand, in my humbled opinion,
the process should be able to handle this situation.
I am attaching an updated bootlogd.c source file which I think should
avoid the issue. The original, endless loop doesn't make sense to me. We
make one attempt to re-open the file, but if that doesn't work we should
probably assume it's not going to become available in the future. So
we'll just drop out of the function.
I'm open to thoughts or other ways to improve this situation. (Maybe a
maximum of 99 attempts with a sleep() call?)
- 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) {
close(pts);
fprintf(stderr, "bootlogd: writing to console: %s\n",
strerror(e));
return -1;
}
close(realfd);
if ((fd = open_nb(realcons)) < 0)
return -1;
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 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;
continue;
}
/*
* 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;
}