lib/sys/src/process/tracing/launch.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const linux = std.os.linux;
3 const sys = @import("../../root.zig");
4
5 pub const Child = struct {
6 pid: i32,
7 gate: i32,
8
9 pub fn release(self: Child) !void {
10 if (linux.write(self.gate, "x", 1) != 1) return error.ChildReleaseFailed;
11 }
12
13 pub fn close(self: Child) void {
14 _ = linux.close(self.gate);
15 }
16 };
17
18 /// Forks a child that blocks reading the gate, so a tracer can attach before
19 /// the child execs, and returns the child's process id together with the
20 /// writing end of the gate. `Child.release` writes the byte that lets the child
21 /// exec, and `Child.close` drops the gate. The caller forks through this before
22 /// it starts any application thread, because the child runs syscalls alone
23 /// between the fork and the exec. The executable is searched along the
24 /// environment's `PATH`, falling back to `/bin:/usr/bin`, over at most 256
25 /// candidates, while an empty argument vector, and one longer than 4096, fail
26 /// with `InvalidArguments`.
27 pub fn start(
28 allocator: std.mem.Allocator,
29 argv: []const []const u8,
30 environment: *const std.process.Environ.Map,
31 ) !Child {
32 if (argv.len == 0 or argv.len > 4096) return error.InvalidArguments;
33 var arena_state = std.heap.ArenaAllocator.init(allocator);
34 defer arena_state.deinit();
35 const arena = arena_state.allocator();
36 const arguments = try arena.allocSentinel(?[*:0]const u8, argv.len, null);
37 for (argv, 0..) |arg, i| arguments[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
38 const env = try environment.createPosixBlock(arena, .{ .zig_progress_fd = -1 });
39 const paths = try candidates(arena, argv[0], environment.get("PATH") orelse "/bin:/usr/bin");
40 var pipe: [2]i32 = undefined;
41 if (linux.errno(linux.pipe2(&pipe, .{ .CLOEXEC = true })) != .SUCCESS)
42 return error.PipeFailed;
43 errdefer {
44 _ = linux.close(pipe[0]);
45 _ = linux.close(pipe[1]);
46 }
47 const forked = try sys.process.fork();
48 if (forked == .child) {
49 _ = linux.close(pipe[1]);
50 child(pipe[0], paths, arguments, env.slice);
51 }
52 _ = linux.close(pipe[0]);
53 return .{ .pid = forked.parent, .gate = pipe[1] };
54 }
55
56 fn candidates(
57 allocator: std.mem.Allocator,
58 name: []const u8,
59 path: []const u8,
60 ) ![]const [:0]const u8 {
61 if (name.len == 0) return error.EmptyCommand;
62 var result: std.ArrayList([:0]const u8) = .empty;
63 if (std.mem.indexOfScalar(u8, name, '/') != null) {
64 try result.append(allocator, try allocator.dupeSentinel(u8, name, 0));
65 } else {
66 var parts = std.mem.splitScalar(u8, path, ':');
67 while (parts.next()) |part| {
68 if (result.items.len == 256) return error.PathLimitExceeded;
69 const directory = if (part.len == 0) "." else part;
70 try result.append(allocator, try std.fmt.allocPrintSentinel(allocator, "{s}/{s}", .{
71 directory, name,
72 }, 0));
73 }
74 }
75 return result.toOwnedSlice(allocator);
76 }
77
78 fn child(
79 gate: i32,
80 paths: []const [:0]const u8,
81 argv: [:null]const ?[*:0]const u8,
82 env: [:null]const ?[*:0]const u8,
83 ) noreturn {
84 var byte: [1]u8 = undefined;
85 const n = linux.read(gate, &byte, 1);
86 _ = linux.close(gate);
87 if (n != 1) linux.exit(125);
88 var code: u8 = 127;
89 for (paths) |path| {
90 const failure = linux.errno(linux.execve(path, argv, env));
91 if (failure == .ACCES) {
92 code = 126;
93 continue;
94 }
95 if (failure != .NOENT and failure != .NOTDIR) {
96 code = 126;
97 break;
98 }
99 }
100 const message = "tempo: command could not be executed\n";
101 _ = linux.write(2, message.ptr, message.len);
102 linux.exit(code);
103 }
104
105 test "executable lookup preserves absolute paths and empty PATH entries" {
106 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
107 defer arena.deinit();
108 const paths = try candidates(arena.allocator(), "echo", ":/bin");
109 try std.testing.expectEqualStrings("./echo", paths[0]);
110 try std.testing.expectEqualStrings("/bin/echo", paths[1]);
111 const absolute = try candidates(arena.allocator(), "/bin/echo", "/wrong");
112 try std.testing.expectEqual(@as(usize, 1), absolute.len);
113 try std.testing.expectEqualStrings("/bin/echo", absolute[0]);
114 }