From 197c5767ea3f27b92c8972c27e7f1f086c0199a1 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sat, 6 Dec 2025 15:39:24 +0100 Subject: [PATCH] Switch from PTY to pipe for service log redirection The PTY approach caused isatty() to return true for services using the log directive, triggering programs like fprintd (using glib) to emit ANSI escape codes and other TTY-specific formatting in syslog. Using a standard pipe ensures isatty() correctly returns false, so programs produce plain text output suitable for logging. For services that require line-buffered output, users can wrap the command with `stdbuf -oL` as documented in doc/config/logging.md. Fixes #455 Signed-off-by: Joachim Wiberg --- doc/config/logging.md | 22 ++++++++++++++++++++++ src/service.c | 37 +++++++++++++++---------------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/doc/config/logging.md b/doc/config/logging.md index 0da98e8a..5220a465 100644 --- a/doc/config/logging.md +++ b/doc/config/logging.md @@ -39,3 +39,25 @@ Log rotation is controlled using the global `log` setting. **Example:** service log:prio:user.warn,tag:ntpd /sbin/ntpd pool.ntp.org -- NTP daemon + +Output Buffering +---------------- + +When using the `log` directive, Finit redirects the service's stdout and +stderr to a pipe connected to a logger process. Programs detect this as +non-interactive output (i.e., `isatty()` returns false) and typically +switch from line-buffered to fully-buffered mode. + +Most well-behaved daemons explicitly flush their output or use syslog +directly, so this is rarely an issue. However, if a service's log +messages appear delayed or batched, you can force line-buffered output +by wrapping the command with `stdbuf`: + + service log /usr/bin/stdbuf -oL /path/to/command -- My service + +The `-oL` option forces line-buffered output, and `-o0` forces unbuffered +output. See `stdbuf(1)` for details. + +> [!NOTE] +> Using `stdbuf` is rarely necessary. Only use it if you observe actual +> buffering issues with a specific service. diff --git a/src/service.c b/src/service.c index 9d59c1ca..a161b6f0 100644 --- a/src/service.c +++ b/src/service.c @@ -271,25 +271,22 @@ static int lredirect(svc_t *svc) { static int have_sysklogd = -1; pid_t svc_pid = getpid(); + int pipefd[2]; pid_t pid; int fd; /* - * Open PTY to connect to logger. A pty isn't buffered - * like a pipe, and it eats newlines so they aren't logged + * Use a pipe to connect to logger. This ensures isatty() + * returns false for the service, preventing programs from + * emitting ANSI escape codes and other TTY-specific output. */ - fd = posix_openpt(O_RDWR); - if (fd == -1) { - dbg("Failed posix_openpt(), errno %d: %s", errno, strerror(errno)); - svc->log.enabled = 0; - return -1; - } - if (grantpt(fd) == -1 || unlockpt(fd) == -1) { - dbg("Failed grantpt()|unlockpt(), errno %d: %s", errno, strerror(errno)); - close(fd); + if (pipe(pipefd) == -1) { + dbg("Failed pipe(), errno %d: %s", errno, strerror(errno)); svc->log.enabled = 0; + return -1; } + fd = pipefd[1]; /* Write end for service */ /* * First time, check if we have sysklogd logger tool. @@ -319,17 +316,12 @@ static int lredirect(svc_t *svc) char *prio = "daemon.info"; char buf[MAX_IDENT_LEN]; char *tag; - int fds; sched_yield(); - fds = open(ptsname(fd), O_RDONLY); - close(fd); - if (fds == -1) { - logit(LOG_WARNING, "failed open() ptsname(%d), errno %d", fd, errno); - _exit(0); - } - dup2(fds, STDIN_FILENO); + close(pipefd[1]); /* Close write end in logger */ + dup2(pipefd[0], STDIN_FILENO); + close(pipefd[0]); /* Reset signals */ sig_unblock(); @@ -385,10 +377,11 @@ static int lredirect(svc_t *svc) _exit(1); } - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); + close(pipefd[0]); /* Close read end in service */ + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); - return close(fd); + return close(pipefd[1]); } /*