Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package blog for openSUSE:Factory checked in at 2026-07-17 18:47:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/blog (Old) and /work/SRC/openSUSE:Factory/.blog.new.24530 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "blog" Fri Jul 17 18:47:04 2026 rev:29 rq:1365970 version:2.47 Changes: -------- --- /work/SRC/openSUSE:Factory/blog/blog.changes 2026-07-10 17:35:11.792585430 +0200 +++ /work/SRC/openSUSE:Factory/.blog.new.24530/blog.changes 2026-07-17 18:47:16.020771938 +0200 @@ -1,0 +2,51 @@ +Wed Jul 15 12:29:23 UTC 2026 - Dr. Werner Fink <[email protected]> + +- Update to version 2.47 + This addresses several race conditions, file descriptor leaks, and + architectural issues regarding virtual terminal (VT) handling in blogd. + +* VT Encapsulation: Extracted all kernel-specific terminal ioctls and + macros (e.g., KD_GRAPHICS, KD_TEXT) into a dedicated vt.c module. + Added clean abstractions like vt_is_graphics(), vt_is_text(), and + vt_set_text_mode() to decouple blogd.c and console.c from kernel headers. +* List Management & FD Leaks: Fixed dynamic VT allocation in console.c. + By resetting the console pointer to NULL before consalloc() and + explicitly searching the doubly linked list for the target device ID + afterward, we ensure the exact newly allocated terminal node is referenced. + This fixes incorrect node assignments and prevents file descriptor leaks. +* Smart VT Switching: Optimized the password prompt logic. If the + currently active VT is a text console, the prompt is displayed there + directly without unnecessary context switches. If the active VT is + running a GUI (X11/Wayland), it safely switches to a newly allocated + text VT and returns afterward. +* EBUSY Race Condition: The child process now explicitly closes the + temporary VT file descriptor before initiating the vt_switch back to + the original console. This allows the parent process to safely perform + vt_disallocate without encountering EBUSY kernel errors. +* Timeout Handling: Replaced the infinite wait loop in request_tty() + (tty.c) with a safe 2000ms timeout via can_read(). This prevents the + daemon from hanging indefinitely on locked terminals and allowed the + removal of error-prone alarm() signal hacks. +* Lifecycle Management: Ensured newly allocated dynamic VTs are properly + initialized via consinitIO() and reliably deallocated upon daemon + exit (lcons_shutdown). + + Fix epoll EEXIST crash and prevent infinite waits on busy terminals + + This also addresses two critical edge cases that could cause the blogd + daemon to either crash or hang during concurrent or blocked prompt requests. + + Key changes: +* epoll.c: Prevent an `EEXIST` crash in `epoll_addition()`. If a file + descriptor is already registered in the watch list, the daemon now + gracefully updates it using `EPOLL_CTL_MOD` instead of blindly calling + `EPOLL_CTL_ADD`. This makes the socket handling robust against rapid, + overlapping client requests. +* console.c: Add a 2-second deadline (`alarm(2)`) around the `request_tty()` + call inside the password-prompt child process. Previously, if a terminal + was exclusively locked (e.g. by an X11 server returning EPERM), the child + would fall into an infinite inotify wait-loop. With the timeout, the child + now dies cleanly, allowing the parent's reaping logic to properly cancel + the prompt and send an ANSWER_ENQ (abort) to the waiting client. + +------------------------------------------------------------------- Old: ---- showconsole-2.46.tar.gz New: ---- showconsole-2.47.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ blog.spec ++++++ --- /var/tmp/diff_new_pack.ZfOAME/_old 2026-07-17 18:47:16.824799169 +0200 +++ /var/tmp/diff_new_pack.ZfOAME/_new 2026-07-17 18:47:16.828799305 +0200 @@ -17,7 +17,7 @@ Name: blog -Version: 2.46 +Version: 2.47 %define sonum 2 Release: 0 Summary: Boot logging ++++++ showconsole-2.46.tar.gz -> showconsole-2.47.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/.github/workflows/c-cpp.yml new/showconsole-2.47/.github/workflows/c-cpp.yml --- old/showconsole-2.46/.github/workflows/c-cpp.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/showconsole-2.47/.github/workflows/c-cpp.yml 2026-07-15 14:22:56.000000000 +0200 @@ -0,0 +1,37 @@ +name: openSUSE Tumbleweed CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + +jobs: + build-and-test: + runs-on: ubuntu-latest + container: + image: registry.opensuse.org/opensuse/tumbleweed:latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Refresh repositories and install dependencies + run: | + zypper --non-interactive refresh + zypper --non-interactive install -y \ + gcc make glibc-devel \ + rpm-config-SUSE \ + bash \ + coreutils \ + findutils + + - name: Run build or test scripts + run: | + echo "Running tasks inside openSUSE Tumbleweed..." + set -e + make + mkdir -p $GITHUB_WORKSPACE/buildroot + make install DESTDIR=$GITHUB_WORKSPACE/buildroot + find $GITHUB_WORKSPACE/buildroot diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/Makefile new/showconsole-2.47/Makefile --- old/showconsole-2.46/Makefile 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/Makefile 2026-07-15 14:22:56.000000000 +0200 @@ -15,7 +15,7 @@ DEBUG = DESTDIR = MAJOR := 2 -MINOR := 46 +MINOR := 47 VERSION := $(MAJOR).$(MINOR) DATE = $(shell date +'%d%b%y' | tr '[:lower:]' '[:upper:]') COPTS = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/README.md new/showconsole-2.47/README.md --- old/showconsole-2.46/README.md 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/README.md 2026-07-15 14:22:56.000000000 +0200 @@ -1,25 +1,54 @@ # showconsole -The package showconsole includes several helpers which had been developed -over the years for SUSE and openSUSE Linux. There are +The `showconsole` package provides a suite of lightweight system console and +boot logging utilities, originally developed over the years for SUSE and +openSUSE Linux. - * blogd - Does boot logging on /dev/console - * blogctl - Control the boot logging daemon blogd - * blogger - Writes messages to a running blogd process - * isserial - Determines if the underlying tty of stdin is a serial line - * showconsole - Determines the real tty of stdin - * setconsole - Redirect system console output to a tty - -Whereas blogd had been written for SysVinit based systems this newer version -also supports systemd based system. The blogd together with the new tool -blogctl can replace the well known plymouth but without any frame buffer and -splash support. Therefore blogd may used on workstations as well as on so -called big irons. - -The blogd causes systemd to show its boot messages on the system console to -collect them for repeating them on all devices used for the system console -as well as it writes out a copy to boot.log and boot.old below /var/log/. - -Beside boot messages and logging them the blogd can handle password or -passphrase requests send by password agents. For those requests the blogd -do ask on all terminal lines used for the system console. +## Included Tools + +* **`blogd`**: Does boot logging on `/dev/console`. + +* **`blogctl`**: Control the boot logging daemon `blogd`. + +* **`blogger`**: Writes messages to a running `blogd` process. + +* **`showconsole`**: Determines the real TTY of standard input. + +* **`setconsole`**: Redirect system console output to a TTY. + +* **`isserial`**: Determines if the underlying TTY of standard input is + a serial line. + +## The `blogd` Boot Logging Daemon + +Whereas `blogd` had been written for SysVinit based systems, this newer +version also supports systemd based systems. + +The `blogd` together with the new tool `blogctl` can replace the well-known +plymouth but without any frame buffer and splash support. Therefore, `blogd` +may be used on workstations as well as on so-called big irons. + +### Key Features & Purpose + +* **Boot Message Multiplexing:** The `blogd` causes systemd to show its boot + messages on the system console to collect them for repeating them on all + devices used for the system console. + +* **Persistent Logging:** It writes out a copy to `boot.log` and `boot.old` + below `/var/log/`. + +* **Advanced Password Handling:** Beside boot messages and logging them the + `blogd` can handle password or passphrase requests send by password + agents. For those requests the `blogd` do ask on all terminal lines + used for the system console. + +* **Smart Virtual Terminal (VT) Switching:** It features robust, dynamic + Virtual Terminal management. If the system is running a graphical + environment (X11/Wayland) that locks the active terminal, `blogd` + smartly allocates a free text terminal, safely switches the display + to prompt the user, and seamlessly switches back to the graphical + session once complete. + +* **Context-Aware Prompts:** If the user is already actively viewing a text + console, the prompt is seamlessly integrated directly into the active + text stream without unnecessary screen switching or visual disruption. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/blogctl.8 new/showconsole-2.47/blogctl.8 --- old/showconsole-2.46/blogctl.8 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/blogctl.8 2026-07-15 14:22:56.000000000 +0200 @@ -113,6 +113,9 @@ .I /var/log/boot.old as well as mask it own program name in the process table with the @ character. +.TP +.B help +Show a help text. .SH SEE ALSO .BR blogd (8), .BR systemd (1), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/blogctl.c new/showconsole-2.47/blogctl.c --- old/showconsole-2.46/blogctl.c 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/blogctl.c 2026-07-15 14:22:56.000000000 +0200 @@ -1,7 +1,8 @@ /* - * blogd.c + * blogctl.c * - * Copyright 2015 Werner Fink, 2015 SuSE Linux GmbH. + * Copyright 2015, 2026 Werner Fink, 2015 SuSE Linux GmbH, + * 2026 SUSE Software Solutions Germany GmbH * * This source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,6 +41,7 @@ return fd; } +#define MAGIC_HELP 0x19 static char getcmd(int argc, char *argv[]) { static const struct { @@ -50,16 +52,17 @@ } cmds[] = { { "root=", MAGIC_CHROOT, 1, NULL }, /* New root */ { "ping", MAGIC_PING, 0, NULL }, /* Ping */ - { "ask-for-password", MAGIC_ASK_PWD, 0, NULL }, - { "ask-question", MAGIC_QUESTION, 0, NULL }, - { "display-message", MAGIC_SHOW_MSG, 0, NULL }, - { "hide-message", MAGIC_HIDE_MSG, 0, NULL }, + { "ask-for-password", MAGIC_ASK_PWD, 0, NULL }, /* Ask for password */ + { "ask-question", MAGIC_QUESTION, 0, NULL }, /* Ask a question */ + { "display-message", MAGIC_SHOW_MSG, 0, NULL }, /* Display a message */ + { "hide-message", MAGIC_HIDE_MSG, 0, NULL }, /* Sorry, no backroll */ { "ready", MAGIC_SYS_INIT, 0, NULL }, /* System ready */ { "quit", MAGIC_QUIT, 0, NULL }, /* Quit */ { "final", MAGIC_FINAL, 0, NULL }, /* Final */ { "close", MAGIC_CLOSE, 0, NULL }, /* Close logging only */ { "deactivate", MAGIC_DEACTIVATE, 0, NULL }, /* Deactivate logging */ { "reactivate", MAGIC_REACTIVATE, 0, NULL }, /* Reactivate logging */ + { "help", MAGIC_HELP, 0, NULL }, /* End Of Medium aka Help */ {} }, *cmd = cmds; char ret = (char)-1; @@ -124,11 +127,12 @@ cmd[1] = '\0'; answer[0] = '\x15'; - fdsock = getsocket(); - if (fdsock < 0) - error("no blogd active"); - while ((cmd[0] = getcmd(argc, argv)) != (char)-1) { + if (cmd[0] != MAGIC_HELP && fdsock < 0) { + fdsock = getsocket(); + if (fdsock < 0) + error("no blogd active"); + } switch (cmd[0]) { case MAGIC_CHROOT: root = optarg; @@ -173,8 +177,9 @@ {0, 0, 0, 0} }; - /* * getopt_long_only parst ab argv[optind]. Da wir getcmd() - * benutzt haben, steht optind genau richtig auf der ersten Option. + /* + * The getopt_long_only function parses starting from argv[optind]. + * Since we used getcmd(), optind is pointing exactly at the first option. */ while ((c = getopt_long_only(argc, argv, "", long_options, NULL)) != -1) { switch (c) { @@ -290,6 +295,30 @@ break; } + case MAGIC_HELP: + printf("Usage: /sbin/blogctl [COMMAND] [OPTIONS]\n\n" + "Commands:\n" + " ping Check if blogd is active\n" + " quit [--wait] Gracefully terminate blogd\n" + " root=<path> Set new root file system path\n" + " ready Signal that file systems are writable\n" + " close Finish logging, allow systemd to unmount\n" + " ask-for-password Ask the user for a password\n" + " --prompt=STRING Message to display\n" + " --command=STRING Command to receive password via stdin\n" + " --number-of-tries=INT Number of retries\n" + " ask-question Ask the user a question\n" + " --prompt=STRING Message to display\n" + " --command=STRING Command to receive answer via stdin\n" + " display-message Display a status message\n" + " --text=STRING The message text\n" + " hide-message Hide a status message\n" + " deactivate Disconnect blogd from system console\n" + " reactivate Reconnect blogd to system console\n" + " final Rotate boot.log to boot.old\n" + " help Show this help text\n"); + answer[0] = '\x6'; + goto fail; case '?': default: goto fail; @@ -309,6 +338,8 @@ argv += optind; argc -= optind; + if (argc != 0) + printf("Usage: /sbin/blogctl help\n"); fail: if (fdsock >= 0) close(fdsock); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/blogd.c new/showconsole-2.47/blogd.c --- old/showconsole-2.46/blogd.c 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/blogd.c 2026-07-15 14:22:56.000000000 +0200 @@ -19,7 +19,6 @@ #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/vt.h> -#include <sys/kd.h> #include <time.h> #include <pty.h> #include <stdio.h> @@ -54,10 +53,12 @@ static void _initialize(void) __attribute__((__constructor__)); static void _initialize(void) { - const char *run; + char *run; run = realpath(varrun, NULL); if (run && *run) varrun = run; + else if (run) + free(run); } /* @@ -143,6 +144,7 @@ exe = proc2exe(pid); if (exe) { errno = EACCES; + free(exe); error("plymouth is active %ld", (long int)pid); } } @@ -291,13 +293,13 @@ parse_cmdline(); /* Parse the kernel command line for blog.<key>(=<val>) */ val = value_cmdline("silent"); if (val) { - if (strcmp(val, "1") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 || strcasecmp(val, "true") == 0) - console_silent = 1; + if (strcmp(val, "1") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 || strcasecmp(val, "true") == 0) + console_silent = 1; } val = value_cmdline("coldboot"); if (val) { - if (strcmp(val, "1") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 || strcasecmp(val, "true") == 0) - coldboot = 1; + if (strcmp(val, "1") == 0 || strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 || strcasecmp(val, "true") == 0) + coldboot = 1; } myname = program_invocation_short_name; @@ -478,9 +480,12 @@ cfsetispeed(&c->otio, ispeed); cfsetospeed(&c->otio, ospeed); } else { - if (major(c->dev) == 4 && minor(c->dev) <= 63) - ioctl(c->fd, KDSETMODE, KD_TEXT); /* Enforce text mode */ - +#if !defined(__s390__) && !defined(__s390x__) + if (major(c->dev) == 4 && minor(c->dev) <= 63) { + if (!vt_is_graphics(c->fd)) + vt_set_text_mode(c->fd); /* Enforce text mode */ + } +#endif c->otio.c_iflag |= (ICRNL | IXON); c->otio.c_iflag &= ~(INLCR | IGNCR | BRKINT); c->otio.c_oflag |= (ONLCR | OPOST); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/libconsole/console.c new/showconsole-2.47/libconsole/console.c --- old/showconsole-2.46/libconsole/console.c 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/libconsole/console.c 2026-07-15 14:22:56.000000000 +0200 @@ -171,11 +171,34 @@ * Console list */ list_t lcons = { &(lcons), &(lcons) }; +#if !defined(__s390__) && !defined(__s390x__) +static int active_vt_fd = -1; +static struct console *dyn_vt_cons = NULL; /* For dynamic allocated VT */ +static void epoll_vt_active_change(int fd); +#endif static __attribute__((destructor)) void lcons_shutdown(void) { struct console *c, *n; +#if !defined(__s390__) && !defined(__s390x__) + if (dyn_vt_cons) { + int old_vt = -1; + if (major(dyn_vt_cons->dev) == 4) + old_vt = minor(dyn_vt_cons->dev); + + if (old_vt > 0) { + int fd_tty0 = open("/dev/tty0", O_RDWR | O_CLOEXEC); + if (fd_tty0 >= 0) { + vt_disallocate(fd_tty0, old_vt); + close(fd_tty0); + } + } + /* Set pointer to NULL, the final free happens in the lcons loop below */ + dyn_vt_cons = NULL; + } +#endif + if (list_empty(&lcons)) return; @@ -607,6 +630,17 @@ fdsock = listen; /* We use only ONE socket ... see also safeout() */ fdread = input; +#if !defined(__s390__) && !defined(__s390x__) + if (vt_supported()) { + active_vt_fd = open("/sys/devices/virtual/tty/tty0/active", O_RDONLY|O_CLOEXEC); + if (active_vt_fd >= 0) { + epoll_addsysfs(active_vt_fd, &epoll_vt_active_change); + /* Trigger initially once to get the actual VT */ + epoll_vt_active_change(active_vt_fd); + } + } +#endif + /* Phase 1: Scan for pending systemd password queries */ scan_ask_directory("/run/systemd/ask-password"); @@ -893,7 +927,7 @@ fdsock = -1; } - epoll_close_fd(); + epoll_close_fd(-1); if (epfd >= 0) close(epfd); @@ -1097,8 +1131,10 @@ #ifdef TIOCGDEV fd = open("/dev/console", O_RDONLY|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); if (fd >= 0) { - if (ioctl (0, TIOCGDEV, &devnum) < 0) + if (ioctl (0, TIOCGDEV, &devnum) < 0) { + close(fd); goto fallback; + } close(fd); tty = chardev((dev_t)devnum); if (!tty) @@ -1121,6 +1157,82 @@ free(tty); } +#if !defined(__s390__) && !defined(__s390x__) +/* + * Be noted and handle change of active virtual console + */ +static void epoll_vt_active_change(int fd) +{ + char tty_name[32]; + char path[64]; + struct stat st; + + /* Rewind file descriptor before reading EPOLLPRI data */ + if (lseek(fd, 0, SEEK_SET) < 0) + return; + + if (read(fd, tty_name, sizeof(tty_name)-1) > 0) { + struct console *c; + dev_t target_dev; + int found = 0; + + tty_name[strcspn(tty_name, "\n")] = 0; /* remove newline */ + + /* Close old virtual console and remove it from lcons */ + if (dyn_vt_cons) { + struct console *n; + int old_vt = -1; + + if (major(dyn_vt_cons->dev) == 4) + old_vt = minor(dyn_vt_cons->dev); + + list_for_each_entry_safe(c, n, &lcons, node) { + if (c != dyn_vt_cons) + continue; + delete(&c->node); + if (c->fd >= 0) + close(c->fd); + free(c); + dyn_vt_cons = NULL; + break; + } + + if (old_vt > 0) { + int fd_tty0 = open("/dev/tty0", O_RDWR | O_CLOEXEC); + if (fd_tty0 >= 0) { + vt_disallocate(fd_tty0, old_vt); + close(fd_tty0); + } + } + } + + snprintf(path, sizeof(path), "/dev/%s", tty_name); + target_dev = (stat(path, &st) == 0 && S_ISCHR(st.st_mode)) ? st.st_rdev : makedev(4,0); + + list_for_each_entry(c, &lcons, node) { + if (c->dev == st.st_rdev) { + found++; + break; + } + } + + if (!found) { + struct console *d = NULL; + consalloc(&d, path, CON_CONSDEV, st.st_rdev, 0); + + list_for_each_entry(c, &lcons, node) { + if (c->dev == target_dev) { + dyn_vt_cons = c; + break; + } + } + if (dyn_vt_cons) + consinitIO(dyn_vt_cons); + } + } +} +#endif + /* * Do handle the console in data */ @@ -1370,12 +1482,38 @@ coldstart_next(); } } else { + int still_running = 0; + /* Clear Zombies */ list_for_each_entry(c, &lcons, node) { if (c->pid == info.si_pid) { c->pid = -1; } } + + /* Check, if there is any active child for this prompt */ + list_for_each_entry(c, &lcons, node) { + if (c->pid > 0) + still_running = 1; + } + + if (!still_running) { + /* All consoles have failed. cancel the prompt! */ + asking = 0; + if (coldstart_active && coldstart_socket_path) { + free(coldstart_socket_path); + coldstart_socket_path = NULL; + } + if (pwd_client_fd >= 0) { + const char *enq = ANSWER_ENQ; /* ENQ = send Cancel/Abort to blogctl */ + safeout(pwd_client_fd, enq, strlen(enq)+1, SSIZE_MAX); + epoll_delete(pwd_client_fd); + close(pwd_client_fd); + pwd_client_fd = -1; + } + if (coldstart_active) + coldstart_next(); + } } } @@ -1787,6 +1925,7 @@ { struct console *c; size_t len; + int any_running = 0; if (!pwprompt || !*pwprompt) return; @@ -1822,9 +1961,12 @@ struct console *d; char *message; int eightbit; - int len, fdc, tflags; + int len, fdc = -1, tflags; #if defined(__s390__) || defined(__s390x__) int vmcpfd = -1; +#else + int fd_tty0 = -1, orig_vt = -1, prompt_vt = -1; + char newtty[32]; #endif #ifndef NO_SIGNALFD sigset_t empty_mask; @@ -1840,16 +1982,18 @@ close(fdsock); if (flog) (void)fclose(flog); + if (pwd_client_fd >= 0) + close(pwd_client_fd); if (epfd) { - epoll_close_fd(); + epoll_close_fd(c->fd); /* We keep c->fd */ close(epfd); } FD_ZERO(&blocked); vc_reconnect = NULL; - dup2(1, 2); dup2(c->fd, 0); dup2(c->fd, 1); + dup2(c->fd, 2); currenttty = c->tty; /* Used in readpw() in case of an error */ list_for_each_entry(d, &lcons, node) @@ -1871,14 +2015,80 @@ set_signal(SIGSYS, NULL, SIG_DFL); set_signal(SIGQUIT, NULL, SIG_IGN); - fdc = request_tty(c->tty); +#if !defined(__s390__) && !defined(__s390x__) + if (major(c->dev) == 4 && vt_supported()) { + int blocked = 0; + /* Open the terminal without hanging or be blocked */ + int fdc_test = open(c->tty, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC); + + if (fdc_test >= 0) { + /* Is terminal locked by getty/login/shell ? */ + if (ioctl(fdc_test, TIOCSCTTY, 0) < 0 && errno == EPERM) + blocked = 1; + /* It the terminal in graphic mode (X11/Wayland)? */ + if (vt_is_graphics(fdc_test)) + blocked = 1; + + if (blocked) { + close(fdc_test); + } else { + /* Terminal is free! Use it and remove O_NONBLOCK */ + int flags = fcntl(fdc_test, F_GETFL); + fcntl(fdc_test, F_SETFL, flags & ~O_NONBLOCK); + fdc = fdc_test; + + /* Are we here? */ + fd_tty0 = open("/dev/tty0", O_RDWR | O_CLOEXEC); + if (fd_tty0 >= 0) { + orig_vt = vt_get_active(fd_tty0); + int target_vt = minor(c->dev); + + /* If the user is e.g. on tty2 (X11/Wayland), but the terminal at tty7 -> switch over! */ + if (orig_vt > 0 && target_vt > 0 && orig_vt != target_vt) { + vt_switch(fd_tty0, target_vt); + prompt_vt = target_vt; /* Remember: to find back to tty2 */ + } + } + } + } else { + blocked = 1; + } + + if (blocked) { + if (fd_tty0 < 0) + fd_tty0 = open("/dev/tty0", O_RDWR | O_CLOEXEC); + + if (fd_tty0 >= 0) { + orig_vt = vt_get_active(fd_tty0); + prompt_vt = vt_get_free(fd_tty0); + + if (prompt_vt > 0) { + vt_switch(fd_tty0, prompt_vt); + snprintf(newtty, sizeof(newtty), "/dev/tty%d", prompt_vt); + currenttty = newtty; + fdc = open(currenttty, O_RDWR | O_CLOEXEC); + if (fdc >= 0) + ioctl(fdc, TIOCSCTTY, 0); + } + } + } + } +#endif if (fdc < 0) + fdc = request_tty(c->tty); + if (fdc < 0) { +#if !defined(__s390__) && !defined(__s390x__) + if (fd_tty0 >= 0) + close(fd_tty0); +#endif _exit(1); + } - dup2(fdc, 0); - dup2(fdc, 1); - if (fdc > 1) + dup2(fdc, 0); /* stdin */ + dup2(fdc, 1); /* stdout */ + if (fdc > 1) { /* Intentional: let 0 and 1 open */ close(fdc); + } /* * Mandatory: Remove none blocking mode for reading @@ -1944,6 +2154,10 @@ #endif if (len < 0) { warn("can not set password prompt"); +#if !defined(__s390__) && !defined(__s390x__) + if (fd_tty0 >= 0) + close(fd_tty0); +#endif _exit(1); } /* @@ -2026,6 +2240,18 @@ warn("can not read password"); password = frobnicate(password, *pwsize); + +#if !defined(__s390__) && !defined(__s390x__) + if (orig_vt > 0 && prompt_vt > 0) { + if (fdc >= 0) { + close(fdc); + fdc = -1; + } + vt_switch(fd_tty0, orig_vt); + } + if (fd_tty0 >= 0) + close(fd_tty0); +#endif _exit(0); } @@ -2067,4 +2293,26 @@ } while (c->pid != -1 && errno == EINTR); } } + + /* Fallback: When we waited synchronously and everything failed immediately */ + list_for_each_entry(c, &lcons, node) { + if (c->pid > 0) + any_running = 1; + } + if (asking && !any_running) { + asking = 0; + if (coldstart_active && coldstart_socket_path) { + free(coldstart_socket_path); + coldstart_socket_path = NULL; + } + if (pwd_client_fd >= 0) { + const char *enq = ANSWER_ENQ; + safeout(pwd_client_fd, enq, strlen(enq)+1, SSIZE_MAX); + epoll_delete(pwd_client_fd); + close(pwd_client_fd); + pwd_client_fd = -1; + } + if (coldstart_active) + coldstart_next(); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/libconsole/epoll.c new/showconsole-2.47/libconsole/epoll.c --- old/showconsole-2.46/libconsole/epoll.c 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/libconsole/epoll.c 2026-07-15 14:22:56.000000000 +0200 @@ -40,6 +40,18 @@ ev.events = flags; + /* Prevent EEXIST crash: If fd is already registered, just modify it */ + list_for_each_entry(ep, &lpolls, watch) { + if (ep->fd == fd) { + ep->handle = (typeof(ep->handle))fptr; + ev.data.ptr = (void*)ep; + ret = epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev); + if (ret < 0) + error("can not modify %d file descriptor on epoll file descriptor", fd); + return; + } + } + if (posix_memalign((void**)&ep, sizeof(void*), align_up(struct epolls, void*)) != 0 || !ep) error("memory allocation"); @@ -66,6 +78,12 @@ epoll_addition(fd, fptr, EPOLLIN|EPOLLPRI|EPOLLRDHUP); } +void epoll_addsysfs(int fd, void *fptr) +{ + /* ONLY wait for exceptions (EPOLLPRI|EPOLLERR), never for EPOLLIN! */ + epoll_addition(fd, fptr, EPOLLPRI|EPOLLERR); +} + void epoll_addwrite(int fd, void *fptr) { epoll_addition(fd, fptr, EPOLLOUT|EPOLLONESHOT|EPOLLPRI|EPOLLERR); @@ -168,10 +186,12 @@ } /* Only usefull within forked sub processes */ -void epoll_close_fd(void) +void epoll_close_fd(int keep_fd) { struct epolls *ep; - list_for_each_entry(ep, &lpolls, watch) - close(ep->fd); + list_for_each_entry(ep, &lpolls, watch) { + if (ep->fd >= 0 && ep->fd != keep_fd) + close(ep->fd); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/libconsole/libconsole.h new/showconsole-2.47/libconsole/libconsole.h --- old/showconsole-2.46/libconsole/libconsole.h 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/libconsole/libconsole.h 2026-07-15 14:22:56.000000000 +0200 @@ -167,12 +167,13 @@ /* epoll.c */ extern void epoll_addread(int fd, void *fptr); +extern void epoll_addsysfs(int fd, void *fptr); extern void epoll_addwrite(int fd, void *fptr); extern void epoll_answer_once(int fd, void *fptr); extern void epoll_reenable(int fd); extern void epoll_delete(int fd); extern void (*epoll_handle(void *ptr, int *fd))(int); -extern void epoll_close_fd(void); +extern void epoll_close_fd(int keep_fd); /* frobnicate.c */ extern void *frobnicate(void *in, const size_t len); @@ -240,6 +241,17 @@ extern void parseterm(char *msg); extern void parsespool(char *msg); extern void warning3215(int fd); +#else +/* vt.c */ +extern int vt_supported(void); +extern int vt_is_graphics(int fd); +extern int vt_is_text(int fd); +extern int vt_get_mode(int fd); +extern int vt_set_text_mode(int fd); +extern int vt_get_free(int fd); +extern int vt_get_active(int fd); +extern int vt_switch(int fd, int vtno); +extern void vt_disallocate(int fd, int vtno); #endif #define MAX_PASSLEN LINE_MAX diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/libconsole/tty.c new/showconsole-2.47/libconsole/tty.c --- old/showconsole-2.46/libconsole/tty.c 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/libconsole/tty.c 2026-07-15 14:22:56.000000000 +0200 @@ -119,7 +119,7 @@ unsigned char buf[BUF_LEN]; ssize_t e; - if (!can_read(nd, -1)) + if (!can_read(nd, 2000)) break; len = read (nd, buf, BUF_LEN); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/libconsole/vmcp.c new/showconsole-2.47/libconsole/vmcp.c --- old/showconsole-2.46/libconsole/vmcp.c 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/libconsole/vmcp.c 2026-07-15 14:22:56.000000000 +0200 @@ -102,8 +102,11 @@ do { rc = read(fd, ret, buffersize); if (rc < 0) { - if (errno != EINTR) + if (errno != EINTR) { + free(ret); + ret = NULL; goto out; + } } } while (rc < 0); out: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/libconsole/vt.c new/showconsole-2.47/libconsole/vt.c --- old/showconsole-2.46/libconsole/vt.c 1970-01-01 01:00:00.000000000 +0100 +++ new/showconsole-2.47/libconsole/vt.c 2026-07-15 14:22:56.000000000 +0200 @@ -0,0 +1,99 @@ +/* + * vt.c - Virtual Terminal Management for blogd + * + * Copyright 2026 Werner Fink + * Copyright 2026 SUSE Software Solutions Germany GmbH + * + * This source 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. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <linux/vt.h> +#include <linux/kd.h> +#include "libconsole.h" + +#if !defined(__s390__) && !defined(__s390x__) +/* Check if the system supports VTs (e.g. not s390x) */ +int vt_supported(void) +{ + return (access("/sys/devices/virtual/tty/tty0/active", R_OK) == 0); +} + +/* Checks if the terminal is running within a graphical environment (X11/Wayland) */ +int vt_is_graphics(int fd) +{ + int mode = KD_TEXT; + if (ioctl(fd, KDGETMODE, &mode) < 0) + return 0; + return (mode == KD_GRAPHICS); +} + +/* Checks if the terminal is a pure text console (TTY) */ +int vt_is_text(int fd) +{ + int mode = KD_TEXT; + if (ioctl(fd, KDGETMODE, &mode) < 0) + return 0; + return (mode == KD_TEXT); +} + +/* Get current KD mode (KD_TEXT or KD_GRAPHICS) */ +int vt_get_mode(int fd) +{ + int mode = KD_TEXT; + if (ioctl(fd, KDGETMODE, &mode) < 0) + return -1; + return mode; +} + +/* Set KD mode to KD_TEXT */ +int vt_set_text_mode(int fd) +{ + int mode = KD_TEXT; + if (ioctl(fd, KDSETMODE, &mode) < 0) + return -1; + return mode; +} + +/* Find the first free Virtual Terminal */ +int vt_get_free(int fd) +{ + int vtno = -1; + if (ioctl(fd, VT_OPENQRY, &vtno) < 0) + return -1; + return vtno; +} + +/* Get currently active VT number */ +int vt_get_active(int fd) +{ + struct vt_stat vstate; + if (ioctl(fd, VT_GETSTATE, &vstate) < 0) + return -1; + return vstate.v_active; +} + +/* Switch to a specific VT and wait until the switch completes */ +int vt_switch(int fd, int vtno) +{ + if (ioctl(fd, VT_ACTIVATE, vtno) < 0) + return -1; + if (ioctl(fd, VT_WAITACTIVE, vtno) < 0) + return -1; + return 0; +} + +/* Deallocate a VT after use to free up kernel memory */ +void vt_disallocate(int fd, int vtno) +{ + ioctl(fd, VT_DISALLOCATE, vtno); +} +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/showconsole-2.46/listing.h new/showconsole-2.47/listing.h --- old/showconsole-2.46/listing.h 2026-07-09 15:29:51.000000000 +0200 +++ new/showconsole-2.47/listing.h 2026-07-15 14:22:56.000000000 +0200 @@ -102,6 +102,8 @@ #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) +#define list_last_entry(ptr, type, member) \ + list_entry((ptr)->prev, type, member) #define list_next_entry(pos, member) \ list_entry((pos)->member.next, typeof(*(pos)), member)
