const std = @import("std"); pub const ProcessQuery = struct { name: []const u8, found_exepath: ?[]const u8 = null, pub fn deinit(self: *ProcessQuery, alloc: std.mem.Allocator) void { if (self.found_exepath) |p| alloc.free(p); self.* = undefined; } }; pub fn query(alloc: std.mem.Allocator, queries: []ProcessQuery) !void { var proc_dir = try std.fs.openDirAbsolute("/proc", .{ .iterate = true }); defer proc_dir.close(); var proc_iter = proc_dir.iterate(); procs: while (try proc_iter.next()) |proc| { // only look at directories which represent PIDs for (std.fs.path.basename(proc.name)) |c| if (!std.ascii.isDigit(c)) continue :procs; var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const cmdline_f = std.fs.openFileAbsolute( try std.fmt.bufPrint(&buf, "/proc/{s}/cmdline", .{proc.name}), .{}, ) catch |e| switch (e) { // skip other users' processes error.AccessDenied => continue, else => return e, }; defer cmdline_f.close(); // read first part of null-separated data (binary path) const exepath = epath: { var fbs = std.io.fixedBufferStream(&buf); cmdline_f.reader().streamUntilDelimiter(fbs.writer(), 0, null) catch |e| switch (e) { // occurs if there's no delimiter error.EndOfStream => {}, else => return e, }; break :epath fbs.getWritten(); }; var found_all = true; for (queries) |*q| { if (q.found_exepath) |_| continue; found_all = false; // this is a startsWith instead of an eql because the arguments in the // cmdline file are sometimes (and only sometimes!) separated by spaces // and not null bytes. if (!std.mem.startsWith(u8, std.fs.path.basename(exepath), q.name)) continue; q.found_exepath = try alloc.dupe(u8, exepath); } if (found_all) break; } }