authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-10 19:47:29+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-10 11:20:01-07:00
log608024720688c268a8b5fdb06665b7ce66293ba1
tree114de522b26d6397fa7522bea319015a11cedf11
parente63e88c791d6661f599d3292025bfc152ead0f47

Merge pull request 'Io.Uring fixes & updates' (#37172) from sinon/zig:iouring-fixes into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37172 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

1 files changed, 159 insertions(+), 92 deletions(-)

lib/std/Io/Uring.zig+159-92
......@@ -756,13 +756,13 @@ pub fn io(ev: *Evented) Io {
756756 .processSetCurrentDir = processSetCurrentDir,
757757 .processSetCurrentPath = processSetCurrentPath,
758758 .processReplace = processReplace,
759 .processReplacePath = processReplacePath,
760759 .processSpawn = processSpawn,
761 .processSpawnPath = processSpawnPath,
762760 .childWait = childWait,
763761 .childKill = childKill,
764762
765763 .progressParentFile = progressParentFile,
764 .inheritParentDir = inheritParentDir,
765 .inheritParentFile = inheritParentFile,
766766
767767 .now = now,
768768 .clockResolution = clockResolution,
......@@ -804,7 +804,7 @@ pub const InitOptions = struct {
804804 argv0: Argv0 = .empty,
805805 /// Affects the following operations:
806806 /// * `fileIsTty`
807 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
807 /// * `processSpawn`, `processReplace`
808808 environ: process.Environ = .empty,
809809};
810810
......@@ -1230,7 +1230,8 @@ fn idle(ev: *Evented, thread: *Thread) void {
12301230 batch_userdata[0] = next;
12311231 }
12321232 break :ready_fiber switch (@as(u2, @truncate(next))) {
1233 0b00, 0b01 => @ptrFromInt(next & ~@as(usize, 0b11)),
1233 0b00 => @ptrFromInt(next),
1234 0b01 => null, // the timeout completion already woke this fiber
12341235 0b10, 0b11 => null,
12351236 };
12361237 },
......@@ -2401,6 +2402,10 @@ fn batchDrainSubmitted(
24012402 return error.ConcurrencyUnavailable
24022403 else
24032404 .{ .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o) },
2405 .net_accept => |o| {
2406 _ = o;
2407 @panic("TODO implement batchDrainSubmitted for net_receive");
2408 },
24042409 .net_receive => |o| {
24052410 _ = o;
24062411 @panic("TODO implement batchDrainSubmitted for net_receive");
......@@ -2479,7 +2484,10 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
24792484 if (@as(?Io.Operation.Result, result: switch (pending.tag) {
24802485 .file_read_streaming => .{
24812486 .file_read_streaming = switch (completion.errno()) {
2482 .SUCCESS => @as(u32, @bitCast(completion.result)),
2487 .SUCCESS => if (completion.result == 0)
2488 error.EndOfStream
2489 else
2490 @as(u32, @bitCast(completion.result)),
24832491 .INTR => 0,
24842492 .CANCELED => break :result null,
24852493 .INVAL => |err| errnoBug(err),
......@@ -2517,6 +2525,7 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
25172525 },
25182526 },
25192527 .device_io_control => unreachable,
2528 .net_accept => @panic("TODO"),
25202529 .net_receive => @panic("TODO"),
25212530 .net_send => @panic("TODO"),
25222531 .net_read => @panic("TODO"),
......@@ -4260,19 +4269,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42604269
42614270 var sync: CancelRegion.Sync = try .init(ev);
42624271 defer sync.deinit(ev);
4263 return execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
4264}
42654272
4266fn processReplacePath(
4267 userdata: ?*anyopaque,
4268 dir: Dir,
4269 options: process.ReplaceOptions,
4270) process.ReplaceError {
4271 const ev: *Evented = @ptrCast(@alignCast(userdata));
4272 _ = ev;
4273 _ = dir;
4274 _ = options;
4275 @panic("TODO processReplacePath");
4273 switch (options.cwd) {
4274 .inherit => {},
4275 .dir => |cwd_dir| fchdir(&sync, cwd_dir.handle) catch |err| switch (err) {
4276 error.NoDevice, error.UnrecognizedVolume => return error.FileSystem,
4277 else => |e| return e,
4278 },
4279 .path => |cwd_path| {
4280 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4281 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4282 chdir(&sync, cwd_path_posix) catch |err| switch (err) {
4283 error.SymLinkLoop => return error.FileSystem,
4284 else => |e| return e,
4285 };
4286 },
4287 }
4288 for (options.inherit_dirs) |dir| try setFdFlags(&sync, dir.handle, 0);
4289 for (options.inherit_files) |file| try setFdFlags(&sync, file.handle, 0);
4290 if (options.start_suspended) {
4291 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4292 .SUCCESS => {},
4293 .PERM => return error.PermissionDenied,
4294 else => return error.Unexpected,
4295 }
4296 }
4297 return processExec(&sync, options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
42764298}
42774299
42784300fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
......@@ -4309,18 +4331,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
43094331 return child_err;
43104332}
43114333
4312fn processSpawnPath(
4313 userdata: ?*anyopaque,
4314 dir: Dir,
4315 options: process.SpawnOptions,
4316) process.SpawnError!process.Child {
4317 const ev: *Evented = @ptrCast(@alignCast(userdata));
4318 _ = ev;
4319 _ = dir;
4320 _ = options;
4321 @panic("TODO processSpawnPath");
4322}
4323
43244334const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);
43254335
43264336const Spawned = struct {
......@@ -4504,6 +4514,20 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
45044514 PATH: []const u8,
45054515 spawn: process.SpawnOptions,
45064516}) ForkBailError {
4517 // Must happen before clobbering file descriptors below.
4518 switch (options.spawn.cwd) {
4519 .inherit => {},
4520 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
4521 .path => |cwd_path| {
4522 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4523 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4524 try chdir(sync, cwd_path_posix);
4525 },
4526 }
4527
4528 for (options.spawn.inherit_dirs) |dir| try setFdFlags(sync, dir.handle, 0);
4529 for (options.spawn.inherit_files) |file| try setFdFlags(sync, file.handle, 0);
4530
45074531 try setUpChildIo(
45084532 sync,
45094533 options.spawn.stdin,
......@@ -4526,20 +4550,15 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
45264550 options.dev_null_fd,
45274551 );
45284552
4529 switch (options.spawn.cwd) {
4530 .inherit => {},
4531 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
4532 .path => |cwd_path| {
4533 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4534 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4535 try chdir(sync, cwd_path_posix);
4536 },
4553 // Must happen after fchdir above because the cwd file descriptor might be
4554 // equal to prog_fileno and be clobbered by this operation.
4555 if (options.prog_pipe != -1) {
4556 if (options.prog_pipe == prog_fileno)
4557 try setFdFlags(sync, options.prog_pipe, 0)
4558 else
4559 try dup2(sync, options.prog_pipe, prog_fileno);
45374560 }
45384561
4539 // Must happen after fchdir above, the cwd file descriptor might be
4540 // equal to prog_fileno and be clobbered by this dup2 call.
4541 if (options.prog_pipe != -1) try dup2(sync, options.prog_pipe, prog_fileno);
4542
45434562 if (options.spawn.gid) |gid| {
45444563 switch (linux.errno(linux.setregid(gid, gid))) {
45454564 .SUCCESS => {},
......@@ -4571,18 +4590,18 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
45714590 }
45724591
45734592 if (options.spawn.start_suspended) {
4574 switch (linux.errno(linux.kill(0, .STOP))) {
4593 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
45754594 .SUCCESS => {},
45764595 .PERM => return error.PermissionDenied,
45774596 else => return error.Unexpected,
45784597 }
45794598 }
45804599
4581 return execv(
4600 return processExec(
45824601 sync,
4583 options.spawn.expand_arg0,
4584 options.argv_buf.ptr[0].?,
4602 options.spawn.exe,
45854603 options.argv_buf.ptr,
4604 options.spawn.expand_arg0,
45864605 options.env_block,
45874606 options.PATH,
45884607 );
......@@ -4604,6 +4623,17 @@ fn setUpChildIo(
46044623 }
46054624}
46064625
4626fn setFdFlags(sync: *CancelRegion.Sync, fd: fd_t, flags: usize) (Io.Cancelable || Io.UnexpectedError)!void {
4627 while (true) {
4628 try sync.cancel_region.await(.nothing);
4629 switch (linux.errno(linux.fcntl(fd, linux.F.SETFD, flags))) {
4630 .SUCCESS => return,
4631 .INTR => {},
4632 else => |err| return unexpectedErrno(err),
4633 }
4634 }
4635}
4636
46074637pub const DupError = error{
46084638 ProcessFdQuotaExceeded,
46094639 SystemResources,
......@@ -4623,63 +4653,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void
46234653 }
46244654}
46254655
4626fn execv(
4656fn processExec(
46274657 sync: *CancelRegion.Sync,
4628 arg0_expand: process.ArgExpansion,
4629 file: [*:0]const u8,
4630 child_argv: [*:null]?[*:0]const u8,
4658 exe: process.ReplaceOptions.Exe,
4659 argv: [*:null]?[*:0]const u8,
4660 expand_arg0: process.ArgExpansion,
46314661 env_block: process.Environ.PosixBlock,
46324662 PATH: []const u8,
46334663) process.ReplaceError {
4634 const file_slice = std.mem.sliceTo(file, 0);
4635 if (std.mem.findScalar(u8, file_slice, '/') != null)
4636 return execvPath(sync, file, child_argv, env_block);
4637
4638 // Use of PATH_MAX here is valid as the path_buf will be passed
4639 // directly to the operating system in posixExecvPath.
4640 var path_buf: [PATH_MAX]u8 = undefined;
4641 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4642 var seen_eacces = false;
4643 var err: process.ReplaceError = error.FileNotFound;
4644
4645 // In case of expanding arg0 we must put it back if we return with an error.
4646 const prev_arg0 = child_argv[0];
4647 defer switch (arg0_expand) {
4648 .expand => child_argv[0] = prev_arg0,
4649 .no_expand => {},
4650 };
4651
4652 while (it.next()) |search_path| {
4653 const path_len = search_path.len + file_slice.len + 1;
4654 if (path_buf.len < path_len + 1) return error.NameTooLong;
4655 @memcpy(path_buf[0..search_path.len], search_path);
4656 path_buf[search_path.len] = '/';
4657 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
4658 path_buf[path_len] = 0;
4659 const full_path = path_buf[0..path_len :0].ptr;
4660 switch (arg0_expand) {
4661 .expand => child_argv[0] = full_path,
4662 .no_expand => {},
4663 }
4664 err = execvPath(sync, full_path, child_argv, env_block);
4665 switch (err) {
4666 error.AccessDenied => seen_eacces = true,
4667 error.FileNotFound, error.NotDir => {},
4668 else => |e| return e,
4669 }
4664 const arg0_slice = std.mem.span(argv[0].?);
4665 exe: switch (exe) {
4666 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
4667 .path = .cwd(),
4668 } else .search,
4669 .search => {
4670 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
4671 var path_buf: [PATH_MAX]u8 = undefined;
4672 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4673 var err: error{ FileNotFound, NotDir } = error.FileNotFound;
4674
4675 while (it.next()) |search_path| {
4676 const path_len = search_path.len + arg0_slice.len + 1;
4677 if (path_buf.len < path_len + 1) return error.NameTooLong;
4678 @memcpy(path_buf[0..search_path.len], search_path);
4679 path_buf[search_path.len] = Dir.path.sep;
4680 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
4681 path_buf[path_len] = 0;
4682 const full_path = path_buf[0..path_len :0].ptr;
4683 switch (expand_arg0) {
4684 .expand => argv[0] = full_path,
4685 .no_expand => {},
4686 }
4687 switch (processExecveat(sync, linux.AT.FDCWD, full_path, argv, env_block)) {
4688 error.AccessDenied => {},
4689 error.FileNotFound, error.NotDir => |e| err = e,
4690 else => |e| return e,
4691 }
4692 }
4693 return err;
4694 },
4695 .path => |dir| return processExecveat(sync, dir.handle, arg0_slice, argv, env_block),
4696 .file => |file| return processExecveat(sync, file.handle, null, argv, env_block),
4697 .explicit => |explicit| {
4698 var path_buffer: [PATH_MAX]u8 = undefined;
4699 const path_posix = try pathToPosix(explicit.path, &path_buffer);
4700 return processExecveat(sync, explicit.dir.handle, path_posix, argv, env_block);
4701 },
46704702 }
4671 if (seen_eacces) return error.AccessDenied;
4672 return err;
46734703}
4674/// This function ignores PATH environment variable.
4675pub fn execvPath(
4704
4705fn processExecveat(
46764706 sync: *CancelRegion.Sync,
4677 path: [*:0]const u8,
4678 child_argv: [*:null]const ?[*:0]const u8,
4707 dir: fd_t,
4708 path: ?[*:0]const u8,
4709 argv: [*:null]const ?[*:0]const u8,
46794710 env_block: process.Environ.PosixBlock,
46804711) process.ReplaceError {
46814712 try sync.cancel_region.await(.nothing);
4682 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {
4713 const rc = if (path) |p|
4714 if (dir == linux.AT.FDCWD or Dir.path.isAbsoluteZ(p))
4715 linux.execve(p, argv, env_block.slice.ptr)
4716 else
4717 linux.execveat(dir, p, argv, env_block.slice.ptr, .{
4718 .EMPTY_PATH = false,
4719 .SYMLINK_NOFOLLOW = false,
4720 })
4721 else
4722 linux.execveat(dir, "", argv, env_block.slice.ptr, .{
4723 .EMPTY_PATH = true,
4724 .SYMLINK_NOFOLLOW = false,
4725 });
4726 switch (linux.errno(rc)) {
46834727 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
46844728 .@"2BIG" => return error.SystemResources,
46854729 .MFILE => return error.ProcessFdQuotaExceeded,
......@@ -4697,6 +4741,7 @@ pub fn execvPath(
46974741 .NOTDIR => return error.NotDir,
46984742 .TXTBSY => return error.FileBusy,
46994743 .LIBBAD => return error.InvalidExe,
4744 .NOSYS => return error.OperationUnsupported,
47004745 else => |err| return unexpectedErrno(err),
47014746 }
47024747}
......@@ -4845,6 +4890,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
48454890 return ev.environ.zig_progress_file;
48464891}
48474892
4893fn inheritParentHandle(ev: *Evented, handle: fd_t) Io.InheritParentHandleError!void {
4894 var sync: CancelRegion.Sync = try .init(ev);
4895 defer sync.deinit(ev);
4896 return setFdFlags(&sync, handle, linux.FD_CLOEXEC);
4897}
4898
4899fn inheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) Io.InheritParentHandleError!Dir {
4900 const ev: *Evented = @ptrCast(@alignCast(userdata));
4901 try inheritParentHandle(ev, handle);
4902 return .{ .handle = handle };
4903}
4904
4905fn inheritParentFile(
4906 userdata: ?*anyopaque,
4907 handle: File.Handle,
4908 flags: File.Flags,
4909) Io.InheritParentHandleError!File {
4910 const ev: *Evented = @ptrCast(@alignCast(userdata));
4911 try inheritParentHandle(ev, handle);
4912 return .{ .handle = handle, .flags = flags };
4913}
4914
48484915fn scanEnviron(ev: *Evented) Io.Cancelable!void {
48494916 const ev_io = ev.io();
48504917 try ev.environ_mutex.lock(ev_io);