2

man 7 daemon

When a traditional SysV daemon starts, it should execute the following steps as part of the initialization. Note that these steps are unnecessary for new-style daemons (see below), and should only be implemented if compatibility with SysV is essential.

[...]

6. In the child, call setsid() to detach from any terminal and create an independent session.

7. In the child, call fork() again, to ensure that the daemon can never re-acquire a terminal again.

but compare this to processes started with no vestige of SysV compatibility:

$ ps -efj
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
root         1     0     1     1  0 May10 ?        00:06:44 /sbin/init
...
root       185     1   185   185  0 May10 ?        00:09:48 /lib/systemd/systemd-journald
root     16434     1 16434 16434  0 May26 ?        00:00:11 /usr/sbin/rsyslogd -n

The processes for both rsyslog.service and systemd-journal.service are session leaders (SID = PID).

It seems that if such programs were configured to log to a TTY, they would gain the TTY as a controlling terminal, and receive an unwanted / fatal signal when the TTY is hung up / receives Ctrl+C, respectively. Unless they remember to set O_NOCTTY when opening the TTY file.

It seems this is a little pitfall when writing or converting a program to run as a systemd service w/o any SysV compatibility, if your program supports writing messages to custom files. It does not seem to be pointed out by this doc which advocates the systemd style. The doc rather implies the opposite, by insisting that double-fork is necessary to avoid this on SysV, and then not mentioning this as an issue when describing the steps a native systemd service would use.

Is that correct? Does systemd provide some protection against this I have overlooked, or is the issue pointed out elsewhere in the systemd doc?

sourcejedi
  • 50,249
  • "It seems if they were configured to log to a TTY" ... configured how? – muru Jun 01 '18 at 09:26
  • @muru e.g. https://mikebeach.org/2011/05/28/ubuntu-real-time-syslog-logging-to-unused-console-tty/. – sourcejedi Jun 01 '18 at 10:08
  • Going by that and the answer, this seems to be rather unrelated to the sysv guide quoted above, which is about protecting services that could have accidentally inherited a controlling terminal as opposed to services that deliberately open a TTY themselves. These would have to take those precautions anyway. – muru Jun 01 '18 at 10:41
  • 1
    @muru I have bolded the specific clause. – sourcejedi Jun 01 '18 at 12:19

2 Answers2

2

Does systemd provide some protection against this […]?

You are assuming that it should. On the contrary, consider settings like TTYPath and services like getty@.service. The ability to gain a controlling terminal is actually necessary, in order that service management can encompass TTY login services, which need to do precisely that.

What actually protects against it is the move away from automatic allocation of a controlling terminal at open(), and discarding the old semantics. Or would protect against it. It is not the case on Linux, but on FreeBSD, NetBSD, OpenBSD, and Hurd nowadays the O_NOCTTY flag to open() is entirely superfluous. The only way to acquire a controlling terminal is by explicitly demanding it, with ioctl(…TIOSCTTY). This has actually been the case for approaching a quarter of a century, since the days of 4.4BSD.

In the meantime, the habit to get into on Linux is the habit that has also been the case for a long time, long before systemd: O_NOCTTY everywhere. ☺

(Yes, the GNU and musl C libraries do not give this to you for fopen(). This is one of several reasons that fdopen() is still a useful mechanism.)

Service management with the nosh toolset's service-manager takes a slightly different tack on this. Rather than always make dæmon processes into session leaders, each service being allocated its own kernel session object that then sees no use, only specific services also chain through setsid explicitly; such as ttylogin@* services that use open-controlling-tty, agetty@* services where of course agetty is setting the controlling terminal, and getty@* services. (As noted in the service source, mgetty calls setsid() itself.)

Further reading

JdeBP
  • 68,745
  • Technically, I did not assume that it should when I wrote the question. The assumption is more that the documentation implies that it should. I could be reading the documentation wrong though, or have written the question wrong, or something. – sourcejedi Jun 01 '18 at 13:29
  • I did consider getty@.service. The documentation around TTYPath includes specific mention of controlling tty. Maybe there's an implication there when you're not using TTYPath, but it didn't feel explicit enough to rule anything out. Actually, if you define your service in terms of controlling a TTY, it seems plausible that systemd would automatically inhibit detaching from a tty (setsid)... if systemd had included such code in the first place. – sourcejedi Jun 01 '18 at 13:31
  • I guess it might be technically possible that the getty shirks the systemd-provided ctty though, before opening the device by itself. The stock systemd getty services tend to confuse me, as they both use TTYPath, and pass the name of the device to the getty. It definitely expects the getty to open the device itself. – sourcejedi Jun 01 '18 at 14:06
  • I see, BSD stopped the automatic allocation of a controlling terminal a few years after the creation of Linux (1994-ish v.s. 1991-ish), and Linux didn't import the change. – sourcejedi Jun 01 '18 at 14:21
1

systemd does not protect service programs against acquiring a controlling terminal. They have to protect themselves when opening user-specified log files, by using the O_NOCTTY flag.

$ rpm -q systemd
systemd-238-8.git0e0aa59.fc28.x86_64

$ systemctl cat test
# /etc/systemd/system/test.service
[Service]
Type=simple
ExecStart=/bin/sh -c "exec cat </dev/tty10 >/dev/tty10"

$ systemctl status test
● test.service
   Loaded: loaded (/etc/systemd/system/test.service; static; vendor preset: disabled)
   Active: active (running) since Fri 2018-06-01 11:28:41 BST; 1min 35s ago
 Main PID: 12173 (cat)
    Tasks: 1 (limit: 4915)
   Memory: 180.0K
   CGroup: /system.slice/test.service
           └─12173 cat

Jun 01 11:28:41 alan-laptop systemd[1]: Started test.service.

$ ps -ejf
UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
...
root     12173     1 12173 12173  0 11:28 tty10    00:00:00 cat

I also confirmed that switching to tty10 and pressing Ctrl+C stops the cat process.

sourcejedi
  • 50,249