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 {...@@ -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
...@@ -1229,7 +1229,8 @@ fn idle(ev: *Evented, thread: *Thread) void {...@@ -1229,7 +1229,8 @@ fn idle(ev: *Evented, thread: *Thread) void {
1229 batch_userdata[0] = next;1229 batch_userdata[0] = next;
1230 }1230 }
1231 break :ready_fiber switch (@as(u2, @truncate(next))) {1231 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
1233 0b10, 0b11 => null,1234 0b10, 0b11 => null,
1234 };1235 };
1235 },1236 },
...@@ -2406,6 +2407,10 @@ fn batchDrainSubmitted(...@@ -2406,6 +2407,10 @@ fn batchDrainSubmitted(
2406 return error.ConcurrencyUnavailable2407 return error.ConcurrencyUnavailable
2407 else2408 else
2408 .{ .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o) },2409 .{ .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 },
2409 .net_receive => |o| {2414 .net_receive => |o| {
2410 _ = o;2415 _ = o;
2411 @panic("TODO implement batchDrainSubmitted for net_receive");2416 @panic("TODO implement batchDrainSubmitted for net_receive");
...@@ -2484,7 +2489,10 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {...@@ -2484,7 +2489,10 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
2484 if (@as(?Io.Operation.Result, result: switch (pending.tag) {2489 if (@as(?Io.Operation.Result, result: switch (pending.tag) {
2485 .file_read_streaming => .{2490 .file_read_streaming => .{
2486 .file_read_streaming = switch (completion.errno()) {2491 .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)),
2488 .INTR => 0,2496 .INTR => 0,
2489 .CANCELED => break :result null,2497 .CANCELED => break :result null,
2490 .INVAL => |err| errnoBug(err),2498 .INVAL => |err| errnoBug(err),
...@@ -2522,6 +2530,7 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {...@@ -2522,6 +2530,7 @@ fn batchDrainReady(batch: *Io.Batch) Io.Timeout.Error!void {
2522 },2530 },
2523 },2531 },
2524 .device_io_control => unreachable,2532 .device_io_control => unreachable,
2533 .net_accept => @panic("TODO"),
2525 .net_receive => @panic("TODO"),2534 .net_receive => @panic("TODO"),
2526 .net_send => @panic("TODO"),2535 .net_send => @panic("TODO"),
2527 .net_read => @panic("TODO"),2536 .net_read => @panic("TODO"),
...@@ -4265,19 +4274,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -4265,19 +4274,32 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42654274
4266 var sync: CancelRegion.Sync = try .init(ev);4275 var sync: CancelRegion.Sync = try .init(ev);
4267 defer sync.deinit(ev);4276 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(4278 switch (options.cwd) {
4272 userdata: ?*anyopaque,4279 .inherit => {},
4273 dir: Dir,4280 .dir => |cwd_dir| fchdir(&sync, cwd_dir.handle) catch |err| switch (err) {
4274 options: process.ReplaceOptions,4281 error.NoDevice, error.UnrecognizedVolume => return error.FileSystem,
4275) process.ReplaceError {4282 else => |e| return e,
4276 const ev: *Evented = @ptrCast(@alignCast(userdata));4283 },
4277 _ = ev;4284 .path => |cwd_path| {
4278 _ = dir;4285 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4279 _ = options;4286 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4280 @panic("TODO processReplacePath");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);
4281}4303}
42824304
4283fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {4305fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
...@@ -4314,18 +4336,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp...@@ -4314,18 +4336,6 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
4314 return child_err;4336 return child_err;
4315}4337}
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
4329const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);4339const prog_fileno = @max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO);
43304340
4331const Spawned = struct {4341const Spawned = struct {
...@@ -4509,6 +4519,20 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4509,6 +4519,20 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4509 PATH: []const u8,4519 PATH: []const u8,
4510 spawn: process.SpawnOptions,4520 spawn: process.SpawnOptions,
4511}) ForkBailError {4521}) 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
4512 try setUpChildIo(4536 try setUpChildIo(
4513 sync,4537 sync,
4514 options.spawn.stdin,4538 options.spawn.stdin,
...@@ -4531,20 +4555,15 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4531,20 +4555,15 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4531 options.dev_null_fd,4555 options.dev_null_fd,
4532 );4556 );
45334557
4534 switch (options.spawn.cwd) {4558 // Must happen after fchdir above because the cwd file descriptor might be
4535 .inherit => {},4559 // equal to prog_fileno and be clobbered by this operation.
4536 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),4560 if (options.prog_pipe != -1) {
4537 .path => |cwd_path| {4561 if (options.prog_pipe == prog_fileno)
4538 var cwd_path_buffer: [PATH_MAX]u8 = undefined;4562 try setFdFlags(sync, options.prog_pipe, 0)
4539 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);4563 else
4540 try chdir(sync, cwd_path_posix);4564 try dup2(sync, options.prog_pipe, prog_fileno);
4541 },
4542 }4565 }
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
4548 if (options.spawn.gid) |gid| {4567 if (options.spawn.gid) |gid| {
4549 switch (linux.errno(linux.setregid(gid, gid))) {4568 switch (linux.errno(linux.setregid(gid, gid))) {
4550 .SUCCESS => {},4569 .SUCCESS => {},
...@@ -4576,18 +4595,18 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {...@@ -4576,18 +4595,18 @@ fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4576 }4595 }
45774596
4578 if (options.spawn.start_suspended) {4597 if (options.spawn.start_suspended) {
4579 switch (linux.errno(linux.kill(0, .STOP))) {4598 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4580 .SUCCESS => {},4599 .SUCCESS => {},
4581 .PERM => return error.PermissionDenied,4600 .PERM => return error.PermissionDenied,
4582 else => return error.Unexpected,4601 else => return error.Unexpected,
4583 }4602 }
4584 }4603 }
45854604
4586 return execv(4605 return processExec(
4587 sync,4606 sync,
4588 options.spawn.expand_arg0,4607 options.spawn.exe,
4589 options.argv_buf.ptr[0].?,
4590 options.argv_buf.ptr,4608 options.argv_buf.ptr,
4609 options.spawn.expand_arg0,
4591 options.env_block,4610 options.env_block,
4592 options.PATH,4611 options.PATH,
4593 );4612 );
...@@ -4609,6 +4628,17 @@ fn setUpChildIo(...@@ -4609,6 +4628,17 @@ fn setUpChildIo(
4609 }4628 }
4610}4629}
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
4612pub const DupError = error{4642pub const DupError = error{
4613 ProcessFdQuotaExceeded,4643 ProcessFdQuotaExceeded,
4614 SystemResources,4644 SystemResources,
...@@ -4628,63 +4658,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void...@@ -4628,63 +4658,77 @@ pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void
4628 }4658 }
4629}4659}
46304660
4631fn execv(4661fn processExec(
4632 sync: *CancelRegion.Sync,4662 sync: *CancelRegion.Sync,
4633 arg0_expand: process.ArgExpansion,4663 exe: process.ReplaceOptions.Exe,
4634 file: [*:0]const u8,4664 argv: [*:null]?[*:0]const u8,
4635 child_argv: [*:null]?[*:0]const u8,4665 expand_arg0: process.ArgExpansion,
4636 env_block: process.Environ.PosixBlock,4666 env_block: process.Environ.PosixBlock,
4637 PATH: []const u8,4667 PATH: []const u8,
4638) process.ReplaceError {4668) process.ReplaceError {
4639 const file_slice = std.mem.sliceTo(file, 0);4669 const arg0_slice = std.mem.span(argv[0].?);
4640 if (std.mem.findScalar(u8, file_slice, '/') != null)4670 exe: switch (exe) {
4641 return execvPath(sync, file, child_argv, env_block);4671 .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{
46424672 .path = .cwd(),
4643 // Use of PATH_MAX here is valid as the path_buf will be passed4673 } else .search,
4644 // directly to the operating system in posixExecvPath.4674 .search => {
4645 var path_buf: [PATH_MAX]u8 = undefined;4675 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
4646 var it = std.mem.tokenizeScalar(u8, PATH, ':');4676 var path_buf: [PATH_MAX]u8 = undefined;
4647 var seen_eacces = false;4677 var it = std.mem.tokenizeScalar(u8, PATH, ':');
4648 var err: process.ReplaceError = error.FileNotFound;4678 var err: error{ FileNotFound, NotDir } = error.FileNotFound;
46494679
4650 // In case of expanding arg0 we must put it back if we return with an error.4680 while (it.next()) |search_path| {
4651 const prev_arg0 = child_argv[0];4681 const path_len = search_path.len + arg0_slice.len + 1;
4652 defer switch (arg0_expand) {4682 if (path_buf.len < path_len + 1) return error.NameTooLong;
4653 .expand => child_argv[0] = prev_arg0,4683 @memcpy(path_buf[0..search_path.len], search_path);
4654 .no_expand => {},4684 path_buf[search_path.len] = Dir.path.sep;
4655 };4685 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
46564686 path_buf[path_len] = 0;
4657 while (it.next()) |search_path| {4687 const full_path = path_buf[0..path_len :0].ptr;
4658 const path_len = search_path.len + file_slice.len + 1;4688 switch (expand_arg0) {
4659 if (path_buf.len < path_len + 1) return error.NameTooLong;4689 .expand => argv[0] = full_path,
4660 @memcpy(path_buf[0..search_path.len], search_path);4690 .no_expand => {},
4661 path_buf[search_path.len] = '/';4691 }
4662 @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);4692 switch (processExecveat(sync, linux.AT.FDCWD, full_path, argv, env_block)) {
4663 path_buf[path_len] = 0;4693 error.AccessDenied => {},
4664 const full_path = path_buf[0..path_len :0].ptr;4694 error.FileNotFound, error.NotDir => |e| err = e,
4665 switch (arg0_expand) {4695 else => |e| return e,
4666 .expand => child_argv[0] = full_path,4696 }
4667 .no_expand => {},4697 }
4668 }4698 return err;
4669 err = execvPath(sync, full_path, child_argv, env_block);4699 },
4670 switch (err) {4700 .path => |dir| return processExecveat(sync, dir.handle, arg0_slice, argv, env_block),
4671 error.AccessDenied => seen_eacces = true,4701 .file => |file| return processExecveat(sync, file.handle, null, argv, env_block),
4672 error.FileNotFound, error.NotDir => {},4702 .explicit => |explicit| {
4673 else => |e| return e,4703 var path_buffer: [PATH_MAX]u8 = undefined;
4674 }4704 const path_posix = try pathToPosix(explicit.path, &path_buffer);
4705 return processExecveat(sync, explicit.dir.handle, path_posix, argv, env_block);
4706 },
4675 }4707 }
4676 if (seen_eacces) return error.AccessDenied;
4677 return err;
4678}4708}
4679/// This function ignores PATH environment variable.4709
4680pub fn execvPath(4710fn processExecveat(
4681 sync: *CancelRegion.Sync,4711 sync: *CancelRegion.Sync,
4682 path: [*:0]const u8,4712 dir: fd_t,
4683 child_argv: [*:null]const ?[*:0]const u8,4713 path: ?[*:0]const u8,
4714 argv: [*:null]const ?[*:0]const u8,
4684 env_block: process.Environ.PosixBlock,4715 env_block: process.Environ.PosixBlock,
4685) process.ReplaceError {4716) process.ReplaceError {
4686 try sync.cancel_region.await(.nothing);4717 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)) {
4688 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.4732 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
4689 .@"2BIG" => return error.SystemResources,4733 .@"2BIG" => return error.SystemResources,
4690 .MFILE => return error.ProcessFdQuotaExceeded,4734 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4702,6 +4746,7 @@ pub fn execvPath(...@@ -4702,6 +4746,7 @@ pub fn execvPath(
4702 .NOTDIR => return error.NotDir,4746 .NOTDIR => return error.NotDir,
4703 .TXTBSY => return error.FileBusy,4747 .TXTBSY => return error.FileBusy,
4704 .LIBBAD => return error.InvalidExe,4748 .LIBBAD => return error.InvalidExe,
4749 .NOSYS => return error.OperationUnsupported,
4705 else => |err| return unexpectedErrno(err),4750 else => |err| return unexpectedErrno(err),
4706 }4751 }
4707}4752}
...@@ -4850,6 +4895,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -4850,6 +4895,28 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
4850 return ev.environ.zig_progress_file;4895 return ev.environ.zig_progress_file;
4851}4896}
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
4853fn scanEnviron(ev: *Evented) Io.Cancelable!void {4920fn scanEnviron(ev: *Evented) Io.Cancelable!void {
4854 const ev_io = ev.io();4921 const ev_io = ev.io();
4855 try ev.environ_mutex.lock(ev_io);4922 try ev.environ_mutex.lock(ev_io);