- commit
- 8bdbac6
- parent
- f973821
- author
- Eric Bower
- date
- 2025-11-23 20:47:08 -0500 EST
feat: list command
2 files changed,
+71,
-5
+6,
-0
1@@ -8,6 +8,7 @@ pub const Tag = enum(u8) {
2 Detach = 3,
3 DetachAll = 4,
4 Kill = 5,
5+ Info = 6,
6 };
7
8 pub const Header = packed struct {
9@@ -20,6 +21,11 @@ pub const Resize = packed struct {
10 cols: u16,
11 };
12
13+pub const Info = packed struct {
14+ clients_len: usize,
15+ pid: i32,
16+};
17+
18 pub fn expectedLength(data: []const u8) ?usize {
19 if (data.len < @sizeOf(Header)) return null;
20 const header = std.mem.bytesToValue(Header, data[0..@sizeOf(Header)]);
+65,
-5
1@@ -65,6 +65,7 @@ const Daemon = struct {
2 session_name: []const u8,
3 socket_path: []const u8,
4 running: bool,
5+ pid: i32,
6
7 pub fn deinit(self: *Daemon) void {
8 self.clients.deinit(self.alloc);
9@@ -114,6 +115,8 @@ pub fn main() !void {
10
11 if (std.mem.eql(u8, cmd, "help")) {
12 return help();
13+ } else if (std.mem.eql(u8, cmd, "list")) {
14+ return list(&cfg);
15 } else if (std.mem.eql(u8, cmd, "detach")) {
16 return detachAll(&cfg);
17 } else if (std.mem.eql(u8, cmd, "kill")) {
18@@ -135,6 +138,7 @@ pub fn main() !void {
19 .clients = clients,
20 .session_name = session_name,
21 .socket_path = undefined,
22+ .pid = undefined,
23 };
24 daemon.socket_path = try getSocketPath(alloc, cfg.socket_dir, session_name);
25 std.log.info("socket path={s}", .{daemon.socket_path});
26@@ -148,8 +152,53 @@ fn help() !void {
27 std.log.info("running cmd=help", .{});
28 }
29
30-fn list(_: *Cfg) !void {
31+fn list(cfg: *Cfg) !void {
32 std.log.info("running cmd=list", .{});
33+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
34+ defer _ = gpa.deinit();
35+ const alloc = gpa.allocator();
36+
37+ var dir = try std.fs.openDirAbsolute(cfg.socket_dir, .{ .iterate = true });
38+ defer dir.close();
39+ var iter = dir.iterate();
40+ while (try iter.next()) |entry| {
41+ if (try sessionExists(dir, entry.name)) {
42+ const socket_path = try getSocketPath(alloc, cfg.socket_dir, entry.name);
43+ defer alloc.free(socket_path);
44+
45+ const fd = sessionConnect(socket_path) catch |err| {
46+ std.log.warn("could not connect to session {s}: {s}", .{ entry.name, @errorName(err) });
47+ continue;
48+ };
49+ defer posix.close(fd);
50+
51+ try ipc.send(fd, .Info, "");
52+
53+ var sb = try ipc.SocketBuffer.init(alloc);
54+ defer sb.deinit();
55+
56+ var info: ?ipc.Info = null;
57+ read_loop: while (true) {
58+ const n = try sb.read(fd);
59+ if (n == 0) break; // EOF
60+
61+ while (sb.next()) |msg| {
62+ if (msg.header.tag == .Info) {
63+ if (msg.payload.len == @sizeOf(ipc.Info)) {
64+ info = std.mem.bytesToValue(ipc.Info, msg.payload[0..@sizeOf(ipc.Info)]);
65+ break :read_loop;
66+ }
67+ }
68+ }
69+ }
70+
71+ if (info) |i| {
72+ std.log.info("session_name={s}\tpid={d}\tclients={d}", .{ entry.name, i.pid, i.clients_len });
73+ } else {
74+ std.log.info("session_name={s}\tpid=?\tclients=?", .{entry.name});
75+ }
76+ }
77+ }
78 }
79
80 fn detachAll(cfg: *Cfg) !void {
81@@ -499,14 +548,14 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
82 const n = client.read_buf.read(client.socket_fd) catch |err| {
83 if (err == error.WouldBlock) continue;
84 std.log.warn("client read error err={s}", .{@errorName(err)});
85- const last = daemon.closeClient(client, i, true);
86+ const last = daemon.closeClient(client, i, false);
87 if (last) should_exit = true;
88 continue;
89 };
90
91 if (n == 0) {
92 // Client closed connection
93- const last = daemon.closeClient(client, i, true);
94+ const last = daemon.closeClient(client, i, false);
95 if (last) should_exit = true;
96 continue;
97 }
98@@ -547,6 +596,16 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
99 should_exit = true;
100 break :clients_loop;
101 },
102+ .Info => {
103+ // subtract current client since it's just fetching info
104+ const clients_len = daemon.clients.items.len - 1;
105+ const info = ipc.Info{
106+ .clients_len = clients_len,
107+ .pid = daemon.pid,
108+ };
109+ try ipc.appendMessage(daemon.alloc, &client.write_buf, .Info, std.mem.asBytes(&info));
110+ client.has_pending_output = true;
111+ },
112 .Output => {}, // Clients shouldn't send output
113 }
114 }
115@@ -557,7 +616,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
116 const n = posix.write(client.socket_fd, client.write_buf.items) catch |err| blk: {
117 if (err == error.WouldBlock) break :blk 0;
118 // Error on write, close client
119- const last = daemon.closeClient(client, i, true);
120+ const last = daemon.closeClient(client, i, false);
121 if (last) should_exit = true;
122 continue;
123 };
124@@ -572,7 +631,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
125 }
126
127 if (revents & (posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL) != 0) {
128- const last = daemon.closeClient(client, i, true);
129+ const last = daemon.closeClient(client, i, false);
130 if (last) should_exit = true;
131 }
132 }
133@@ -611,6 +670,7 @@ fn spawnPty(daemon: *Daemon) !c_int {
134 }
135 // master pid code path
136
137+ daemon.pid = pid;
138 std.log.info("created pty session: session_name={s} master_pid={d} child_pid={d}", .{ daemon.session_name, master_fd, pid });
139
140 // make pty non-blocking