Commit 0b44313
Eric Bower
·
2026-07-30 11:41:38 -0400 EDT
parent 6171338
docs: daemonize
1 files changed,
+35,
-14
+35,
-14
1@@ -118,30 +118,51 @@ pub fn spawnPty(sesh_name: []const u8, cmd: Cmd) !PtyInfo {
2 }
3
4 /// daemonize is the first fork in a double-fork technique to create a
5-/// completely disconnected session (group of processes).
6+/// completely disconnected session (container of process groups).
7 ///
8 /// When launching a daemon, you normally set the child process of the fork to
9-/// be the session leader via setsid() which creates a new session that does
10-/// *not* have a controlling terminal. This is important because we don't want
11-/// a controlling terminal for our daemon or else our daemon could receive
12-/// signals to shutdown when the controlling terminal closes.
13+/// be the session leader via setsid() which creates a new session that removes
14+/// the current controlling terminal but creates the authority to grab a new
15+/// one. This is important because we don't want a controlling terminal for our
16+/// daemon or else our daemon could receive signals to shutdown when the
17+/// controlling terminal closes.
18 ///
19 /// However, if the first fork's child process is also the daemon process, then
20-/// it's technically possible for the daemon to open a terminal device
21-/// (e.g. open("/dev/console", O_RDWR)) and then it would acquire a controlling
22+/// it's technically possible for the daemon to open a terminal device (e.g.
23+/// open("/dev/console", O_RDWR)) and then it would acquire a controlling
24 /// terminal! A controlling terminal would expose the daemon to
25 /// terminal-generated signals (e.g. SIGINT) or SIGHUP from terminal disconnect
26 /// which could kill the daemon.
27 ///
28-/// By forking a second time, the grandchild process (the daemon) is not the
29-/// session leader. Per POSIX, only a process that is the session leader can
30-/// acquire a controlling terminal.
31+/// The first fork isn't arbitrary either: setsid() fails with EPERM if the
32+/// caller is already a process group leader, which a process launched directly
33+/// from a shell typically is. The first fork produces a child guaranteed not
34+/// to be a group leader, so setsid() will succeed. By forking a second time,
35+/// the grandchild process (the daemon) is not the session leader. Per POSIX,
36+/// only a process that is the session leader can acquire a controlling terminal.
37 ///
38-/// Apparently this is considered "being paranoid" but appears to be a standard
39-/// practice for daemons so we're doing it anyway.
40+/// Apparently this is "a bit paranoid," and on Linux it is arguable since a
41+/// session leader only acquires a controlling terminal under
42+/// implementation-defined conditions. But the double-fork is the portable way
43+/// to guarantee the daemon can never acquire one, regardless of how a given
44+/// POSIX implementation behaves. So we baked it into zmx.
45 ///
46-/// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_03
47-/// https://stackoverflow.com/a/16317668
48+/// PID=42 SID=10 PGID=10 ← original (PG leader, has tty)
49+/// │
50+/// │ fork #1
51+/// ├──────────┐
52+/// │ exit │ PID=55 SID=10 PGID=10 (not a PG leader)
53+/// ✝ │
54+/// │ setsid()
55+/// ▼
56+/// PID=55 SID=55 PGID=55 (session leader, no tty)
57+/// │
58+/// │ fork #2
59+/// ├──────────┐
60+/// │ exit │ PID=73 SID=55 PGID=55
61+/// ✝ │ PID≠SID → can't get a tty
62+/// ▼
63+/// DAEMON ✓
64 pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo {
65 // creates the daemon
66 const pid = try lib_posix.fork();