authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 14:12:34-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 17:33:06-08:00
log213ef953462341b44c52adc0696ab3fbc60e82b4
tree2a2580d329b06c672beb8e0491e165c8e710c34d
parent2f372b3dc00c60a625e1cc3518fa1bda3429de59

goodbye posix.open

see #6600

4 files changed, 51 insertions(+), 109 deletions(-)

lib/std/dynamic_library.zig+15-23
......@@ -148,7 +148,7 @@ const ElfDynLibError = error{
148148 ElfHashTableNotFound,
149149 Canceled,
150150 Streaming,
151} || posix.OpenError || posix.MMapError;
151} || Io.File.OpenError || posix.MMapError;
152152
153153pub const ElfDynLib = struct {
154154 strings: [*:0]u8,
......@@ -177,27 +177,20 @@ pub const ElfDynLib = struct {
177177 return parent;
178178 }
179179
180 fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t {
180 fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?Io.File {
181181 var paths = std.mem.tokenizeScalar(u8, search_path, delim);
182182 while (paths.next()) |p| {
183183 var dir = openPath(io, p) catch continue;
184184 defer dir.close(io);
185 const fd = posix.openat(dir.handle, file_name, .{
186 .ACCMODE = .RDONLY,
187 .CLOEXEC = true,
188 }, 0) catch continue;
189 return fd;
185 return dir.openFile(io, file_name, .{}) catch continue;
190186 }
191187 return null;
192188 }
193189
194 fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?posix.fd_t {
190 fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?Io.File {
195191 var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null;
196192 defer dir.close(io);
197 return posix.openat(dir.handle, file_name, .{
198 .ACCMODE = .RDONLY,
199 .CLOEXEC = true,
200 }, 0) catch null;
193 return dir.openFile(io, file_name, .{}) catch null;
201194 }
202195
203196 // This implements enough to be able to load system libraries in general
......@@ -205,10 +198,10 @@ pub const ElfDynLib = struct {
205198 // - DT_RPATH of the calling binary is not used as a search path
206199 // - DT_RUNPATH of the calling binary is not used as a search path
207200 // - /etc/ld.so.cache is not read
208 fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !posix.fd_t {
201 fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !Io.File {
209202 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
210203 if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
211 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
204 return Io.Dir.cwd().openFile(io, path_or_name, .{});
212205 }
213206
214207 // Only read LD_LIBRARY_PATH if the binary is not setuid/setgid
......@@ -216,15 +209,15 @@ pub const ElfDynLib = struct {
216209 std.os.linux.getegid() == std.os.linux.getgid())
217210 {
218211 if (LD_LIBRARY_PATH) |ld_library_path| {
219 if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |fd| {
220 return fd;
212 if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |file| {
213 return file;
221214 }
222215 }
223216 }
224217
225218 // Lastly the directories /lib and /usr/lib are searched (in this exact order)
226 if (resolveFromParent(io, "/lib", path_or_name)) |fd| return fd;
227 if (resolveFromParent(io, "/usr/lib", path_or_name)) |fd| return fd;
219 if (resolveFromParent(io, "/lib", path_or_name)) |file| return file;
220 if (resolveFromParent(io, "/usr/lib", path_or_name)) |file| return file;
228221 return error.FileNotFound;
229222 }
230223
......@@ -232,10 +225,9 @@ pub const ElfDynLib = struct {
232225 pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
233226 const io = std.Options.debug_io;
234227
235 const fd = try resolveFromName(io, path, LD_LIBRARY_PATH);
236 defer posix.close(fd);
228 const file = try resolveFromName(io, path, LD_LIBRARY_PATH);
229 defer file.close(io);
237230
238 const file: Io.File = .{ .handle = fd };
239231 const stat = try file.stat(io);
240232 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
241233
......@@ -248,7 +240,7 @@ pub const ElfDynLib = struct {
248240 mem.alignForward(usize, size, page_size),
249241 posix.PROT.READ,
250242 .{ .TYPE = .PRIVATE },
251 fd,
243 file.handle,
252244 0,
253245 );
254246 defer posix.munmap(file_bytes);
......@@ -318,7 +310,7 @@ pub const ElfDynLib = struct {
318310 extended_memsz,
319311 prot,
320312 .{ .TYPE = .PRIVATE, .FIXED = true },
321 fd,
313 file.handle,
322314 ph.p_offset - extra_bytes,
323315 );
324316 } else {
lib/std/os/linux/IoUring/test.zig+24-18
......@@ -932,6 +932,8 @@ test "accept/connect/recv/cancel" {
932932}
933933
934934test "register_files_update" {
935 const io = testing.io;
936
935937 var ring = IoUring.init(1, 0) catch |err| switch (err) {
936938 error.SystemOutdated => return error.SkipZigTest,
937939 error.PermissionDenied => return error.SkipZigTest,
......@@ -939,13 +941,13 @@ test "register_files_update" {
939941 };
940942 defer ring.deinit();
941943
942 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
943 defer posix.close(fd);
944 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
945 defer file.close(io);
944946
945947 var registered_fds = [_]linux.fd_t{0} ** 2;
946948 const fd_index = 0;
947949 const fd_index2 = 1;
948 registered_fds[fd_index] = fd;
950 registered_fds[fd_index] = file.handle;
949951 registered_fds[fd_index2] = -1;
950952
951953 ring.register_files(registered_fds[0..]) catch |err| switch (err) {
......@@ -957,10 +959,10 @@ test "register_files_update" {
957959 // Test IORING_REGISTER_FILES_UPDATE
958960 // Only available since Linux 5.5
959961
960 const fd2 = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
961 defer posix.close(fd2);
962 const file2 = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
963 defer file2.close(io);
962964
963 registered_fds[fd_index] = fd2;
965 registered_fds[fd_index] = file2.handle;
964966 registered_fds[fd_index2] = -1;
965967 try ring.register_files_update(0, registered_fds[0..]);
966968
......@@ -1339,6 +1341,8 @@ test "linkat" {
13391341}
13401342
13411343test "provide_buffers: read" {
1344 const io = testing.io;
1345
13421346 var ring = IoUring.init(1, 0) catch |err| switch (err) {
13431347 error.SystemOutdated => return error.SkipZigTest,
13441348 error.PermissionDenied => return error.SkipZigTest,
......@@ -1346,8 +1350,8 @@ test "provide_buffers: read" {
13461350 };
13471351 defer ring.deinit();
13481352
1349 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
1350 defer posix.close(fd);
1353 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
1354 defer file.close(io);
13511355
13521356 const group_id = 1337;
13531357 const buffer_id = 0;
......@@ -1380,9 +1384,9 @@ test "provide_buffers: read" {
13801384
13811385 var i: usize = 0;
13821386 while (i < buffers.len) : (i += 1) {
1383 const sqe = try ring.read(0xdededede, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1387 const sqe = try ring.read(0xdededede, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
13841388 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1385 try testing.expectEqual(@as(i32, fd), sqe.fd);
1389 try testing.expectEqual(@as(i32, file.handle), sqe.fd);
13861390 try testing.expectEqual(@as(u64, 0), sqe.addr);
13871391 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
13881392 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
......@@ -1406,9 +1410,9 @@ test "provide_buffers: read" {
14061410 // This read should fail
14071411
14081412 {
1409 const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1413 const sqe = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
14101414 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1411 try testing.expectEqual(@as(i32, fd), sqe.fd);
1415 try testing.expectEqual(@as(i32, file.handle), sqe.fd);
14121416 try testing.expectEqual(@as(u64, 0), sqe.addr);
14131417 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
14141418 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
......@@ -1445,9 +1449,9 @@ test "provide_buffers: read" {
14451449 // Final read which should work
14461450
14471451 {
1448 const sqe = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1452 const sqe = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
14491453 try testing.expectEqual(linux.IORING_OP.READ, sqe.opcode);
1450 try testing.expectEqual(@as(i32, fd), sqe.fd);
1454 try testing.expectEqual(@as(i32, file.handle), sqe.fd);
14511455 try testing.expectEqual(@as(u64, 0), sqe.addr);
14521456 try testing.expectEqual(@as(u32, buffer_len), sqe.len);
14531457 try testing.expectEqual(@as(u16, group_id), sqe.buf_index);
......@@ -1469,6 +1473,8 @@ test "provide_buffers: read" {
14691473}
14701474
14711475test "remove_buffers" {
1476 const io = testing.io;
1477
14721478 var ring = IoUring.init(1, 0) catch |err| switch (err) {
14731479 error.SystemOutdated => return error.SkipZigTest,
14741480 error.PermissionDenied => return error.SkipZigTest,
......@@ -1476,8 +1482,8 @@ test "remove_buffers" {
14761482 };
14771483 defer ring.deinit();
14781484
1479 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
1480 defer posix.close(fd);
1485 const file = try Io.Dir.openFileAbsolute(io, "/dev/zero", .{});
1486 defer file.close(io);
14811487
14821488 const group_id = 1337;
14831489 const buffer_id = 0;
......@@ -1522,7 +1528,7 @@ test "remove_buffers" {
15221528 // This read should work
15231529
15241530 {
1525 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1531 _ = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
15261532 try testing.expectEqual(@as(u32, 1), try ring.submit());
15271533
15281534 const cqe = try ring.copy_cqe();
......@@ -1542,7 +1548,7 @@ test "remove_buffers" {
15421548 // Final read should _not_ work
15431549
15441550 {
1545 _ = try ring.read(0xdfdfdfdf, fd, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
1551 _ = try ring.read(0xdfdfdfdf, file.handle, .{ .buffer_selection = .{ .group_id = group_id, .len = buffer_len } }, 0);
15461552 try testing.expectEqual(@as(u32, 1), try ring.submit());
15471553
15481554 const cqe = try ring.copy_cqe();
lib/std/posix.zig-60
......@@ -448,66 +448,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
448448
449449pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
450450
451/// Open and possibly create a file. Keeps trying if it gets interrupted.
452/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
453/// On WASI, `file_path` should be encoded as valid UTF-8.
454/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
455/// See also `openZ`.
456pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t {
457 if (native_os == .windows) {
458 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
459 } else if (native_os == .wasi and !builtin.link_libc) {
460 return openat(AT.FDCWD, file_path, flags, perm);
461 }
462 const file_path_c = try toPosixPath(file_path);
463 return openZ(&file_path_c, flags, perm);
464}
465
466/// Open and possibly create a file. Keeps trying if it gets interrupted.
467/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
468/// On WASI, `file_path` should be encoded as valid UTF-8.
469/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
470/// See also `open`.
471pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
472 if (native_os == .windows) {
473 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
474 } else if (native_os == .wasi and !builtin.link_libc) {
475 return open(mem.sliceTo(file_path, 0), flags, perm);
476 }
477
478 const open_sym = if (lfs64_abi) system.open64 else system.open;
479 while (true) {
480 const rc = open_sym(file_path, flags, perm);
481 switch (errno(rc)) {
482 .SUCCESS => return @intCast(rc),
483 .INTR => continue,
484
485 .FAULT => unreachable,
486 .INVAL => return error.BadPathName,
487 .ACCES => return error.AccessDenied,
488 .FBIG => return error.FileTooBig,
489 .OVERFLOW => return error.FileTooBig,
490 .ISDIR => return error.IsDir,
491 .LOOP => return error.SymLinkLoop,
492 .MFILE => return error.ProcessFdQuotaExceeded,
493 .NAMETOOLONG => return error.NameTooLong,
494 .NFILE => return error.SystemFdQuotaExceeded,
495 .NODEV => return error.NoDevice,
496 .NOENT => return error.FileNotFound,
497 // Can happen on Linux when opening procfs files.
498 .SRCH => return error.FileNotFound,
499 .NOMEM => return error.SystemResources,
500 .NOSPC => return error.NoSpaceLeft,
501 .NOTDIR => return error.NotDir,
502 .PERM => return error.PermissionDenied,
503 .EXIST => return error.PathAlreadyExists,
504 .BUSY => return error.DeviceBusy,
505 .ILSEQ => return error.BadPathName,
506 else => |err| return unexpectedErrno(err),
507 }
508 }
509}
510
511451/// Open and possibly create a file. Keeps trying if it gets interrupted.
512452/// `file_path` is relative to the open directory handle `dir_fd`.
513453/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
lib/std/posix/test.zig+12-8
......@@ -463,8 +463,12 @@ test "rename smoke test" {
463463 // Create some file using `open`.
464464 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
465465 defer gpa.free(file_path);
466 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR, .CREAT = true, .EXCL = true }, mode);
467 posix.close(fd);
466 const file = try Io.Dir.cwd().createFile(io, file_path, .{
467 .read = true,
468 .exclusive = true,
469 .permissions = .fromMode(mode),
470 });
471 file.close(io);
468472
469473 // Rename the file
470474 const new_file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
......@@ -476,15 +480,15 @@ test "rename smoke test" {
476480 // Try opening renamed file
477481 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_file" });
478482 defer gpa.free(file_path);
479 const fd = try posix.open(file_path, .{ .ACCMODE = .RDWR }, mode);
480 posix.close(fd);
483 const file = try Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write });
484 file.close(io);
481485 }
482486
483487 {
484488 // Try opening original file - should fail with error.FileNotFound
485489 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_file" });
486490 defer gpa.free(file_path);
487 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDWR }, mode));
491 try expectError(error.FileNotFound, Io.Dir.cwd().openFile(io, file_path, .{ .mode = .read_write }));
488492 }
489493
490494 {
......@@ -503,15 +507,15 @@ test "rename smoke test" {
503507 // Try opening renamed directory
504508 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_other_dir" });
505509 defer gpa.free(file_path);
506 const fd = try posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode);
507 posix.close(fd);
510 const dir = try Io.Dir.cwd().openDir(io, file_path, .{});
511 dir.close(io);
508512 }
509513
510514 {
511515 // Try opening original directory - should fail with error.FileNotFound
512516 const file_path = try Dir.path.join(gpa, &.{ base_path, "some_dir" });
513517 defer gpa.free(file_path);
514 try expectError(error.FileNotFound, posix.open(file_path, .{ .ACCMODE = .RDONLY, .DIRECTORY = true }, mode));
518 try expectError(error.FileNotFound, Io.Dir.cwd().openDir(io, file_path, .{}));
515519 }
516520}
517521