Commit 3b0e4e8

Eric Bower  ·  2026-03-22 22:12:51 -0400 EDT
parent 89e2728
docs: doc strings
1 files changed,  +47, -15
+47, -15
......@@ -187,6 +187,9 @@ pub fn main() !void {
187187 }
188188 }
189189
190+/// Client represents each terminal that has connected to a session.
191+///
192+/// Multiple Clients can connect to a single session.
190193 const Client = struct {
191194 alloc: std.mem.Allocator,
192195 socket_fd: i32,
......@@ -201,23 +204,16 @@ const Client = struct {
201204 }
202205 };
203206
207+/// Cfg is zmx's configuration container.
208+///
209+/// The purpose of this container is to hold anything that can be modified by the user.
204210 const Cfg = struct {
205211 socket_dir: []const u8,
206212 log_dir: []const u8,
207213 max_scrollback: usize = 10_000_000,
208214
209215 pub fn init(alloc: std.mem.Allocator) !Cfg {
210- const tmpdir = std.mem.trimRight(u8, posix.getenv("TMPDIR") orelse "/tmp", "/");
211- const uid = posix.getuid();
212-
213- const socket_dir: []const u8 = if (posix.getenv("ZMX_DIR")) |zmxdir|
214- try alloc.dupe(u8, zmxdir)
215- else if (posix.getenv("XDG_RUNTIME_DIR")) |xdg_runtime|
216- try std.fmt.allocPrint(alloc, "{s}/zmx", .{xdg_runtime})
217- else
218- try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid });
219- errdefer alloc.free(socket_dir);
220-
216+ const socket_dir = try socketDir(alloc);
221217 const log_dir = try std.fmt.allocPrint(alloc, "{s}/logs", .{socket_dir});
222218 errdefer alloc.free(log_dir);
223219
......@@ -231,6 +227,21 @@ const Cfg = struct {
231227 return cfg;
232228 }
233229
230+ fn socketDir(alloc: std.mem.Allocator) ![]const u8 {
231+ const tmpdir = std.mem.trimRight(u8, posix.getenv("TMPDIR") orelse "/tmp", "/");
232+ const uid = posix.getuid();
233+
234+ const socket_dir: []const u8 = if (posix.getenv("ZMX_DIR")) |zmxdir|
235+ try alloc.dupe(u8, zmxdir)
236+ else if (posix.getenv("XDG_RUNTIME_DIR")) |xdg_runtime|
237+ try std.fmt.allocPrint(alloc, "{s}/zmx", .{xdg_runtime})
238+ else
239+ try std.fmt.allocPrint(alloc, "{s}/zmx-{d}", .{ tmpdir, uid });
240+ errdefer alloc.free(socket_dir);
241+
242+ return socket_dir;
243+ }
244+
234245 pub fn deinit(self: *Cfg, alloc: std.mem.Allocator) void {
235246 if (self.socket_dir.len > 0) alloc.free(self.socket_dir);
236247 if (self.log_dir.len > 0) alloc.free(self.log_dir);
......@@ -254,6 +265,14 @@ const EnsureSessionResult = struct {
254265 is_daemon: bool,
255266 };
256267
268+/// Daemon is responsible for managing a zmx session.
269+///
270+/// It holds all the state for a running session. Instead of a single daemon for all sessions, we
271+/// create a daemon for every session. This has some benefits. The ipc communication between
272+/// session clients and the daemon doesn't need to be tagged with the session name. If a daemon
273+/// crashes for one session won't crash all the other sessions.
274+///
275+/// Conceptually it's also much simpler to reason about.
257276 const Daemon = struct {
258277 cfg: *Cfg,
259278 alloc: std.mem.Allocator,
......@@ -343,6 +362,7 @@ const Daemon = struct {
343362 std.posix.exit(1);
344363 }
345364
365+ /// spawnPty runs forkpty() and executes the shell or shell command the user provides.
346366 fn spawnPty(self: *Daemon) !c_int {
347367 const size = ipc.getTerminalSize(posix.STDOUT_FILENO);
348368 var ws: cross.c.struct_winsize = .{
......@@ -379,6 +399,8 @@ const Daemon = struct {
379399 return master_fd;
380400 }
381401
402+ /// ensureSession "upserts" a session by checking if the unix socket exists already.
403+ /// If not it creates one and spawns the daemon.
382404 fn ensureSession(self: *Daemon) !EnsureSessionResult {
383405 var dir = try std.fs.openDirAbsolute(self.cfg.socket_dir, .{});
384406 defer dir.close();
......@@ -411,8 +433,10 @@ const Daemon = struct {
411433 std.log.info("creating session={s}", .{self.session_name});
412434 const server_sock_fd = try socket.createSocket(self.socket_path);
413435
436+ // creates the daemon
414437 const pid = try posix.fork();
415438 if (pid == 0) { // child (daemon)
439+ // becomes the session leader and detaches process from its controlling terminal
416440 _ = try posix.setsid();
417441
418442 log_system.deinit();
......@@ -695,10 +719,10 @@ fn help() !void {
695719 \\ [r]un <name> [command...] Send command without attaching, creating session if needed
696720 \\ [d]etach Detach all clients from current session (ctrl+\ for current client)
697721 \\ [l]ist [--short] List active sessions
698- \\ [c]ompletions <shell> Completion scripts for shell integration (bash, zsh, or fish)
699722 \\ [k]ill <name> Kill a session and all attached clients
700723 \\ [hi]story <name> [--vt|--html] Output session scrollback (--vt or --html for escape sequences)
701724 \\ [w]ait <name>... Wait for session tasks to complete
725+ \\ [c]ompletions <shell> Completion scripts for shell integration (bash, zsh, or fish)
702726 \\ [v]ersion Show version information
703727 \\ [h]elp Show this help message
704728 \\
......@@ -707,7 +731,7 @@ fn help() !void {
707731 \\ - ZMX_DIR Controls which folder is used to store unix socket files (prio: 1)
708732 \\ - XDG_RUNTIME_DIR Controls which folder is used to store unix socket files (prio: 2)
709733 \\ - TMPDIR Controls which folder is used to store unix socket files (prio: 3)
710- \\ - ZMX_SESSION This variable is injected into every zmx session automatically
734+ \\ - ZMX_SESSION The session name we inject into every zmx session automatically
711735 \\ - ZMX_SESSION_PREFIX Adds this value to the start of every session name for all commands
712736 \\
713737 ;
......@@ -1001,7 +1025,7 @@ fn attach(daemon: *Daemon) !void {
10011025
10021026 const client_sock = try socket.sessionConnect(daemon.socket_path);
10031027 std.log.info("attached session={s}", .{daemon.session_name});
1004- // this is typically used with tcsetattr() to modify terminal settings.
1028+ // This is typically used with tcsetattr() to modify terminal settings.
10051029 // - you first get the current settings with tcgetattr()
10061030 // - modify the desired attributes in the termios structure
10071031 // - then apply the changes with tcsetattr().
......@@ -1079,6 +1103,10 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
10791103
10801104 const shell = util.detectShell();
10811105 const shell_basename = std.fs.path.basename(shell);
1106+ // We append a task marker so we can:
1107+ // - know when the command finishes
1108+ // - capture its exit status
1109+ // This information is retrived when running `zmx list`
10821110 const inline_task_marker = if (std.mem.eql(u8, shell_basename, "fish"))
10831111 "; echo ZMX_TASK_COMPLETED:$status"
10841112 else
......@@ -1185,6 +1213,8 @@ fn run(daemon: *Daemon, command_args: [][]const u8) !void {
11851213 return error.NoAckReceived;
11861214 }
11871215
1216+/// clientLoop sends ipc commands to its corresponding daemon. It uses poll() as its non-blocking
1217+/// mechanism. It will send stdin to the daemon and receive stdout from the daemon.
11881218 fn clientLoop(client_sock_fd: i32) !void {
11891219 // use c_allocator to avoid "reached unreachable code" panic in DebugAllocator when forking
11901220 const alloc = std.heap.c_allocator;
......@@ -1343,6 +1373,8 @@ fn clientLoop(client_sock_fd: i32) !void {
13431373 }
13441374 }
13451375
1376+/// dameonLoop is what the daemon runs to send and receive ipc commands from its corresponding
1377+/// clients. It uses poll() as its non-blocking mechanism.
13461378 fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
13471379 std.log.info("daemon started session={s} pty_fd={d}", .{ daemon.session_name, pty_fd });
13481380 setupSigtermHandler();
......@@ -1554,7 +1586,7 @@ fn handleSigterm(_: i32, _: *const posix.siginfo_t, _: ?*anyopaque) callconv(.c)
15541586 sigterm_received.store(true, .release);
15551587 }
15561588
1557-// No SA_RESTART on these: we WANT the signal to interrupt poll() so the
1589+// No SA_RESTART: we want the signal to interrupt poll() so the
15581590 // loop can check the flag. On BSD/macOS, SA_RESTART makes poll restartable,
15591591 // which would leave an idle daemon deaf to SIGTERM until other I/O wakes it.
15601592 fn setupSigwinchHandler() void {