Commit 0b44313

Eric Bower  ·  2026-07-30 11:41:38 -0400 EDT
parent 6171338
docs: daemonize
1 files changed,  +35, -14
+35, -14
......@@ -118,30 +118,51 @@ pub fn spawnPty(sesh_name: []const u8, cmd: Cmd) !PtyInfo {
118118 }
119119
120120 /// daemonize is the first fork in a double-fork technique to create a
121-/// completely disconnected session (group of processes).
121+/// completely disconnected session (container of process groups).
122122 ///
123123 /// When launching a daemon, you normally set the child process of the fork to
124-/// be the session leader via setsid() which creates a new session that does
125-/// *not* have a controlling terminal. This is important because we don't want
126-/// a controlling terminal for our daemon or else our daemon could receive
127-/// signals to shutdown when the controlling terminal closes.
124+/// be the session leader via setsid() which creates a new session that removes
125+/// the current controlling terminal but creates the authority to grab a new
126+/// one. This is important because we don't want a controlling terminal for our
127+/// daemon or else our daemon could receive signals to shutdown when the
128+/// controlling terminal closes.
128129 ///
129130 /// However, if the first fork's child process is also the daemon process, then
130-/// it's technically possible for the daemon to open a terminal device
131-/// (e.g. open("/dev/console", O_RDWR)) and then it would acquire a controlling
131+/// it's technically possible for the daemon to open a terminal device (e.g.
132+/// open("/dev/console", O_RDWR)) and then it would acquire a controlling
132133 /// terminal! A controlling terminal would expose the daemon to
133134 /// terminal-generated signals (e.g. SIGINT) or SIGHUP from terminal disconnect
134135 /// which could kill the daemon.
135136 ///
136-/// By forking a second time, the grandchild process (the daemon) is not the
137-/// session leader. Per POSIX, only a process that is the session leader can
138-/// acquire a controlling terminal.
137+/// The first fork isn't arbitrary either: setsid() fails with EPERM if the
138+/// caller is already a process group leader, which a process launched directly
139+/// from a shell typically is. The first fork produces a child guaranteed not
140+/// to be a group leader, so setsid() will succeed. By forking a second time,
141+/// the grandchild process (the daemon) is not the session leader. Per POSIX,
142+/// only a process that is the session leader can acquire a controlling terminal.
139143 ///
140-/// Apparently this is considered "being paranoid" but appears to be a standard
141-/// practice for daemons so we're doing it anyway.
144+/// Apparently this is "a bit paranoid," and on Linux it is arguable since a
145+/// session leader only acquires a controlling terminal under
146+/// implementation-defined conditions. But the double-fork is the portable way
147+/// to guarantee the daemon can never acquire one, regardless of how a given
148+/// POSIX implementation behaves. So we baked it into zmx.
142149 ///
143-/// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_03
144-/// https://stackoverflow.com/a/16317668
150+/// PID=42 SID=10 PGID=10 ← original (PG leader, has tty)
151+/// │
152+/// │ fork #1
153+/// ├──────────┐
154+/// │ exit │ PID=55 SID=10 PGID=10 (not a PG leader)
155+/// ✝ │
156+/// │ setsid()
157+/// ▼
158+/// PID=55 SID=55 PGID=55 (session leader, no tty)
159+/// │
160+/// │ fork #2
161+/// ├──────────┐
162+/// │ exit │ PID=73 SID=55 PGID=55
163+/// ✝ │ PID≠SID → can't get a tty
164+/// ▼
165+/// DAEMON ✓
145166 pub fn daemonize(sesh_name: []const u8, cmd: Cmd, keep_fds_open: []i32) !PtyInfo {
146167 // creates the daemon
147168 const pid = try lib_posix.fork();