Subject: bug#78757: shepherd: dangling file descriptor to /dev/console

From: [email-masked]
Date: Sat, 14 Jun 2025 20:22:11 +0000

Hello,

Ludovic Courtès <[email-masked]> writes:

> Hi burban,
>
> burban--- via Bug reports for GNU Guix <[email-masked]> writes:
>
>> All shepherd children have an open file descriptor to /dev/console,
>> probably because of the use of dup2 (and not dup3) in system.scm.
>
> I checked the /proc/PID/fd directory of several of my system services on
> Guix System (nscd, guix-publish, ntpd, upower-daemon) and I don’t see
> any file descriptor pointing to /dev/console, with version 1.0.5.
>
> You said you’re on Debian; which version of the Shepherd is it?

Bookworm with my own shepherd package (1.0.0) and also Trixie with the
official shepherd package (1.0.3).

Without the CLOEXEC forcing below, I get this:

# lsof |grep shepherd |grep /dev/console
shepherd       1                         root    0u      CHR                5,1      0t0         12 /dev/console
shepherd       1                         root    1u      CHR                5,1      0t0         12 /dev/console
shepherd       1                         root    2u      CHR                5,1      0t0         12 /dev/console
shepherd       1                         root   19w      CHR                5,1      0t0         12 /dev/console
shepherd       1    183 shepherd         root    0u      CHR                5,1      0t0         12 /dev/console
shepherd       1    183 shepherd         root    1u      CHR                5,1      0t0         12 /dev/console
shepherd       1    183 shepherd         root    2u      CHR                5,1      0t0         12 /dev/console
shepherd       1    183 shepherd         root   19w      CHR                5,1      0t0         12 /dev/console
shepherd       1 251476 shepherd         root    0u      CHR                5,1      0t0         12 /dev/console
shepherd       1 251476 shepherd         root    1u      CHR                5,1      0t0         12 /dev/console
shepherd       1 251476 shepherd         root    2u      CHR                5,1      0t0         12 /dev/console
shepherd       1 251476 shepherd         root   19w      CHR                5,1      0t0         12 /dev/console

and fd 19 is duplicated for a lot of the children:

# lsof |grep 19w
systemd-u    211                         root   19w      CHR                5,1      0t0         12 /dev/console
getty        853                         root   19w      CHR                5,1      0t0         12 /dev/console
getty        856                         root   19w      CHR                5,1      0t0         12 /dev/console
getty        859                         root   19w      CHR                5,1      0t0         12 /dev/console
radvd        894                        radvd   19w      CHR                5,1      0t0         12 /dev/console
radvd        895                         root   19w      CHR                5,1      0t0         12 /dev/console
exim4       1001                  Debian-exim   19w      CHR                5,1      0t0         12 /dev/console
ntpd        1003                       ntpsec   19w      CHR                5,1      0t0         12 /dev/console
fwknopd     1005                         root   19w      CHR                5,1      0t0         12 /dev/console
dbus-daem   1006                   messagebus   19w      CHR                5,1      0t0         12 /dev/console
openvpn     1416                         root   19w      CHR                5,1      0t0         12 /dev/console
apache2   160524                         root   19w      CHR                5,1      0t0         12 /dev/console
php-fpm8. 160561                         root   19w      CHR                5,1      0t0         12 /dev/console
php-fpm8. 160562                     www-data   19w      CHR                5,1      0t0         12 /dev/console
php-fpm8. 160563                     www-data   19w      CHR                5,1      0t0         12 /dev/console
unbound   199459                      unbound   19w      CHR                5,1      0t0         12 /dev/console
postgres  214913                     postgres   19w      CHR                5,1      0t0         12 /dev/console
cron      230165                         root   19w      CHR                5,1      0t0         12 /dev/console
apache2   281804                     www-data   19w      CHR                5,1      0t0         12 /dev/console
apache2   281804 281805 apache2      www-data   19w      CHR                5,1      0t0         12 /dev/console

And here the boot log when forcing CLOEXEC:

2025-06-13T11:32:51.657288+02:00 tanis [    8.21    ] shepherd[1]: fcntl error for port #<output:soft-port 7f4045fb2980>
2025-06-13T11:32:51.657312+02:00 tanis [    8.21    ] shepherd[1]: FD_CLOEXEC flag set on port #<output: file /dev/console> (fd: 2)
2025-06-13T11:32:51.657336+02:00 tanis [    8.21    ] shepherd[1]: FD_CLOEXEC flag set on port #<output: file /dev/console> (fd: 1)
2025-06-13T11:32:51.657359+02:00 tanis [    8.22    ] shepherd[1]: fcntl error for port #<output: #{write pipe}# 24>
2025-06-13T11:32:51.657383+02:00 tanis [    8.22    ] shepherd[1]: FD_CLOEXEC flag set on port #<output: /proc/self/fd/1 /dev/console> (fd: 19)
2025-06-13T11:32:51.657408+02:00 tanis [    8.22    ] shepherd[1]: fcntl error for port #<output: #{write pipe}# 24>
2025-06-13T11:32:51.657432+02:00 tanis [    8.22    ] shepherd[1]: FD_CLOEXEC flag set on port #<input: file /dev/console> (fd: 0)
2025-06-13T11:32:51.657456+02:00 tanis [    8.234859] shepherd[1]: GNU Shepherd 1.0.3 (Guile 3.0.10, x86_64-pc-linux-gnu)
2025-06-13T11:32:51.657480+02:00 tanis [    8.234924] shepherd[1]: Starting service root...
2025-06-13T11:32:51.657503+02:00 tanis [    8.235127] shepherd[1]: Service root started.
 
> I also don’t see any ‘dup2’ call in ‘system.scm’; this takes place
> rather in ‘service.scm’.

Oops, yes of course.

>> The pb. disapears if I force that flag with that code before I define
>> my services (context: shepherd used as init system on Debian):
>> (port-for-each (lambda (x)
>>                  (catch #t
>> 		   (lambda ()
>> 		     (when (= (fcntl x F_GETFD) 0)
>> 		       (fcntl x F_SETFD FD_CLOEXEC)
>> 		       (format #t "FD_CLOEXEC flag set on port ~a (fd: ~a)\n" x (port->fdes x))))
>> 		   (lambda (keys . args) (format #t "fcntl error for port ~a\n" x)))
>> 		 ))
>
> ‘shepherd’ marks all previously-opened FDs as O_CLOEXEC upfront—see
> ‘mark-as-close-on-exec’ in ‘shepherd.scm’.  Those opened later are
> opened with O_CLOEXEC, except for user code that explicitly omits the
> O_CLOEXEC flag.

Hmmm.... I have at one point (before the above (port-for-each ...)

(define *s* (open-output-file "/proc/self/fd/1"))

to have an early log output to (on ordinary PC) /dev/tty1. But maybe /dev/tty1
is a synonym for /dev/console. Let's test that:

(fileno *s*) returns 19 indeed! Now all I have to do is (fcntl *s*
F_SETFD FD_CLOEXEC) after creating *s*.

So you can close that ticket.

> Thanks,
> Ludo’.

Regards.

-- 

Bernard