authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2026-10-09 15:08:22-05:00
committergravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2026-10-09 17:52:13-05:00
log5bec90b3bca72d4dbd8e41858224575ba0611ef5
treeb24b5a636d288ac56bcf1cc63aba23ff8f5db731
parent71f6780c98c0f84668385a901bb591d19aa3944d
signature Commit is signed but in an unrecognized format.

Io.Uring: update process API


1 files changed, 129 insertions(+), 77 deletions(-)

lib/std/Io/Uring.zig+129-77
...@@ -756,13 +756,13 @@ pub fn io(ev: *Evented) Io {...@@ -756,13 +756,13 @@ pub fn io(ev: *Evented) Io {
756 .processSetCurrentDir = processSetCurrentDir,756 .processSetCurrentDir = processSetCurrentDir,
757 .processSetCurrentPath = processSetCurrentPath,757 .processSetCurrentPath = processSetCurrentPath,
758 .processReplace = processReplace,758 .processReplace = processReplace,
759 .processReplacePath = processReplacePath,
760 .processSpawn = processSpawn,759 .processSpawn = processSpawn,
761 .processSpawnPath = processSpawnPath,
762 .childWait = childWait,760 .childWait = childWait,
763 .childKill = childKill,761 .childKill = childKill,
764762
765 .progressParentFile = progressParentFile,763 .progressParentFile = progressParentFile,
764 .inheritParentDir = inheritParentDir,
765 .inheritParentFile = inheritParentFile,
766766
767 .now = now,767 .now = now,
768 .clockResolution = clockResolution,768 .clockResolution = clockResolution,
...@@ -803,7 +803,7 @@ pub const InitOptions = struct {...@@ -803,7 +803,7 @@ pub const InitOptions = struct {
803 argv0: Argv0 = .empty,803 argv0: Argv0 = .empty,
804 /// Affects the following operations:804 /// Affects the following operations:
805 /// * `fileIsTty`805 /// * `fileIsTty`
806 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`806 /// * `processSpawn`, `processReplace`
807 environ: process.Environ = .empty,807 environ: process.Environ = .empty,
808};808};
809809
...@@ -4265,19 +4265,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -4265,19 +4265,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42654265
4266 var sync: CancelRegion.Sync = try .init(ev);4266 var sync: CancelRegion.Sync = try .init(ev);
4267 defer sync.deinit(ev);4267 defer sync.deinit(ev);
4268 return execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
4269}
42704268
4271fn processReplacePath(4269 switch (options.cwd) {
4272 userdata: ?*anyopaque,4270 .inherit => {},
4273 dir: Dir,4271 .dir => |cwd_dir| fchdir(&sync, cwd_dir.handle) catch |err| switch (err) {
4274 options: process.ReplaceOptions,4272 error.NoDevice, error.UnrecognizedVolume => return error.FileSystem,
4275) process.ReplaceError {4273 else => |e| return e,
4276 const ev: *Evented = @ptrCast(@alignCast(userdata));4274 },
4277 _ = ev;4275 .path => |cwd_path| {
4278 _ = dir;4276 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4279 _ = options;4277 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4280 @panic("TODO processReplacePath");4278 chdir(&sync, cwd_path_posix) catch |err| switch (err) {
4279 error.SymLinkLoop => return error.FileSystem,
4280 else => |e| return e,
4281 };
4282 },
4283 }
4284 for (options.inherit_dirs) |dir| try setFdFlags(&sync, dir.handle, 0);
4285 for (options.inherit_files) |file| try setFdFlags(&sync, file.handle, 0);
4286 if (options.start_suspended) {
4287 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4288 .SUCCESS => {},
4289 .PERM => return error.PermissionDenied,
4290 else => return error.Unexpected,
4291 }
4292 }
4293 return processExec(&sync, options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
4281}4294}
42824295
4283fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {4296fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
...@@ -4314,18 +4327,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp...@@ -4314,18 +4327,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
4314 return child_err;4327 return child_err;
4315}4328}
43164329
4317fn processSpawnPath(
4318 userdata: ?*anyopaque,
4319 dir: Dir,
4320 options: process.SpawnOptions,
4321) process.SpawnError!process.Child {
4322 const ev: *Evented = @ptrCast(@alignCast(userdata));
4323 _ = ev;
4324 _ = dir;
4325 _ = options;
4326 @panic("TODO processSpawnPath");
4327}
4328
4329const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);4330const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);
43304331
4331const Spawned = struct {4332const Spawned = struct {
...@@ -4541,6 +4542,9 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4541,6 +4542,9 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4541 },4542 },
4542 }4543 }
45434544
4545 for (options.spawn.inherit_dirs) |dir| try setFdFlags(sync, dir.handle, 0);
4546 for (options.spawn.inherit_files) |file| try setFdFlags(sync, file.handle, 0);
4547
4544 // Must happen after fchdir above, the cwd file descriptor might be4548 // Must happen after fchdir above, the cwd file descriptor might be
4545 // equal to prog_fileno and be clobbered by this dup2 call.4549 // equal to prog_fileno and be clobbered by this dup2 call.
4546 if (options.prog_pipe != -1) try dup2(sync, options.prog_pipe, prog_fileno);4550 if (options.prog_pipe != -1) try dup2(sync, options.prog_pipe, prog_fileno);
...@@ -4583,11 +4587,11 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4583,11 +4587,11 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4583 }4587 }
4584 }4588 }
45854589
4586 return execv(4590 return processExec(
4587 sync,4591 sync,
4588 options.spawn.expand_arg0,4592 options.spawn.exe,
4589 options.argv_buf.ptr[0].?,
4590 options.argv_buf.ptr,4593 options.argv_buf.ptr,
4594 options.spawn.expand_arg0,
4591 options.env_block,4595 options.env_block,
4592 options.PATH,4596 options.PATH,
4593 );4597 );
...@@ -4609,6 +4613,17 @@ fn setUpChildIo(...@@ -4609,6 +4613,17 @@ fn setUpChildIo(
4609 }4613 }
4610}4614}
46114615
4616fn setFdFlags(sync: *CancelRegion.Sync, fd: fd_t, flags: usize) (Io.Cancelable || Io.UnexpectedError)!void {
4617 while (true) {
4618 try sync.cancel_region.await(.nothing);
4619 switch (linux.errno(linux.fcntl(fd, linux.F.SETFD, flags))) {
4620 .SUCCESS => return,
4621 .INTR => {},
4622 else => |err| return unexpectedErrno(err),
4623 }
4624 }
4625}
4626
4612pub const DupError = error{4627pub const DupError = error{
4613 ProcessFdQuotaExceeded,4628 ProcessFdQuotaExceeded,
4614 SystemResources,4629 SystemResources,
...@@ -4628,63 +4643,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void...@@ -4628,63 +4643,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void
4628 }4643 }
4629}4644}
46304645
4631fn execv(4646fn processExec(
4632 sync: *CancelRegion.Sync,4647 sync: *CancelRegion.Sync,
4633 arg0_expand: process.ArgExpansion,4648 exe: process.ReplaceOptions.Exe,
4634 file: [*:0]const u8,4649 argv: [*:null]?[*:0]const u8,
4635 child_argv: [*:null]?[*:0]const u8,4650 expand_arg0: process.ArgExpansion,
4636 env_block: process.Environ.PosixBlock,4651 env_block: process.Environ.PosixBlock,
4637 PATH: []const u8,4652 PATH: []const u8,
4638) process.ReplaceError {4653) process.ReplaceError {
4639 const file_slice = std.mem.sliceTo(file, 0);4654 const arg0_slice = std.mem.span(argv[0].?);
4640 if (std.mem.findScalar(u8, file_slice, '/') != null)4655 exe: switch (exe) {
4641 return execvPath(sync, file, child_argv, env_block);4656 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
46424657 .path = .cwd(),
4643 // Use of PATH_MAX here is valid as the path_buf will be passed4658 } else .search,
4644 // directly to the operating system in posixExecvPath.4659 .search => {
4645 var path_buf: [PATH_MAX]u8 = undefined;4660 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
4646 var it = std.mem.tokenizeScalar(u8, PATH, ':');4661 var path_buf: [PATH_MAX]u8 = undefined;
4647 var seen_eacces = false;4662 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4648 var err: process.ReplaceError = error.FileNotFound;4663 var err: error{ FileNotFound, NotDir } = error.FileNotFound;
46494664
4650 // In case of expanding arg0 we must put it back if we return with an error.4665 while (it.next()) |search_path| {
4651 const prev_arg0 = child_argv[0];4666 const path_len = search_path.len + arg0_slice.len + 1;
4652 defer switch (arg0_expand) {4667 if (path_buf.len < path_len + 1) return error.NameTooLong;
4653 .expand => child_argv[0] = prev_arg0,4668 @memcpy(path_buf[0..search_path.len], search_path);
4654 .no_expand => {},4669 path_buf[search_path.len] = Dir.path.sep;
4655 };4670 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
46564671 path_buf[path_len] = 0;
4657 while (it.next()) |search_path| {4672 const full_path = path_buf[0..path_len :0].ptr;
4658 const path_len = search_path.len + file_slice.len + 1;4673 switch (expand_arg0) {
4659 if (path_buf.len < path_len + 1) return error.NameTooLong;4674 .expand => argv[0] = full_path,
4660 @memcpy(path_buf[0..search_path.len], search_path);4675 .no_expand => {},
4661 path_buf[search_path.len] = '/';4676 }
4662 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);4677 switch (processExecveat(sync, linux.AT.FDCWD, full_path, argv, env_block)) {
4663 path_buf[path_len] = 0;4678 error.AccessDenied => {},
4664 const full_path = path_buf[0..path_len :0].ptr;4679 error.FileNotFound, error.NotDir => |e| err = e,
4665 switch (arg0_expand) {4680 else => |e| return e,
4666 .expand => child_argv[0] = full_path,4681 }
4667 .no_expand => {},4682 }
4668 }4683 return err;
4669 err = execvPath(sync, full_path, child_argv, env_block);4684 },
4670 switch (err) {4685 .path => |dir| return processExecveat(sync, dir.handle, arg0_slice, argv, env_block),
4671 error.AccessDenied => seen_eacces = true,4686 .file => |file| return processExecveat(sync, file.handle, null, argv, env_block),
4672 error.FileNotFound, error.NotDir => {},4687 .explicit => |explicit| {
4673 else => |e| return e,4688 var path_buffer: [PATH_MAX]u8 = undefined;
4674 }4689 const path_posix = try pathToPosix(explicit.path, &path_buffer);
4690 return processExecveat(sync, explicit.dir.handle, path_posix, argv, env_block);
4691 },
4675 }4692 }
4676 if (seen_eacces) return error.AccessDenied;
4677 return err;
4678}4693}
4679/// This function ignores PATH environment variable.4694
4680pub fn execvPath(4695fn processExecveat(
4681 sync: *CancelRegion.Sync,4696 sync: *CancelRegion.Sync,
4682 path: [*:0]const u8,4697 dir: fd_t,
4683 child_argv: [*:null]const ?[*:0]const u8,4698 path: ?[*:0]const u8,
4699 argv: [*:null]const ?[*:0]const u8,
4684 env_block: process.Environ.PosixBlock,4700 env_block: process.Environ.PosixBlock,
4685) process.ReplaceError {4701) process.ReplaceError {
4686 try sync.cancel_region.await(.nothing);4702 try sync.cancel_region.await(.nothing);
4687 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {4703 const rc = if (path) |p|
4704 if (dir == linux.AT.FDCWD or Dir.path.isAbsoluteZ(p))
4705 linux.execve(p, argv, env_block.slice.ptr)
4706 else
4707 linux.execveat(dir, p, argv, env_block.slice.ptr, .{
4708 .EMPTY_PATH = false,
4709 .SYMLINK_NOFOLLOW = false,
4710 })
4711 else
4712 linux.execveat(dir, "", argv, env_block.slice.ptr, .{
4713 .EMPTY_PATH = true,
4714 .SYMLINK_NOFOLLOW = false,
4715 });
4716 switch (linux.errno(rc)) {
4688 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.4717 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
4689 .@"2BIG" => return error.SystemResources,4718 .@"2BIG" => return error.SystemResources,
4690 .MFILE => return error.ProcessFdQuotaExceeded,4719 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4702,6 +4731,7 @@ pub fn execvPath(...@@ -4702,6 +4731,7 @@ pub fn execvPath(
4702 .NOTDIR => return error.NotDir,4731 .NOTDIR => return error.NotDir,
4703 .TXTBSY => return error.FileBusy,4732 .TXTBSY => return error.FileBusy,
4704 .LIBBAD => return error.InvalidExe,4733 .LIBBAD => return error.InvalidExe,
4734 .NOSYS => return error.OperationUnsupported,
4705 else => |err| return unexpectedErrno(err),4735 else => |err| return unexpectedErrno(err),
4706 }4736 }
4707}4737}
...@@ -4850,6 +4880,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -4850,6 +4880,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
4850 return ev.environ.zig_progress_file;4880 return ev.environ.zig_progress_file;
4851}4881}
48524882
4883fn inheritParentHandle(ev: *Evented, handle: fd_t) Io.InheritParentHandleError!void {
4884 var sync: CancelRegion.Sync = try .init(ev);
4885 defer sync.deinit(ev);
4886 return setFdFlags(&sync, handle, linux.FD_CLOEXEC);
4887}
4888
4889fn inheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) Io.InheritParentHandleError!Dir {
4890 const ev: *Evented = @ptrCast(@alignCast(userdata));
4891 try inheritParentHandle(ev, handle);
4892 return .{ .handle = handle };
4893}
4894
4895fn inheritParentFile(
4896 userdata: ?*anyopaque,
4897 handle: File.Handle,
4898 flags: File.Flags,
4899) Io.InheritParentHandleError!File {
4900 const ev: *Evented = @ptrCast(@alignCast(userdata));
4901 try inheritParentHandle(ev, handle);
4902 return .{ .handle = handle, .flags = flags };
4903}
4904
4853fn scanEnviron(ev: *Evented) Io.Cancelable!void {4905fn scanEnviron(ev: *Evented) Io.Cancelable!void {
4854 const ev_io = ev.io();4906 const ev_io = ev.io();
4855 try ev.environ_mutex.lock(ev_io);4907 try ev.environ_mutex.lock(ev_io);