authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-23 20:22:58-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-27 20:56:48-07:00
logc01cfde6882840980cbf63593eafb5e1090b1ff0
treefab6d65ab9f4552e66f6e61f9867a5ccb5d9cdfa
parent3a768bd6ce9e891ad258b32fb2dd0ed912ef9e9b

std.process.Child: fix ZIG_PROGRESS env var handling

and properly dup2 the file descriptor to make it handle the case when other files are already open

2 files changed, 138 insertions(+), 66 deletions(-)

lib/std/process.zig+107-33
......@@ -1814,58 +1814,132 @@ test raiseFileDescriptorLimit {
18141814}
18151815
18161816pub const CreateEnvironOptions = struct {
1817 env_map: ?*const EnvMap = null,
1818 existing: ?[*:null]const ?[*:0]const u8 = null,
1819 extra_usizes: []const ExtraUsize = &.{},
1820
1821 pub const ExtraUsize = struct {
1822 name: []const u8,
1823 value: usize,
1824 };
1817 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
1818 /// If non-null, negative means to remove the environment variable, and >= 0
1819 /// means to provide it with the given integer.
1820 zig_progress_fd: ?i32 = null,
18251821};
18261822
18271823/// Creates a null-deliminated environment variable block in the format
1828/// expected by POSIX, by combining all the sources of key-value pairs together
1829/// from `options`.
1830pub fn createEnviron(arena: Allocator, options: CreateEnvironOptions) Allocator.Error![:null]?[*:0]u8 {
1831 const envp_count = c: {
1832 var count: usize = 0;
1833 if (options.existing) |env| {
1834 while (env[count]) |_| : (count += 1) {}
1835 }
1836 if (options.env_map) |env_map| {
1837 count += env_map.count();
1824/// expected by POSIX, from a hash map plus options.
1825pub fn createEnvironFromMap(
1826 arena: Allocator,
1827 map: *const EnvMap,
1828 options: CreateEnvironOptions,
1829) Allocator.Error![:null]?[*:0]u8 {
1830 const ZigProgressAction = enum { nothing, edit, delete, add };
1831 const zig_progress_action: ZigProgressAction = a: {
1832 const fd = options.zig_progress_fd orelse break :a .nothing;
1833 const contains = map.get("ZIG_PROGRESS") != null;
1834 if (fd >= 0) {
1835 break :a if (contains) .edit else .add;
1836 } else {
1837 if (contains) break :a .delete;
18381838 }
1839 count += options.extra_usizes.len;
1840 break :c count;
1839 break :a .nothing;
18411840 };
1841
1842 const envp_count: usize = @intCast(@as(isize, map.count()) + @as(isize, switch (zig_progress_action) {
1843 .add => 1,
1844 .delete => -1,
1845 .nothing, .edit => 0,
1846 }));
1847
18421848 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
18431849 var i: usize = 0;
18441850
1845 if (options.existing) |env| {
1846 while (env[i]) |line| : (i += 1) {
1847 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
1848 }
1851 if (zig_progress_action == .add) {
1852 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1853 i += 1;
18491854 }
18501855
1851 for (options.extra_usizes, envp_buf[i..][0..options.extra_usizes.len]) |extra_usize, *out| {
1852 out.* = try std.fmt.allocPrintZ(arena, "{s}={d}", .{ extra_usize.name, extra_usize.value });
1853 }
1854 i += options.extra_usizes.len;
1856 {
1857 var it = map.iterator();
1858 while (it.next()) |pair| {
1859 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
1860 .add => unreachable,
1861 .delete => continue,
1862 .edit => {
1863 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={d}", .{
1864 pair.key_ptr.*, options.zig_progress_fd.?,
1865 });
1866 i += 1;
1867 continue;
1868 },
1869 .nothing => {},
1870 };
18551871
1856 if (options.env_map) |env_map| {
1857 var it = env_map.iterator();
1858 while (it.next()) |pair| : (i += 1) {
18591872 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* });
1873 i += 1;
1874 }
1875 }
1876
1877 assert(i == envp_count);
1878 return envp_buf;
1879}
1880
1881/// Creates a null-deliminated environment variable block in the format
1882/// expected by POSIX, from a hash map plus options.
1883pub fn createEnvironFromExisting(
1884 arena: Allocator,
1885 existing: [*:null]const ?[*:0]const u8,
1886 options: CreateEnvironOptions,
1887) Allocator.Error![:null]?[*:0]u8 {
1888 const existing_count, const contains_zig_progress = c: {
1889 var count: usize = 0;
1890 var contains = false;
1891 while (existing[count]) |line| : (count += 1) {
1892 contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
18601893 }
1894 break :c .{ count, contains };
1895 };
1896 const ZigProgressAction = enum { nothing, edit, delete, add };
1897 const zig_progress_action: ZigProgressAction = a: {
1898 const fd = options.zig_progress_fd orelse break :a .nothing;
1899 if (fd >= 0) {
1900 break :a if (contains_zig_progress) .edit else .add;
1901 } else {
1902 if (contains_zig_progress) break :a .delete;
1903 }
1904 break :a .nothing;
1905 };
1906
1907 const envp_count: usize = @intCast(@as(isize, @intCast(existing_count)) + @as(isize, switch (zig_progress_action) {
1908 .add => 1,
1909 .delete => -1,
1910 .nothing, .edit => 0,
1911 }));
1912
1913 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1914 var i: usize = 0;
1915 var existing_index: usize = 0;
1916
1917 if (zig_progress_action == .add) {
1918 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1919 i += 1;
1920 }
1921
1922 while (existing[existing_index]) |line| : (existing_index += 1) {
1923 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
1924 .add => unreachable,
1925 .delete => continue,
1926 .edit => {
1927 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1928 i += 1;
1929 continue;
1930 },
1931 .nothing => {},
1932 };
1933 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
1934 i += 1;
18611935 }
18621936
18631937 assert(i == envp_count);
18641938 return envp_buf;
18651939}
18661940
1867pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) ![:null]?[*:0]u8 {
1868 return createEnviron(arena, .{ .env_map = env_map });
1941pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 {
1942 return createEnvironFromMap(arena, env_map, .{});
18691943}
18701944
18711945test createNullDelimitedEnvMap {
lib/std/process/Child.zig+31-33
......@@ -216,9 +216,9 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
216216 .stdin = null,
217217 .stdout = null,
218218 .stderr = null,
219 .stdin_behavior = StdIo.Inherit,
220 .stdout_behavior = StdIo.Inherit,
221 .stderr_behavior = StdIo.Inherit,
219 .stdin_behavior = .Inherit,
220 .stdout_behavior = .Inherit,
221 .stderr_behavior = .Inherit,
222222 .expand_arg0 = .no_expand,
223223 };
224224}
......@@ -549,22 +549,22 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
549549 // turns out, we `dup2` everything anyway, so there's no need!
550550 const pipe_flags: posix.O = .{ .CLOEXEC = true };
551551
552 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
553 errdefer if (self.stdin_behavior == StdIo.Pipe) {
552 const stdin_pipe = if (self.stdin_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
553 errdefer if (self.stdin_behavior == .Pipe) {
554554 destroyPipe(stdin_pipe);
555555 };
556556
557 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
558 errdefer if (self.stdout_behavior == StdIo.Pipe) {
557 const stdout_pipe = if (self.stdout_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
558 errdefer if (self.stdout_behavior == .Pipe) {
559559 destroyPipe(stdout_pipe);
560560 };
561561
562 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;
563 errdefer if (self.stderr_behavior == StdIo.Pipe) {
562 const stderr_pipe = if (self.stderr_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
563 errdefer if (self.stderr_behavior == .Pipe) {
564564 destroyPipe(stderr_pipe);
565565 };
566566
567 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
567 const any_ignore = (self.stdin_behavior == .Ignore or self.stdout_behavior == .Ignore or self.stderr_behavior == .Ignore);
568568 const dev_null_fd = if (any_ignore)
569569 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
570570 error.PathAlreadyExists => unreachable,
......@@ -609,35 +609,24 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
609609 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
610610 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
611611
612 const prog_fileno = 3;
613
612614 const envp: [*:null]const ?[*:0]const u8 = m: {
613 const extra_usizes: []const process.CreateEnvironOptions.ExtraUsize = if (prog_pipe[1] == -1) &.{} else &.{
614 .{ .name = "ZIG_PROGRESS", .value = @intCast(prog_pipe[1]) },
615 };
615 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
616616 if (self.env_map) |env_map| {
617 break :m (try process.createEnviron(arena, .{
618 .env_map = env_map,
619 .extra_usizes = extra_usizes,
617 break :m (try process.createEnvironFromMap(arena, env_map, .{
618 .zig_progress_fd = prog_fd,
620619 })).ptr;
621620 } else if (builtin.link_libc) {
622 if (extra_usizes.len == 0) {
623 break :m std.c.environ;
624 } else {
625 break :m (try process.createEnviron(arena, .{
626 .existing = std.c.environ,
627 .extra_usizes = extra_usizes,
628 })).ptr;
629 }
621 break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{
622 .zig_progress_fd = prog_fd,
623 })).ptr;
630624 } else if (builtin.output_mode == .Exe) {
631625 // Then we have Zig start code and this works.
632 if (extra_usizes.len == 0) {
633 break :m @ptrCast(std.os.environ.ptr);
634 } else {
635 break :m (try process.createEnviron(arena, .{
636 // TODO type-safety for null-termination of `os.environ`.
637 .existing = @ptrCast(std.os.environ.ptr),
638 .extra_usizes = extra_usizes,
639 })).ptr;
640 }
626 // TODO type-safety for null-termination of `os.environ`.
627 break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{
628 .zig_progress_fd = prog_fd,
629 })).ptr;
641630 } else {
642631 // TODO come up with a solution for this.
643632 @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process");
......@@ -664,6 +653,12 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
664653 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
665654 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
666655 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
656 if (prog_pipe[1] != -1) posix.dup2(prog_pipe[1], prog_fileno) catch |err| forkChildErrReport(err_pipe[1], err);
657
658 if (prog_pipe[1] != -1) {
659 if (prog_pipe[0] != prog_fileno) posix.close(prog_pipe[0]);
660 if (prog_pipe[1] != prog_fileno) posix.close(prog_pipe[1]);
661 }
667662
668663 if (self.cwd_dir) |cwd| {
669664 posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);
......@@ -718,6 +713,9 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
718713 posix.close(stderr_pipe[1]);
719714 }
720715
716 if (prog_pipe[1] != -1) {
717 posix.close(prog_pipe[1]);
718 }
721719 self.progress_node.setIpcFd(prog_pipe[0]);
722720}
723721