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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-12-06 15:41:30 +01:00
parent 22072e477c
commit 197c5767ea
2 changed files with 37 additions and 22 deletions
+22
View File
@@ -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.
+15 -22
View File
@@ -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]);
}
/*