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 {...@@ -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,
...@@ -804,7 +804,7 @@ pub const InitOptions = struct {...@@ -804,7 +804,7 @@ pub const InitOptions = struct {
804 argv0: Argv0 = .empty,804 argv0: Argv0 = .empty,
805 /// Affects the following operations:805 /// Affects the following operations:
806 /// * `fileIsTty`806 /// * `fileIsTty`
807 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`807 /// * `processSpawn`, `processReplace`
808 environ: process.Environ = .empty,808 environ: process.Environ = .empty,
809};809};
810810
...@@ -1230,7 +1230,8 @@ fn idle(ev: *Evented, thread: *Thread) void {...@@ -1230,7 +1230,8 @@ fn idle(ev: *Evented, thread: *Thread) void {
1230 batch_userdata[0] = next;1230 batch_userdata[0] = next;
1231 }1231 }
1232 break :ready_fiber switch (@as(u2, @truncate(next))) {1232 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
1234 0b10, 0b11 => null,1235 0b10, 0b11 => null,
1235 };1236 };
1236 },1237 },
...@@ -2401,6 +2402,10 @@ fn batchDrainSubmitted(...@@ -2401,6 +2402,10 @@ fn batchDrainSubmitted(
2401 return error.ConcurrencyUnavailable2402 return error.ConcurrencyUnavailable
2402 else2403 else
2403 .{ .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o) },2404 .{ .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 },
2404 .net_receive => |o| {2409 .net_receive => |o| {
2405 _ = o;2410 _ = o;
2406 @panic("TODO implement batchDrainSubmitted for net_receive");2411 @panic("TODO implement batchDrainSubmitted for net_receive");
...@@ -2479,7 +2484,10 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {...@@ -2479,7 +2484,10 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
2479 if (@as(?Io.Operation.Result, result: switch (pending.tag) {2484 if (@as(?Io.Operation.Result, result: switch (pending.tag) {
2480 .file_read_streaming => .{2485 .file_read_streaming => .{
2481 .file_read_streaming = switch (completion.errno()) {2486 .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)),
2483 .INTR => 0,2491 .INTR => 0,
2484 .CANCELED => break :result null,2492 .CANCELED => break :result null,
2485 .INVAL => |err| errnoBug(err),2493 .INVAL => |err| errnoBug(err),
...@@ -2517,6 +2525,7 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {...@@ -2517,6 +2525,7 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
2517 },2525 },
2518 },2526 },
2519 .device_io_control => unreachable,2527 .device_io_control => unreachable,
2528 .net_accept => @panic("TODO"),
2520 .net_receive => @panic("TODO"),2529 .net_receive => @panic("TODO"),
2521 .net_send => @panic("TODO"),2530 .net_send => @panic("TODO"),
2522 .net_read => @panic("TODO"),2531 .net_read => @panic("TODO"),
...@@ -4260,19 +4269,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -4260,19 +4269,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42604269
4261 var sync: CancelRegion.Sync = try .init(ev);4270 var sync: CancelRegion.Sync = try .init(ev);
4262 defer sync.deinit(ev);4271 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(4273 switch (options.cwd) {
4267 userdata: ?*anyopaque,4274 .inherit => {},
4268 dir: Dir,4275 .dir => |cwd_dir| fchdir(&sync, cwd_dir.handle) catch |err| switch (err) {
4269 options: process.ReplaceOptions,4276 error.NoDevice, error.UnrecognizedVolume => return error.FileSystem,
4270) process.ReplaceError {4277 else => |e| return e,
4271 const ev: *Evented = @ptrCast(@alignCast(userdata));4278 },
4272 _ = ev;4279 .path => |cwd_path| {
4273 _ = dir;4280 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4274 _ = options;4281 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4275 @panic("TODO processReplacePath");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);
4276}4298}
42774299
4278fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {4300fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
...@@ -4309,18 +4331,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp...@@ -4309,18 +4331,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
4309 return child_err;4331 return child_err;
4310}4332}
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
4324const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);4334const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);
43254335
4326const Spawned = struct {4336const Spawned = struct {
...@@ -4504,6 +4514,20 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4504,6 +4514,20 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4504 PATH: []const u8,4514 PATH: []const u8,
4505 spawn: process.SpawnOptions,4515 spawn: process.SpawnOptions,
4506}) ForkBailError {4516}) 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
4507 try setUpChildIo(4531 try setUpChildIo(
4508 sync,4532 sync,
4509 options.spawn.stdin,4533 options.spawn.stdin,
...@@ -4526,20 +4550,15 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4526,20 +4550,15 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4526 options.dev_null_fd,4550 options.dev_null_fd,
4527 );4551 );
45284552
4529 switch (options.spawn.cwd) {4553 // Must happen after fchdir above because the cwd file descriptor might be
4530 .inherit => {},4554 // equal to prog_fileno and be clobbered by this operation.
4531 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),4555 if (options.prog_pipe != -1) {
4532 .path => |cwd_path| {4556 if (options.prog_pipe == prog_fileno)
4533 var cwd_path_buffer: [PATH_MAX]u8 = undefined;4557 try setFdFlags(sync, options.prog_pipe, 0)
4534 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);4558 else
4535 try chdir(sync, cwd_path_posix);4559 try dup2(sync, options.prog_pipe, prog_fileno);
4536 },
4537 }4560 }
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
4543 if (options.spawn.gid) |gid| {4562 if (options.spawn.gid) |gid| {
4544 switch (linux.errno(linux.setregid(gid, gid))) {4563 switch (linux.errno(linux.setregid(gid, gid))) {
4545 .SUCCESS => {},4564 .SUCCESS => {},
...@@ -4571,18 +4590,18 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4571,18 +4590,18 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4571 }4590 }
45724591
4573 if (options.spawn.start_suspended) {4592 if (options.spawn.start_suspended) {
4574 switch (linux.errno(linux.kill(0, .STOP))) {4593 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4575 .SUCCESS => {},4594 .SUCCESS => {},
4576 .PERM => return error.PermissionDenied,4595 .PERM => return error.PermissionDenied,
4577 else => return error.Unexpected,4596 else => return error.Unexpected,
4578 }4597 }
4579 }4598 }
45804599
4581 return execv(4600 return processExec(
4582 sync,4601 sync,
4583 options.spawn.expand_arg0,4602 options.spawn.exe,
4584 options.argv_buf.ptr[0].?,
4585 options.argv_buf.ptr,4603 options.argv_buf.ptr,
4604 options.spawn.expand_arg0,
4586 options.env_block,4605 options.env_block,
4587 options.PATH,4606 options.PATH,
4588 );4607 );
...@@ -4604,6 +4623,17 @@ fn setUpChildIo(...@@ -4604,6 +4623,17 @@ fn setUpChildIo(
4604 }4623 }
4605}4624}
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
4607pub const DupError = error{4637pub const DupError = error{
4608 ProcessFdQuotaExceeded,4638 ProcessFdQuotaExceeded,
4609 SystemResources,4639 SystemResources,
...@@ -4623,63 +4653,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void...@@ -4623,63 +4653,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void
4623 }4653 }
4624}4654}
46254655
4626fn execv(4656fn processExec(
4627 sync: *CancelRegion.Sync,4657 sync: *CancelRegion.Sync,
4628 arg0_expand: process.ArgExpansion,4658 exe: process.ReplaceOptions.Exe,
4629 file: [*:0]const u8,4659 argv: [*:null]?[*:0]const u8,
4630 child_argv: [*:null]?[*:0]const u8,4660 expand_arg0: process.ArgExpansion,
4631 env_block: process.Environ.PosixBlock,4661 env_block: process.Environ.PosixBlock,
4632 PATH: []const u8,4662 PATH: []const u8,
4633) process.ReplaceError {4663) process.ReplaceError {
4634 const file_slice = std.mem.sliceTo(file, 0);4664 const arg0_slice = std.mem.span(argv[0].?);
4635 if (std.mem.findScalar(u8, file_slice, '/') != null)4665 exe: switch (exe) {
4636 return execvPath(sync, file, child_argv, env_block);4666 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
46374667 .path = .cwd(),
4638 // Use of PATH_MAX here is valid as the path_buf will be passed4668 } else .search,
4639 // directly to the operating system in posixExecvPath.4669 .search => {
4640 var path_buf: [PATH_MAX]u8 = undefined;4670 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
4641 var it = std.mem.tokenizeScalar(u8, PATH, ':');4671 var path_buf: [PATH_MAX]u8 = undefined;
4642 var seen_eacces = false;4672 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4643 var err: process.ReplaceError = error.FileNotFound;4673 var err: error{ FileNotFound, NotDir } = error.FileNotFound;
46444674
4645 // In case of expanding arg0 we must put it back if we return with an error.4675 while (it.next()) |search_path| {
4646 const prev_arg0 = child_argv[0];4676 const path_len = search_path.len + arg0_slice.len + 1;
4647 defer switch (arg0_expand) {4677 if (path_buf.len < path_len + 1) return error.NameTooLong;
4648 .expand => child_argv[0] = prev_arg0,4678 @memcpy(path_buf[0..search_path.len], search_path);
4649 .no_expand => {},4679 path_buf[search_path.len] = Dir.path.sep;
4650 };4680 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
46514681 path_buf[path_len] = 0;
4652 while (it.next()) |search_path| {4682 const full_path = path_buf[0..path_len :0].ptr;
4653 const path_len = search_path.len + file_slice.len + 1;4683 switch (expand_arg0) {
4654 if (path_buf.len < path_len + 1) return error.NameTooLong;4684 .expand => argv[0] = full_path,
4655 @memcpy(path_buf[0..search_path.len], search_path);4685 .no_expand => {},
4656 path_buf[search_path.len] = '/';4686 }
4657 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);4687 switch (processExecveat(sync, linux.AT.FDCWD, full_path, argv, env_block)) {
4658 path_buf[path_len] = 0;4688 error.AccessDenied => {},
4659 const full_path = path_buf[0..path_len :0].ptr;4689 error.FileNotFound, error.NotDir => |e| err = e,
4660 switch (arg0_expand) {4690 else => |e| return e,
4661 .expand => child_argv[0] = full_path,4691 }
4662 .no_expand => {},4692 }
4663 }4693 return err;
4664 err = execvPath(sync, full_path, child_argv, env_block);4694 },
4665 switch (err) {4695 .path => |dir| return processExecveat(sync, dir.handle, arg0_slice, argv, env_block),
4666 error.AccessDenied => seen_eacces = true,4696 .file => |file| return processExecveat(sync, file.handle, null, argv, env_block),
4667 error.FileNotFound, error.NotDir => {},4697 .explicit => |explicit| {
4668 else => |e| return e,4698 var path_buffer: [PATH_MAX]u8 = undefined;
4669 }4699 const path_posix = try pathToPosix(explicit.path, &path_buffer);
4700 return processExecveat(sync, explicit.dir.handle, path_posix, argv, env_block);
4701 },
4670 }4702 }
4671 if (seen_eacces) return error.AccessDenied;
4672 return err;
4673}4703}
4674/// This function ignores PATH environment variable.4704
4675pub fn execvPath(4705fn processExecveat(
4676 sync: *CancelRegion.Sync,4706 sync: *CancelRegion.Sync,
4677 path: [*:0]const u8,4707 dir: fd_t,
4678 child_argv: [*:null]const ?[*:0]const u8,4708 path: ?[*:0]const u8,
4709 argv: [*:null]const ?[*:0]const u8,
4679 env_block: process.Environ.PosixBlock,4710 env_block: process.Environ.PosixBlock,
4680) process.ReplaceError {4711) process.ReplaceError {
4681 try sync.cancel_region.await(.nothing);4712 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)) {
4683 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.4727 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
4684 .@"2BIG" => return error.SystemResources,4728 .@"2BIG" => return error.SystemResources,
4685 .MFILE => return error.ProcessFdQuotaExceeded,4729 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4697,6 +4741,7 @@ pub fn execvPath(...@@ -4697,6 +4741,7 @@ pub fn execvPath(
4697 .NOTDIR => return error.NotDir,4741 .NOTDIR => return error.NotDir,
4698 .TXTBSY => return error.FileBusy,4742 .TXTBSY => return error.FileBusy,
4699 .LIBBAD => return error.InvalidExe,4743 .LIBBAD => return error.InvalidExe,
4744 .NOSYS => return error.OperationUnsupported,
4700 else => |err| return unexpectedErrno(err),4745 else => |err| return unexpectedErrno(err),
4701 }4746 }
4702}4747}
...@@ -4845,6 +4890,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -4845,6 +4890,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
4845 return ev.environ.zig_progress_file;4890 return ev.environ.zig_progress_file;
4846}4891}
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
4848fn scanEnviron(ev: *Evented) Io.Cancelable!void {4915fn scanEnviron(ev: *Evented) Io.Cancelable!void {
4849 const ev_io = ev.io();4916 const ev_io = ev.io();
4850 try ev.environ_mutex.lock(ev_io);4917 try ev.environ_mutex.lock(ev_io);