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 {...@@ -1814,58 +1814,132 @@ test raiseFileDescriptorLimit {
1814}1814}
18151815
1816pub const CreateEnvironOptions = struct {1816pub const CreateEnvironOptions = struct {
1817 env_map: ?*const EnvMap = null,1817 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
1818 existing: ?[*:null]const ?[*:0]const u8 = null,1818 /// If non-null, negative means to remove the environment variable, and >= 0
1819 extra_usizes: []const ExtraUsize = &.{},1819 /// means to provide it with the given integer.
18201820 zig_progress_fd: ?i32 = null,
1821 pub const ExtraUsize = struct {
1822 name: []const u8,
1823 value: usize,
1824 };
1825};1821};
18261822
1827/// Creates a null-deliminated environment variable block in the format1823/// Creates a null-deliminated environment variable block in the format
1828/// expected by POSIX, by combining all the sources of key-value pairs together1824/// expected by POSIX, from a hash map plus options.
1829/// from `options`.1825pub fn createEnvironFromMap(
1830pub fn createEnviron(arena: Allocator, options: CreateEnvironOptions) Allocator.Error![:null]?[*:0]u8 {1826 arena: Allocator,
1831 const envp_count = c: {1827 map: *const EnvMap,
1832 var count: usize = 0;1828 options: CreateEnvironOptions,
1833 if (options.existing) |env| {1829) Allocator.Error![:null]?[*:0]u8 {
1834 while (env[count]) |_| : (count += 1) {}1830 const ZigProgressAction = enum { nothing, edit, delete, add };
1835 }1831 const zig_progress_action: ZigProgressAction = a: {
1836 if (options.env_map) |env_map| {1832 const fd = options.zig_progress_fd orelse break :a .nothing;
1837 count += env_map.count();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;
1838 }1838 }
1839 count += options.extra_usizes.len;1839 break :a .nothing;
1840 break :c count;
1841 };1840 };
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
1842 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);1848 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1843 var i: usize = 0;1849 var i: usize = 0;
18441850
1845 if (options.existing) |env| {1851 if (zig_progress_action == .add) {
1846 while (env[i]) |line| : (i += 1) {1852 envp_buf[i] = try std.fmt.allocPrintZ(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?});
1847 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));1853 i += 1;
1848 }
1849 }1854 }
18501855
1851 for (options.extra_usizes, envp_buf[i..][0..options.extra_usizes.len]) |extra_usize, *out| {1856 {
1852 out.* = try std.fmt.allocPrintZ(arena, "{s}={d}", .{ extra_usize.name, extra_usize.value });1857 var it = map.iterator();
1853 }1858 while (it.next()) |pair| {
1854 i += options.extra_usizes.len;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) {
1859 envp_buf[i] = try std.fmt.allocPrintZ(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* });1872 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");
1860 }1893 }
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;
1861 }1935 }
18621936
1863 assert(i == envp_count);1937 assert(i == envp_count);
1864 return envp_buf;1938 return envp_buf;
1865}1939}
18661940
1867pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) ![:null]?[*:0]u8 {1941pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 {
1868 return createEnviron(arena, .{ .env_map = env_map });1942 return createEnvironFromMap(arena, env_map, .{});
1869}1943}
18701944
1871test createNullDelimitedEnvMap {1945test createNullDelimitedEnvMap {
lib/std/process/Child.zig+31-33
...@@ -216,9 +216,9 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {...@@ -216,9 +216,9 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
216 .stdin = null,216 .stdin = null,
217 .stdout = null,217 .stdout = null,
218 .stderr = null,218 .stderr = null,
219 .stdin_behavior = StdIo.Inherit,219 .stdin_behavior = .Inherit,
220 .stdout_behavior = StdIo.Inherit,220 .stdout_behavior = .Inherit,
221 .stderr_behavior = StdIo.Inherit,221 .stderr_behavior = .Inherit,
222 .expand_arg0 = .no_expand,222 .expand_arg0 = .no_expand,
223 };223 };
224}224}
...@@ -549,22 +549,22 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -549,22 +549,22 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
549 // turns out, we `dup2` everything anyway, so there's no need!549 // turns out, we `dup2` everything anyway, so there's no need!
550 const pipe_flags: posix.O = .{ .CLOEXEC = true };550 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;552 const stdin_pipe = if (self.stdin_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
553 errdefer if (self.stdin_behavior == StdIo.Pipe) {553 errdefer if (self.stdin_behavior == .Pipe) {
554 destroyPipe(stdin_pipe);554 destroyPipe(stdin_pipe);
555 };555 };
556556
557 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;557 const stdout_pipe = if (self.stdout_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
558 errdefer if (self.stdout_behavior == StdIo.Pipe) {558 errdefer if (self.stdout_behavior == .Pipe) {
559 destroyPipe(stdout_pipe);559 destroyPipe(stdout_pipe);
560 };560 };
561561
562 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try posix.pipe2(pipe_flags) else undefined;562 const stderr_pipe = if (self.stderr_behavior == .Pipe) try posix.pipe2(pipe_flags) else undefined;
563 errdefer if (self.stderr_behavior == StdIo.Pipe) {563 errdefer if (self.stderr_behavior == .Pipe) {
564 destroyPipe(stderr_pipe);564 destroyPipe(stderr_pipe);
565 };565 };
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);
568 const dev_null_fd = if (any_ignore)568 const dev_null_fd = if (any_ignore)
569 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {569 posix.openZ("/dev/null", .{ .ACCMODE = .RDWR }, 0) catch |err| switch (err) {
570 error.PathAlreadyExists => unreachable,570 error.PathAlreadyExists => unreachable,
...@@ -609,35 +609,24 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -609,35 +609,24 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
609 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);609 const argv_buf = try arena.allocSentinel(?[*:0]const u8, self.argv.len, null);
610 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;610 for (self.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
611611
612 const prog_fileno = 3;
613
612 const envp: [*:null]const ?[*:0]const u8 = m: {614 const envp: [*:null]const ?[*:0]const u8 = m: {
613 const extra_usizes: []const process.CreateEnvironOptions.ExtraUsize = if (prog_pipe[1] == -1) &.{} else &.{615 const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
614 .{ .name = "ZIG_PROGRESS", .value = @intCast(prog_pipe[1]) },
615 };
616 if (self.env_map) |env_map| {616 if (self.env_map) |env_map| {
617 break :m (try process.createEnviron(arena, .{617 break :m (try process.createEnvironFromMap(arena, env_map, .{
618 .env_map = env_map,618 .zig_progress_fd = prog_fd,
619 .extra_usizes = extra_usizes,
620 })).ptr;619 })).ptr;
621 } else if (builtin.link_libc) {620 } else if (builtin.link_libc) {
622 if (extra_usizes.len == 0) {621 break :m (try process.createEnvironFromExisting(arena, std.c.environ, .{
623 break :m std.c.environ;622 .zig_progress_fd = prog_fd,
624 } else {623 })).ptr;
625 break :m (try process.createEnviron(arena, .{
626 .existing = std.c.environ,
627 .extra_usizes = extra_usizes,
628 })).ptr;
629 }
630 } else if (builtin.output_mode == .Exe) {624 } else if (builtin.output_mode == .Exe) {
631 // Then we have Zig start code and this works.625 // Then we have Zig start code and this works.
632 if (extra_usizes.len == 0) {626 // TODO type-safety for null-termination of `os.environ`.
633 break :m @ptrCast(std.os.environ.ptr);627 break :m (try process.createEnvironFromExisting(arena, @ptrCast(std.os.environ.ptr), .{
634 } else {628 .zig_progress_fd = prog_fd,
635 break :m (try process.createEnviron(arena, .{629 })).ptr;
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 }
641 } else {630 } else {
642 // TODO come up with a solution for this.631 // TODO come up with a solution for this.
643 @compileError("missing std lib enhancement: ChildProcess implementation has no way to collect the environment variables to forward to the child process");632 @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 {...@@ -664,6 +653,12 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
664 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);653 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
665 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);654 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
666 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);655 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
668 if (self.cwd_dir) |cwd| {663 if (self.cwd_dir) |cwd| {
669 posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);664 posix.fchdir(cwd.fd) catch |err| forkChildErrReport(err_pipe[1], err);
...@@ -718,6 +713,9 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -718,6 +713,9 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
718 posix.close(stderr_pipe[1]);713 posix.close(stderr_pipe[1]);
719 }714 }
720715
716 if (prog_pipe[1] != -1) {
717 posix.close(prog_pipe[1]);
718 }
721 self.progress_node.setIpcFd(prog_pipe[0]);719 self.progress_node.setIpcFd(prog_pipe[0]);
722}720}
723721