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 19:47:29+02:00
log9449bac4226b659abe7ab190cc5603d47086b53c
tree757c692a324a02d7d5bd4bd935aaac47d0af6382
parent44e5319107f5d5f7b5bd4cc9917b038196999857
parent18b98cee0653ea539514dc14533a82c577511d7e

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,
......@@ -803,7 +803,7 @@ pub const InitOptions = struct {
803803 argv0: Argv0 = .empty,
804804 /// Affects the following operations:
805805 /// * `fileIsTty`
806 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
806 /// * `processSpawn`, `processReplace`
807807 environ: process.Environ = .empty,
808808};
809809
......@@ -1229,7 +1229,8 @@ fn idle(ev: *Evented, thread: *Thread) void {
12291229 batch_userdata[0] = next;
12301230 }
12311231 break :ready_fiber switch (@as(u2, @truncate(next))) {
1232 0b00, 0b01 => @ptrFromInt(next & ~@as(usize, 0b11)),
1232 0b00 => @ptrFromInt(next),
1233 0b01 => null, // the timeout completion already woke this fiber
12331234 0b10, 0b11 => null,
12341235 };
12351236 },
......@@ -2406,6 +2407,10 @@ fn batchDrainSubmitted(
24062407 return error.ConcurrencyUnavailable
24072408 else
24082409 .{ .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o) },
2410 .net_accept => |o| {
2411 _ = o;
2412 @panic("TODO implement batchDrainSubmitted for net_receive");
2413 },
24092414 .net_receive => |o| {
24102415 _ = o;
24112416 @panic("TODO implement batchDrainSubmitted for net_receive");
......@@ -2484,7 +2489,10 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
24842489 if (@as(?Io.Operation.Result, result: switch (pending.tag) {
24852490 .file_read_streaming => .{
24862491 .file_read_streaming = switch (completion.errno()) {
2487 .SUCCESS => @as(u32, @bitCast(completion.result)),
2492 .SUCCESS => if (completion.result == 0)
2493 error.EndOfStream
2494 else
2495 @as(u32, @bitCast(completion.result)),
24882496 .INTR => 0,
24892497 .CANCELED => break :result null,
24902498 .INVAL => |err| errnoBug(err),
......@@ -2522,6 +2530,7 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
25222530 },
25232531 },
25242532 .device_io_control => unreachable,
2533 .net_accept => @panic("TODO"),
25252534 .net_receive => @panic("TODO"),
25262535 .net_send => @panic("TODO"),
25272536 .net_read => @panic("TODO"),
......@@ -4265,19 +4274,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42654274
42664275 var sync: CancelRegion.Sync = try .init(ev);
42674276 defer sync.deinit(ev);
4268 return execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
4269}
42704277
4271fn processReplacePath(
4272 userdata: ?*anyopaque,
4273 dir: Dir,
4274 options: process.ReplaceOptions,
4275) process.ReplaceError {
4276 const ev: *Evented = @ptrCast(@alignCast(userdata));
4277 _ = ev;
4278 _ = dir;
4279 _ = options;
4280 @panic("TODO processReplacePath");
4278 switch (options.cwd) {
4279 .inherit => {},
4280 .dir => |cwd_dir| fchdir(&sync, cwd_dir.handle) catch |err| switch (err) {
4281 error.NoDevice, error.UnrecognizedVolume => return error.FileSystem,
4282 else => |e| return e,
4283 },
4284 .path => |cwd_path| {
4285 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4286 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4287 chdir(&sync, cwd_path_posix) catch |err| switch (err) {
4288 error.SymLinkLoop => return error.FileSystem,
4289 else => |e| return e,
4290 };
4291 },
4292 }
4293 for (options.inherit_dirs) |dir| try setFdFlags(&sync, dir.handle, 0);
4294 for (options.inherit_files) |file| try setFdFlags(&sync, file.handle, 0);
4295 if (options.start_suspended) {
4296 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4297 .SUCCESS => {},
4298 .PERM => return error.PermissionDenied,
4299 else => return error.Unexpected,
4300 }
4301 }
4302 return processExec(&sync, options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
42814303}
42824304
42834305fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
......@@ -4314,18 +4336,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
43144336 return child_err;
43154337}
43164338
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
43294339const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);
43304340
43314341const Spawned = struct {
......@@ -4509,6 +4519,20 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
45094519 PATH: []const u8,
45104520 spawn: process.SpawnOptions,
45114521}) ForkBailError {
4522 // Must happen before clobbering file descriptors below.
4523 switch (options.spawn.cwd) {
4524 .inherit => {},
4525 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
4526 .path => |cwd_path| {
4527 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4528 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4529 try chdir(sync, cwd_path_posix);
4530 },
4531 }
4532
4533 for (options.spawn.inherit_dirs) |dir| try setFdFlags(sync, dir.handle, 0);
4534 for (options.spawn.inherit_files) |file| try setFdFlags(sync, file.handle, 0);
4535
45124536 try setUpChildIo(
45134537 sync,
45144538 options.spawn.stdin,
......@@ -4531,20 +4555,15 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
45314555 options.dev_null_fd,
45324556 );
45334557
4534 switch (options.spawn.cwd) {
4535 .inherit => {},
4536 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
4537 .path => |cwd_path| {
4538 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4539 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4540 try chdir(sync, cwd_path_posix);
4541 },
4558 // Must happen after fchdir above because the cwd file descriptor might be
4559 // equal to prog_fileno and be clobbered by this operation.
4560 if (options.prog_pipe != -1) {
4561 if (options.prog_pipe == prog_fileno)
4562 try setFdFlags(sync, options.prog_pipe, 0)
4563 else
4564 try dup2(sync, options.prog_pipe, prog_fileno);
45424565 }
45434566
4544 // Must happen after fchdir above, the cwd file descriptor might be
4545 // 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);
4547
45484567 if (options.spawn.gid) |gid| {
45494568 switch (linux.errno(linux.setregid(gid, gid))) {
45504569 .SUCCESS => {},
......@@ -4576,18 +4595,18 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
45764595 }
45774596
45784597 if (options.spawn.start_suspended) {
4579 switch (linux.errno(linux.kill(0, .STOP))) {
4598 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
45804599 .SUCCESS => {},
45814600 .PERM => return error.PermissionDenied,
45824601 else => return error.Unexpected,
45834602 }
45844603 }
45854604
4586 return execv(
4605 return processExec(
45874606 sync,
4588 options.spawn.expand_arg0,
4589 options.argv_buf.ptr[0].?,
4607 options.spawn.exe,
45904608 options.argv_buf.ptr,
4609 options.spawn.expand_arg0,
45914610 options.env_block,
45924611 options.PATH,
45934612 );
......@@ -4609,6 +4628,17 @@ fn setUpChildIo(
46094628 }
46104629}
46114630
4631fn setFdFlags(sync: *CancelRegion.Sync, fd: fd_t, flags: usize) (Io.Cancelable || Io.UnexpectedError)!void {
4632 while (true) {
4633 try sync.cancel_region.await(.nothing);
4634 switch (linux.errno(linux.fcntl(fd, linux.F.SETFD, flags))) {
4635 .SUCCESS => return,
4636 .INTR => {},
4637 else => |err| return unexpectedErrno(err),
4638 }
4639 }
4640}
4641
46124642pub const DupError = error{
46134643 ProcessFdQuotaExceeded,
46144644 SystemResources,
......@@ -4628,63 +4658,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void
46284658 }
46294659}
46304660
4631fn execv(
4661fn processExec(
46324662 sync: *CancelRegion.Sync,
4633 arg0_expand: process.ArgExpansion,
4634 file: [*:0]const u8,
4635 child_argv: [*:null]?[*:0]const u8,
4663 exe: process.ReplaceOptions.Exe,
4664 argv: [*:null]?[*:0]const u8,
4665 expand_arg0: process.ArgExpansion,
46364666 env_block: process.Environ.PosixBlock,
46374667 PATH: []const u8,
46384668) process.ReplaceError {
4639 const file_slice = std.mem.sliceTo(file, 0);
4640 if (std.mem.findScalar(u8, file_slice, '/') != null)
4641 return execvPath(sync, file, child_argv, env_block);
4642
4643 // Use of PATH_MAX here is valid as the path_buf will be passed
4644 // directly to the operating system in posixExecvPath.
4645 var path_buf: [PATH_MAX]u8 = undefined;
4646 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4647 var seen_eacces = false;
4648 var err: process.ReplaceError = error.FileNotFound;
4649
4650 // In case of expanding arg0 we must put it back if we return with an error.
4651 const prev_arg0 = child_argv[0];
4652 defer switch (arg0_expand) {
4653 .expand => child_argv[0] = prev_arg0,
4654 .no_expand => {},
4655 };
4656
4657 while (it.next()) |search_path| {
4658 const path_len = search_path.len + file_slice.len + 1;
4659 if (path_buf.len < path_len + 1) return error.NameTooLong;
4660 @memcpy(path_buf[0..search_path.len], search_path);
4661 path_buf[search_path.len] = '/';
4662 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
4663 path_buf[path_len] = 0;
4664 const full_path = path_buf[0..path_len :0].ptr;
4665 switch (arg0_expand) {
4666 .expand => child_argv[0] = full_path,
4667 .no_expand => {},
4668 }
4669 err = execvPath(sync, full_path, child_argv, env_block);
4670 switch (err) {
4671 error.AccessDenied => seen_eacces = true,
4672 error.FileNotFound, error.NotDir => {},
4673 else => |e| return e,
4674 }
4669 const arg0_slice = std.mem.span(argv[0].?);
4670 exe: switch (exe) {
4671 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
4672 .path = .cwd(),
4673 } else .search,
4674 .search => {
4675 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
4676 var path_buf: [PATH_MAX]u8 = undefined;
4677 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4678 var err: error{ FileNotFound, NotDir } = error.FileNotFound;
4679
4680 while (it.next()) |search_path| {
4681 const path_len = search_path.len + arg0_slice.len + 1;
4682 if (path_buf.len < path_len + 1) return error.NameTooLong;
4683 @memcpy(path_buf[0..search_path.len], search_path);
4684 path_buf[search_path.len] = Dir.path.sep;
4685 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
4686 path_buf[path_len] = 0;
4687 const full_path = path_buf[0..path_len :0].ptr;
4688 switch (expand_arg0) {
4689 .expand => argv[0] = full_path,
4690 .no_expand => {},
4691 }
4692 switch (processExecveat(sync, linux.AT.FDCWD, full_path, argv, env_block)) {
4693 error.AccessDenied => {},
4694 error.FileNotFound, error.NotDir => |e| err = e,
4695 else => |e| return e,
4696 }
4697 }
4698 return err;
4699 },
4700 .path => |dir| return processExecveat(sync, dir.handle, arg0_slice, argv, env_block),
4701 .file => |file| return processExecveat(sync, file.handle, null, argv, env_block),
4702 .explicit => |explicit| {
4703 var path_buffer: [PATH_MAX]u8 = undefined;
4704 const path_posix = try pathToPosix(explicit.path, &path_buffer);
4705 return processExecveat(sync, explicit.dir.handle, path_posix, argv, env_block);
4706 },
46754707 }
4676 if (seen_eacces) return error.AccessDenied;
4677 return err;
46784708}
4679/// This function ignores PATH environment variable.
4680pub fn execvPath(
4709
4710fn processExecveat(
46814711 sync: *CancelRegion.Sync,
4682 path: [*:0]const u8,
4683 child_argv: [*:null]const ?[*:0]const u8,
4712 dir: fd_t,
4713 path: ?[*:0]const u8,
4714 argv: [*:null]const ?[*:0]const u8,
46844715 env_block: process.Environ.PosixBlock,
46854716) process.ReplaceError {
46864717 try sync.cancel_region.await(.nothing);
4687 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {
4718 const rc = if (path) |p|
4719 if (dir == linux.AT.FDCWD or Dir.path.isAbsoluteZ(p))
4720 linux.execve(p, argv, env_block.slice.ptr)
4721 else
4722 linux.execveat(dir, p, argv, env_block.slice.ptr, .{
4723 .EMPTY_PATH = false,
4724 .SYMLINK_NOFOLLOW = false,
4725 })
4726 else
4727 linux.execveat(dir, "", argv, env_block.slice.ptr, .{
4728 .EMPTY_PATH = true,
4729 .SYMLINK_NOFOLLOW = false,
4730 });
4731 switch (linux.errno(rc)) {
46884732 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
46894733 .@"2BIG" => return error.SystemResources,
46904734 .MFILE => return error.ProcessFdQuotaExceeded,
......@@ -4702,6 +4746,7 @@ pub fn execvPath(
47024746 .NOTDIR => return error.NotDir,
47034747 .TXTBSY => return error.FileBusy,
47044748 .LIBBAD => return error.InvalidExe,
4749 .NOSYS => return error.OperationUnsupported,
47054750 else => |err| return unexpectedErrno(err),
47064751 }
47074752}
......@@ -4850,6 +4895,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
48504895 return ev.environ.zig_progress_file;
48514896}
48524897
4898fn inheritParentHandle(ev: *Evented, handle: fd_t) Io.InheritParentHandleError!void {
4899 var sync: CancelRegion.Sync = try .init(ev);
4900 defer sync.deinit(ev);
4901 return setFdFlags(&sync, handle, linux.FD_CLOEXEC);
4902}
4903
4904fn inheritParentDir(userdata: ?*anyopaque, handle: Dir.Handle) Io.InheritParentHandleError!Dir {
4905 const ev: *Evented = @ptrCast(@alignCast(userdata));
4906 try inheritParentHandle(ev, handle);
4907 return .{ .handle = handle };
4908}
4909
4910fn inheritParentFile(
4911 userdata: ?*anyopaque,
4912 handle: File.Handle,
4913 flags: File.Flags,
4914) Io.InheritParentHandleError!File {
4915 const ev: *Evented = @ptrCast(@alignCast(userdata));
4916 try inheritParentHandle(ev, handle);
4917 return .{ .handle = handle, .flags = flags };
4918}
4919
48534920fn scanEnviron(ev: *Evented) Io.Cancelable!void {
48544921 const ev_io = ev.io();
48554922 try ev.environ_mutex.lock(ev_io);