- commit
- 3a901e0
- parent
- e719274
- author
- Eric Bower
- date
- 2026-04-14 15:04:15 -0400 EDT
refactor: require "*" suffix for wildcard matches This applies to: wait, kill, and tail Examples: - zmx wait "dev*" - zmx kill "dev*" - zmx tail "dev*" - zmx kill "*"
1 files changed,
+94,
-52
+94,
-52
1@@ -36,6 +36,25 @@ var sigterm_received: std.atomic.Value(bool) = std.atomic.Value(bool).init(false
2 // https://github.com/ziglang/zig/blob/738d2be9d6b6ef3ff3559130c05159ef53336224/lib/std/posix.zig#L3505
3 const O_NONBLOCK: usize = 1 << @bitOffsetOf(posix.O, "NONBLOCK");
4
5+const SessionMatch = struct {
6+ name: []const u8,
7+ is_prefix: bool,
8+
9+ fn matches(self: SessionMatch, session_name: []const u8) bool {
10+ if (self.is_prefix) return std.mem.startsWith(u8, session_name, self.name);
11+ return std.mem.eql(u8, session_name, self.name);
12+ }
13+};
14+
15+fn parseSessionArg(alloc: std.mem.Allocator, raw: []const u8) !SessionMatch {
16+ if (raw.len > 0 and raw[raw.len - 1] == '*') {
17+ const name = try socket.getSeshName(alloc, raw[0 .. raw.len - 1]);
18+ return .{ .name = name, .is_prefix = true };
19+ }
20+ const name = try socket.getSeshName(alloc, raw);
21+ return .{ .name = name, .is_prefix = false };
22+}
23+
24 pub fn main() !void {
25 // use c_allocator to avoid "reached unreachable code" panic in DebugAllocator when forking
26 const alloc = std.heap.c_allocator;
27@@ -185,12 +204,12 @@ pub fn main() !void {
28 var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
29 const stderr = &stderr_writer.interface;
30
31- var args_raw: std.ArrayList([]const u8) = .empty;
32+ var matchers: std.ArrayList(SessionMatch) = .empty;
33 defer {
34- for (args_raw.items) |sesh| {
35- alloc.free(sesh);
36+ for (matchers.items) |m| {
37+ alloc.free(m.name);
38 }
39- args_raw.deinit(alloc);
40+ matchers.deinit(alloc);
41 }
42 var force = false;
43 while (args.next()) |session_name| {
44@@ -198,16 +217,11 @@ pub fn main() !void {
45 force = true;
46 continue;
47 }
48- const sesh = try socket.getSeshName(alloc, session_name);
49- try args_raw.append(alloc, sesh);
50- }
51- // if no args are provided we assume they want to kill all sessions matching the prefix.
52- if (args_raw.items.len == 0) {
53- const prefix = socket.getSeshPrefix();
54- if (prefix.len == 0) {
55- return error.SessionNameRequired;
56- }
57- try args_raw.append(alloc, try alloc.dupe(u8, prefix));
58+ const m = try parseSessionArg(alloc, session_name);
59+ try matchers.append(alloc, m);
60+ }
61+ if (matchers.items.len == 0) {
62+ return error.SessionNameRequired;
63 }
64 var sessions = try util.get_session_entries(alloc, cfg.socket_dir);
65 defer {
66@@ -218,8 +232,8 @@ pub fn main() !void {
67 }
68
69 for (sessions.items) |session| {
70- for (args_raw.items) |prefix| {
71- if (!std.mem.startsWith(u8, session.name, prefix)) {
72+ for (matchers.items) |m| {
73+ if (!m.matches(session.name)) {
74 continue;
75 }
76
77@@ -234,50 +248,79 @@ pub fn main() !void {
78 }
79 }
80 } else if (std.mem.eql(u8, cmd, "wait") or std.mem.eql(u8, cmd, "w")) {
81- var args_raw: std.ArrayList([]const u8) = .empty;
82+ var matchers: std.ArrayList(SessionMatch) = .empty;
83 defer {
84- for (args_raw.items) |sesh| {
85- alloc.free(sesh);
86+ for (matchers.items) |m| {
87+ alloc.free(m.name);
88 }
89- args_raw.deinit(alloc);
90+ matchers.deinit(alloc);
91 }
92 while (args.next()) |session_name| {
93- const sesh = try socket.getSeshName(alloc, session_name);
94- try args_raw.append(alloc, sesh);
95- }
96- // if no args are provided we assume they want to wait for all sessions matching the
97- // prefix.
98- if (args_raw.items.len == 0) {
99- const prefix = socket.getSeshPrefix();
100- if (prefix.len == 0) {
101- return error.SessionNameRequired;
102- }
103- try args_raw.append(alloc, prefix);
104+ const m = try parseSessionArg(alloc, session_name);
105+ try matchers.append(alloc, m);
106 }
107- return wait(&cfg, args_raw);
108+ if (matchers.items.len == 0) {
109+ return error.SessionNameRequired;
110+ }
111+ return wait(&cfg, matchers);
112 } else if (std.mem.eql(u8, cmd, "tail") or std.mem.eql(u8, cmd, "t")) {
113- var session_names: std.ArrayList([]const u8) = .empty;
114+ var matchers: std.ArrayList(SessionMatch) = .empty;
115 defer {
116- for (session_names.items) |sesh| {
117- alloc.free(sesh);
118+ for (matchers.items) |m| {
119+ alloc.free(m.name);
120 }
121- session_names.deinit(alloc);
122+ matchers.deinit(alloc);
123 }
124 while (args.next()) |session_name| {
125- const sesh = try socket.getSeshName(alloc, session_name);
126- try session_names.append(alloc, sesh);
127- }
128- // if no args are provided we assume they want to wait for all sessions matching the
129- // prefix.
130- if (session_names.items.len == 0) {
131- const prefix = socket.getSeshPrefix();
132- if (prefix.len == 0) {
133- return error.SessionNameRequired;
134+ const m = try parseSessionArg(alloc, session_name);
135+ try matchers.append(alloc, m);
136+ }
137+ if (matchers.items.len == 0) {
138+ return error.SessionNameRequired;
139+ }
140+
141+ // Resolve matchers against session list to get actual session names.
142+ var resolved_names: std.ArrayList([]const u8) = .empty;
143+ defer {
144+ for (resolved_names.items) |name| {
145+ alloc.free(name);
146 }
147- try session_names.append(alloc, prefix);
148+ resolved_names.deinit(alloc);
149 }
150
151- var client_socket_fds = try std.ArrayList(i32).initCapacity(alloc, session_names.items.len);
152+ var any_prefix = false;
153+ for (matchers.items) |m| {
154+ if (m.is_prefix) {
155+ any_prefix = true;
156+ break;
157+ }
158+ }
159+
160+ if (any_prefix) {
161+ var sessions = try util.get_session_entries(alloc, cfg.socket_dir);
162+ defer {
163+ for (sessions.items) |session| {
164+ session.deinit(alloc);
165+ }
166+ sessions.deinit(alloc);
167+ }
168+ for (sessions.items) |session| {
169+ for (matchers.items) |m| {
170+ if (m.matches(session.name)) {
171+ try resolved_names.append(alloc, try alloc.dupe(u8, session.name));
172+ break;
173+ }
174+ }
175+ }
176+ }
177+ // Add exact-match names directly.
178+ for (matchers.items) |m| {
179+ if (!m.is_prefix) {
180+ try resolved_names.append(alloc, try alloc.dupe(u8, m.name));
181+ }
182+ }
183+
184+ var client_socket_fds = try std.ArrayList(i32).initCapacity(alloc, resolved_names.items.len);
185 defer {
186 for (client_socket_fds.items) |client_fd| {
187 posix.close(client_fd);
188@@ -285,7 +328,7 @@ pub fn main() !void {
189 client_socket_fds.deinit(alloc);
190 }
191
192- for (session_names.items) |session_name| {
193+ for (resolved_names.items) |session_name| {
194 const socket_path = socket.getSocketPath(alloc, cfg.socket_dir, session_name) catch |err| switch (err) {
195 error.NameTooLong => return socket.printSessionNameTooLong(session_name, cfg.socket_dir),
196 error.OutOfMemory => return err,
197@@ -449,7 +492,6 @@ test "Cfg.init uses custom modes from env vars" {
198 try std.testing.expectEqual(@as(u32, 0o660), cfg.log_mode);
199 }
200
201-
202 /// Daemon is responsible for managing a zmx session.
203 ///
204 /// It holds all the state for a running session. Instead of a single daemon for all sessions, we
205@@ -1295,7 +1337,7 @@ fn tail(client_socket_fds: std.ArrayList(i32), detached: bool, is_run_cmd: bool)
206 }
207 }
208
209-fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
210+fn wait(cfg: *Cfg, matchers: std.ArrayList(SessionMatch)) !void {
211 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
212 defer _ = gpa.deinit();
213 const alloc = gpa.allocator();
214@@ -1322,8 +1364,8 @@ fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
215
216 for (sessions.items) |session| {
217 var found = false;
218- for (session_names.items) |prefix| {
219- if (std.mem.startsWith(u8, session.name, prefix)) {
220+ for (matchers.items) |m| {
221+ if (m.matches(session.name)) {
222 found = true;
223 break;
224 }