Commit 3a901e0

Eric Bower  ·  2026-04-14 15:04:15 -0400 EDT
parent e719274
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
......@@ -36,6 +36,25 @@ var sigterm_received: std.atomic.Value(bool) = std.atomic.Value(bool).init(false
3636 // https://github.com/ziglang/zig/blob/738d2be9d6b6ef3ff3559130c05159ef53336224/lib/std/posix.zig#L3505
3737 const O_NONBLOCK: usize = 1 << @bitOffsetOf(posix.O, "NONBLOCK");
3838
39+const SessionMatch = struct {
40+ name: []const u8,
41+ is_prefix: bool,
42+
43+ fn matches(self: SessionMatch, session_name: []const u8) bool {
44+ if (self.is_prefix) return std.mem.startsWith(u8, session_name, self.name);
45+ return std.mem.eql(u8, session_name, self.name);
46+ }
47+};
48+
49+fn parseSessionArg(alloc: std.mem.Allocator, raw: []const u8) !SessionMatch {
50+ if (raw.len > 0 and raw[raw.len - 1] == '*') {
51+ const name = try socket.getSeshName(alloc, raw[0 .. raw.len - 1]);
52+ return .{ .name = name, .is_prefix = true };
53+ }
54+ const name = try socket.getSeshName(alloc, raw);
55+ return .{ .name = name, .is_prefix = false };
56+}
57+
3958 pub fn main() !void {
4059 // use c_allocator to avoid "reached unreachable code" panic in DebugAllocator when forking
4160 const alloc = std.heap.c_allocator;
......@@ -185,12 +204,12 @@ pub fn main() !void {
185204 var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
186205 const stderr = &stderr_writer.interface;
187206
188- var args_raw: std.ArrayList([]const u8) = .empty;
207+ var matchers: std.ArrayList(SessionMatch) = .empty;
189208 defer {
190- for (args_raw.items) |sesh| {
191- alloc.free(sesh);
209+ for (matchers.items) |m| {
210+ alloc.free(m.name);
192211 }
193- args_raw.deinit(alloc);
212+ matchers.deinit(alloc);
194213 }
195214 var force = false;
196215 while (args.next()) |session_name| {
......@@ -198,16 +217,11 @@ pub fn main() !void {
198217 force = true;
199218 continue;
200219 }
201- const sesh = try socket.getSeshName(alloc, session_name);
202- try args_raw.append(alloc, sesh);
203- }
204- // if no args are provided we assume they want to kill all sessions matching the prefix.
205- if (args_raw.items.len == 0) {
206- const prefix = socket.getSeshPrefix();
207- if (prefix.len == 0) {
208- return error.SessionNameRequired;
209- }
210- try args_raw.append(alloc, try alloc.dupe(u8, prefix));
220+ const m = try parseSessionArg(alloc, session_name);
221+ try matchers.append(alloc, m);
222+ }
223+ if (matchers.items.len == 0) {
224+ return error.SessionNameRequired;
211225 }
212226 var sessions = try util.get_session_entries(alloc, cfg.socket_dir);
213227 defer {
......@@ -218,8 +232,8 @@ pub fn main() !void {
218232 }
219233
220234 for (sessions.items) |session| {
221- for (args_raw.items) |prefix| {
222- if (!std.mem.startsWith(u8, session.name, prefix)) {
235+ for (matchers.items) |m| {
236+ if (!m.matches(session.name)) {
223237 continue;
224238 }
225239
......@@ -234,50 +248,79 @@ pub fn main() !void {
234248 }
235249 }
236250 } else if (std.mem.eql(u8, cmd, "wait") or std.mem.eql(u8, cmd, "w")) {
237- var args_raw: std.ArrayList([]const u8) = .empty;
251+ var matchers: std.ArrayList(SessionMatch) = .empty;
238252 defer {
239- for (args_raw.items) |sesh| {
240- alloc.free(sesh);
253+ for (matchers.items) |m| {
254+ alloc.free(m.name);
241255 }
242- args_raw.deinit(alloc);
256+ matchers.deinit(alloc);
243257 }
244258 while (args.next()) |session_name| {
245- const sesh = try socket.getSeshName(alloc, session_name);
246- try args_raw.append(alloc, sesh);
247- }
248- // if no args are provided we assume they want to wait for all sessions matching the
249- // prefix.
250- if (args_raw.items.len == 0) {
251- const prefix = socket.getSeshPrefix();
252- if (prefix.len == 0) {
253- return error.SessionNameRequired;
254- }
255- try args_raw.append(alloc, prefix);
259+ const m = try parseSessionArg(alloc, session_name);
260+ try matchers.append(alloc, m);
256261 }
257- return wait(&cfg, args_raw);
262+ if (matchers.items.len == 0) {
263+ return error.SessionNameRequired;
264+ }
265+ return wait(&cfg, matchers);
258266 } else if (std.mem.eql(u8, cmd, "tail") or std.mem.eql(u8, cmd, "t")) {
259- var session_names: std.ArrayList([]const u8) = .empty;
267+ var matchers: std.ArrayList(SessionMatch) = .empty;
260268 defer {
261- for (session_names.items) |sesh| {
262- alloc.free(sesh);
269+ for (matchers.items) |m| {
270+ alloc.free(m.name);
263271 }
264- session_names.deinit(alloc);
272+ matchers.deinit(alloc);
265273 }
266274 while (args.next()) |session_name| {
267- const sesh = try socket.getSeshName(alloc, session_name);
268- try session_names.append(alloc, sesh);
269- }
270- // if no args are provided we assume they want to wait for all sessions matching the
271- // prefix.
272- if (session_names.items.len == 0) {
273- const prefix = socket.getSeshPrefix();
274- if (prefix.len == 0) {
275- return error.SessionNameRequired;
275+ const m = try parseSessionArg(alloc, session_name);
276+ try matchers.append(alloc, m);
277+ }
278+ if (matchers.items.len == 0) {
279+ return error.SessionNameRequired;
280+ }
281+
282+ // Resolve matchers against session list to get actual session names.
283+ var resolved_names: std.ArrayList([]const u8) = .empty;
284+ defer {
285+ for (resolved_names.items) |name| {
286+ alloc.free(name);
276287 }
277- try session_names.append(alloc, prefix);
288+ resolved_names.deinit(alloc);
278289 }
279290
280- var client_socket_fds = try std.ArrayList(i32).initCapacity(alloc, session_names.items.len);
291+ var any_prefix = false;
292+ for (matchers.items) |m| {
293+ if (m.is_prefix) {
294+ any_prefix = true;
295+ break;
296+ }
297+ }
298+
299+ if (any_prefix) {
300+ var sessions = try util.get_session_entries(alloc, cfg.socket_dir);
301+ defer {
302+ for (sessions.items) |session| {
303+ session.deinit(alloc);
304+ }
305+ sessions.deinit(alloc);
306+ }
307+ for (sessions.items) |session| {
308+ for (matchers.items) |m| {
309+ if (m.matches(session.name)) {
310+ try resolved_names.append(alloc, try alloc.dupe(u8, session.name));
311+ break;
312+ }
313+ }
314+ }
315+ }
316+ // Add exact-match names directly.
317+ for (matchers.items) |m| {
318+ if (!m.is_prefix) {
319+ try resolved_names.append(alloc, try alloc.dupe(u8, m.name));
320+ }
321+ }
322+
323+ var client_socket_fds = try std.ArrayList(i32).initCapacity(alloc, resolved_names.items.len);
281324 defer {
282325 for (client_socket_fds.items) |client_fd| {
283326 posix.close(client_fd);
......@@ -285,7 +328,7 @@ pub fn main() !void {
285328 client_socket_fds.deinit(alloc);
286329 }
287330
288- for (session_names.items) |session_name| {
331+ for (resolved_names.items) |session_name| {
289332 const socket_path = socket.getSocketPath(alloc, cfg.socket_dir, session_name) catch |err| switch (err) {
290333 error.NameTooLong => return socket.printSessionNameTooLong(session_name, cfg.socket_dir),
291334 error.OutOfMemory => return err,
......@@ -449,7 +492,6 @@ test "Cfg.init uses custom modes from env vars" {
449492 try std.testing.expectEqual(@as(u32, 0o660), cfg.log_mode);
450493 }
451494
452-
453495 /// Daemon is responsible for managing a zmx session.
454496 ///
455497 /// It holds all the state for a running session. Instead of a single daemon for all sessions, we
......@@ -1295,7 +1337,7 @@ fn tail(client_socket_fds: std.ArrayList(i32), detached: bool, is_run_cmd: bool)
12951337 }
12961338 }
12971339
1298-fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
1340+fn wait(cfg: *Cfg, matchers: std.ArrayList(SessionMatch)) !void {
12991341 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
13001342 defer _ = gpa.deinit();
13011343 const alloc = gpa.allocator();
......@@ -1322,8 +1364,8 @@ fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void {
13221364
13231365 for (sessions.items) |session| {
13241366 var found = false;
1325- for (session_names.items) |prefix| {
1326- if (std.mem.startsWith(u8, session.name, prefix)) {
1367+ for (matchers.items) |m| {
1368+ if (m.matches(session.name)) {
13271369 found = true;
13281370 break;
13291371 }