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 }