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 | |
| 36 | 36 | // https://github.com/ziglang/zig/blob/738d2be9d6b6ef3ff3559130c05159ef53336224/lib/std/posix.zig#L3505 | |
| 37 | 37 | const O_NONBLOCK: usize = 1 << @bitOffsetOf(posix.O, "NONBLOCK"); | |
| 38 | 38 | ||
| 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 | + | ||
| 39 | 58 | pub fn main() !void { | |
| 40 | 59 | // use c_allocator to avoid "reached unreachable code" panic in DebugAllocator when forking | |
| 41 | 60 | const alloc = std.heap.c_allocator; |
| ... | ... | @@ -185,12 +204,12 @@ pub fn main() !void { | |
| 185 | 204 | var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer); | |
| 186 | 205 | const stderr = &stderr_writer.interface; | |
| 187 | 206 | ||
| 188 | - | var args_raw: std.ArrayList([]const u8) = .empty; | |
| 207 | + | var matchers: std.ArrayList(SessionMatch) = .empty; | |
| 189 | 208 | defer { | |
| 190 | - | for (args_raw.items) |sesh| { | |
| 191 | - | alloc.free(sesh); | |
| 209 | + | for (matchers.items) |m| { | |
| 210 | + | alloc.free(m.name); | |
| 192 | 211 | } | |
| 193 | - | args_raw.deinit(alloc); | |
| 212 | + | matchers.deinit(alloc); | |
| 194 | 213 | } | |
| 195 | 214 | var force = false; | |
| 196 | 215 | while (args.next()) |session_name| { |
| ... | ... | @@ -198,16 +217,11 @@ pub fn main() !void { | |
| 198 | 217 | force = true; | |
| 199 | 218 | continue; | |
| 200 | 219 | } | |
| 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; | |
| 211 | 225 | } | |
| 212 | 226 | var sessions = try util.get_session_entries(alloc, cfg.socket_dir); | |
| 213 | 227 | defer { |
| ... | ... | @@ -218,8 +232,8 @@ pub fn main() !void { | |
| 218 | 232 | } | |
| 219 | 233 | ||
| 220 | 234 | 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)) { | |
| 223 | 237 | continue; | |
| 224 | 238 | } | |
| 225 | 239 |
| ... | ... | @@ -234,50 +248,79 @@ pub fn main() !void { | |
| 234 | 248 | } | |
| 235 | 249 | } | |
| 236 | 250 | } 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; | |
| 238 | 252 | defer { | |
| 239 | - | for (args_raw.items) |sesh| { | |
| 240 | - | alloc.free(sesh); | |
| 253 | + | for (matchers.items) |m| { | |
| 254 | + | alloc.free(m.name); | |
| 241 | 255 | } | |
| 242 | - | args_raw.deinit(alloc); | |
| 256 | + | matchers.deinit(alloc); | |
| 243 | 257 | } | |
| 244 | 258 | 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); | |
| 256 | 261 | } | |
| 257 | - | return wait(&cfg, args_raw); | |
| 262 | + | if (matchers.items.len == 0) { | |
| 263 | + | return error.SessionNameRequired; | |
| 264 | + | } | |
| 265 | + | return wait(&cfg, matchers); | |
| 258 | 266 | } 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; | |
| 260 | 268 | defer { | |
| 261 | - | for (session_names.items) |sesh| { | |
| 262 | - | alloc.free(sesh); | |
| 269 | + | for (matchers.items) |m| { | |
| 270 | + | alloc.free(m.name); | |
| 263 | 271 | } | |
| 264 | - | session_names.deinit(alloc); | |
| 272 | + | matchers.deinit(alloc); | |
| 265 | 273 | } | |
| 266 | 274 | 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); | |
| 276 | 287 | } | |
| 277 | - | try session_names.append(alloc, prefix); | |
| 288 | + | resolved_names.deinit(alloc); | |
| 278 | 289 | } | |
| 279 | 290 | ||
| 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); | |
| 281 | 324 | defer { | |
| 282 | 325 | for (client_socket_fds.items) |client_fd| { | |
| 283 | 326 | posix.close(client_fd); |
| ... | ... | @@ -285,7 +328,7 @@ pub fn main() !void { | |
| 285 | 328 | client_socket_fds.deinit(alloc); | |
| 286 | 329 | } | |
| 287 | 330 | ||
| 288 | - | for (session_names.items) |session_name| { | |
| 331 | + | for (resolved_names.items) |session_name| { | |
| 289 | 332 | const socket_path = socket.getSocketPath(alloc, cfg.socket_dir, session_name) catch |err| switch (err) { | |
| 290 | 333 | error.NameTooLong => return socket.printSessionNameTooLong(session_name, cfg.socket_dir), | |
| 291 | 334 | error.OutOfMemory => return err, |
| ... | ... | @@ -449,7 +492,6 @@ test "Cfg.init uses custom modes from env vars" { | |
| 449 | 492 | try std.testing.expectEqual(@as(u32, 0o660), cfg.log_mode); | |
| 450 | 493 | } | |
| 451 | 494 | ||
| 452 | - | ||
| 453 | 495 | /// Daemon is responsible for managing a zmx session. | |
| 454 | 496 | /// | |
| 455 | 497 | /// 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) | |
| 1295 | 1337 | } | |
| 1296 | 1338 | } | |
| 1297 | 1339 | ||
| 1298 | - | fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void { | |
| 1340 | + | fn wait(cfg: *Cfg, matchers: std.ArrayList(SessionMatch)) !void { | |
| 1299 | 1341 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | |
| 1300 | 1342 | defer _ = gpa.deinit(); | |
| 1301 | 1343 | const alloc = gpa.allocator(); |
| ... | ... | @@ -1322,8 +1364,8 @@ fn wait(cfg: *Cfg, session_names: std.ArrayList([]const u8)) !void { | |
| 1322 | 1364 | ||
| 1323 | 1365 | for (sessions.items) |session| { | |
| 1324 | 1366 | 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)) { | |
| 1327 | 1369 | found = true; | |
| 1328 | 1370 | break; | |
| 1329 | 1371 | } |