src/os/spawn.zig
Ref: Size: 3.5 KiB History
//! Resolve the executable used when mux starts a daemon process. Resolving
//! the running image ensures the new process runs the current binary rather
//! than another `mux` found through `PATH`.
const std = @import("std");
const builtin = @import("builtin");
/// The kernel's link to the running image, used only when the resolved
/// path is no longer executable — Linux keeps a live link after a rename-
/// over, so the exec runs the image already running. On an OS with no such
/// link the fallback is the resolved path itself, and an exec after a
/// rename-over runs the NEW file at that path rather than the running one.
/// That is the ordinary meaning of the path and not a failure, but it does
/// mean the started daemon can be a different build from the starter.
pub const self_exe: []const u8 = switch (builtin.os.tag) {
.linux => "/proc/self/exe",
else => "",
};
/// Return the resolved path of the current executable, falling back to
/// this OS's link to the running image. On an OS with no such link an
/// unresolvable path comes back empty and the exec fails with it: naming
/// `mux` instead would be the PATH walk this module exists to prevent.
pub fn selfExe(buf: *[std.fs.max_path_bytes]u8) []const u8 {
// Prefer the resolved path because process listings derive `comm` from the
// filename passed to execve; executing the link would name every daemon
// `exe`.
return execOrLink(std.fs.selfExePath(buf) catch return self_exe);
}
/// The resolved path if it can still be exec'd, this OS's link to the
/// running image if it cannot and it has one.
/// Split out so the fallback is assertable without deleting a live binary.
fn execOrLink(resolved: []const u8) []const u8 {
// After `make install`, the resolved path may end in ` (deleted)` on
// Linux and no longer be executable even though the readlink succeeded.
std.posix.access(resolved, std.posix.X_OK) catch
return if (self_exe.len == 0) resolved else self_exe;
return resolved;
}
// ---------------------------------------------------------------------------
test "selfExe: the exec'd name is a real file, not the /proc link" {
var buf: [std.fs.max_path_bytes]u8 = undefined;
const exe = selfExe(&buf);
// The resolved basename becomes the process name shown by tools such as
// `ps`, `pgrep`, and `killall`.
try std.testing.expect(!std.mem.eql(u8, exe, self_exe));
try std.posix.access(exe, std.posix.X_OK);
}
test "selfExe: a resolved path that is no longer a file falls back to the link" {
// What `make install` does to a running wall. Spelled as the suffix the
// kernel actually appends, because that is the string this must survive.
var buf: [std.fs.max_path_bytes]u8 = undefined;
const live = try std.fs.selfExePath(&buf);
try std.testing.expectEqualStrings(live, execOrLink(live));
// The suffix is Linux's; an OS with no such link has no fallback path
// to assert, and `execOrLink` hands back what it was given.
if (builtin.os.tag == .linux) {
var gone: [std.fs.max_path_bytes]u8 = undefined;
const deleted = try std.fmt.bufPrint(&gone, "{s} (deleted)", .{live});
try std.testing.expectEqualStrings(self_exe, execOrLink(deleted));
}
}
// Forces semantic analysis of every pub decl under `zig build test`, so an
// unreferenced decl must at least compile (the silent-module-loss hazard,
// decisions.md). Pub decls only: std.meta.declarations sees nothing private.
test {
std.testing.refAllDeclsRecursive(@This());
}