authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-18 20:55:47-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:10-08:00
log8632a28ca95a5a491bde57fdc45d9a0ce3cf597e
treeaa5d5f1db57d1037d5a18a92b69c594425bcb204
parent9bbb8e0d8e8ce413ad2a9125b1eb9ddbf32b6c87

std: add support for realpath on file handle

and rename OperationNotSupported to OperationUnsupported

8 files changed, 220 insertions(+), 152 deletions(-)

lib/std/Io.zig+3-1
......@@ -670,7 +670,8 @@ pub const VTable = struct {
670670 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
671671 dirClose: *const fn (?*anyopaque, []const Dir) void,
672672 dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
673 dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize,
673 dirRealPath: *const fn (?*anyopaque, Dir, out_buffer: []u8) Dir.RealPathError!usize,
674 dirRealPathFile: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize,
674675 dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
675676 dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
676677 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
......@@ -709,6 +710,7 @@ pub const VTable = struct {
709710 fileTryLock: *const fn (?*anyopaque, File, File.Lock) File.LockError!bool,
710711 fileUnlock: *const fn (?*anyopaque, File) void,
711712 fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void,
713 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
712714
713715 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
714716 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
lib/std/Io/Dir.zig+50-65
......@@ -766,95 +766,80 @@ pub fn statFile(dir: Dir, io: Io, sub_path: []const u8, options: StatFileOptions
766766 return io.vtable.dirStatFile(io.userdata, dir, sub_path, options);
767767}
768768
769pub const RealPathError = error{
770 FileNotFound,
771 AccessDenied,
772 PermissionDenied,
773 NotSupported,
774 NotDir,
775 SymLinkLoop,
776 InputOutput,
777 FileTooBig,
778 IsDir,
779 ProcessFdQuotaExceeded,
780 SystemFdQuotaExceeded,
781 NoDevice,
782 SystemResources,
783 NoSpaceLeft,
784 FileSystem,
785 DeviceBusy,
786 SharingViolation,
787 PipeBusy,
788 /// On Windows, `\\server` or `\\server\share` was not found.
789 NetworkNotFound,
790 PathAlreadyExists,
791 /// On Windows, antivirus software is enabled by default. It can be
792 /// disabled, but Windows Update sometimes ignores the user's preference
793 /// and re-enables it. When enabled, antivirus software on Windows
794 /// intercepts file system operations and makes them significantly slower
795 /// in addition to possibly failing with this error code.
796 AntivirusInterference,
797 /// On Windows, the volume does not contain a recognized file system. File
798 /// system drivers might not be loaded, or the volume may be corrupt.
799 UnrecognizedVolume,
800} || PathNameError || Io.Cancelable || Io.UnexpectedError;
769pub const RealPathError = File.RealPathError;
770
771/// Obtains the canonicalized absolute path name of `sub_path` relative to this
772/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the
773/// canonicalized absolute pathname of `sub_path` argument.
774///
775/// This function has limited platform support, and using it can lead to
776/// unnecessary failures and race conditions. It is generally advisable to
777/// avoid this function entirely.
778pub fn realPath(dir: Dir, io: Io, out_buffer: []u8) RealPathError!usize {
779 return io.vtable.dirRealPath(io.userdata, dir, out_buffer);
780}
801781
802/// This function returns the canonicalized absolute path name of `sub_path`
803/// relative to this `Dir`. If `sub_path` is absolute, ignores this `Dir`
804/// handle and returns the canonicalized absolute pathname of `sub_path`
805/// argument.
782pub const RealPathFileError = RealPathError || PathNameError;
783
784/// Obtains the canonicalized absolute path name of `sub_path` relative to this
785/// `Dir`. If `sub_path` is absolute, ignores this `Dir` handle and obtains the
786/// canonicalized absolute pathname of `sub_path` argument.
806787///
807/// This function is not universally supported by all platforms, and using this
808/// function can lead to unnecessary failures and race conditions.
788/// This function has limited platform support, and using it can lead to
789/// unnecessary failures and race conditions. It is generally advisable to
790/// avoid this function entirely.
809791///
810792/// See also:
811/// * `realPathAlloc`.
812/// * `realPathAbsolute`.
813pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize {
814 return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer);
793/// * `realPathFileAlloc`.
794/// * `realPathFileAbsolute`.
795pub fn realPathFile(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathFileError!usize {
796 return io.vtable.dirRealPathFile(io.userdata, dir, sub_path, out_buffer);
815797}
816798
817pub const RealPathAllocError = RealPathError || Allocator.Error;
799pub const RealPathFileAllocError = RealPathFileError || Allocator.Error;
818800
819/// Same as `realPath` except allocates result.
801/// Same as `realPathFile` except allocates result.
820802///
821/// This function is not universally supported by all platforms, and using this
822/// function can lead to unnecessary failures and race conditions.
803/// This function has limited platform support, and using it can lead to
804/// unnecessary failures and race conditions. It is generally advisable to
805/// avoid this function entirely.
823806///
824807/// See also:
825/// * `realPath`.
826/// * `realPathAbsolute`.
827pub fn realPathAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 {
808/// * `realPathFile`.
809/// * `realPathFileAbsolute`.
810pub fn realPathFileAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 {
828811 var buffer: [max_path_bytes]u8 = undefined;
829 const n = try realPath(dir, io, sub_path, &buffer);
812 const n = try realPathFile(dir, io, sub_path, &buffer);
830813 return allocator.dupeZ(u8, buffer[0..n]);
831814}
832815
833/// Same as `realPath` except `absolute_path` is asserted to be an absolute
816/// Same as `realPathFile` except `absolute_path` is asserted to be an absolute
834817/// path.
835818///
836/// This function is not universally supported by all platforms, and using this
837/// function can lead to unnecessary failures and race conditions.
819/// This function has limited platform support, and using it can lead to
820/// unnecessary failures and race conditions. It is generally advisable to
821/// avoid this function entirely.
838822///
839823/// See also:
840/// * `realPath`.
841/// * `realPathAlloc`.
842pub fn realPathAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathError!usize {
824/// * `realPathFile`.
825/// * `realPathFileAlloc`.
826pub fn realPathFileAbsolute(io: Io, absolute_path: []const u8, out_buffer: []u8) RealPathFileError!usize {
843827 assert(path.isAbsolute(absolute_path));
844 return io.vtable.dirRealPath(io.userdata, .cwd(), absolute_path, out_buffer);
828 return io.vtable.dirRealPathFile(io.userdata, .cwd(), absolute_path, out_buffer);
845829}
846830
847/// Same as `realPathAbsolute` except allocates result.
831/// Same as `realPathFileAbsolute` except allocates result.
848832///
849/// This function is not universally supported by all platforms, and using this
850/// function can lead to unnecessary failures and race conditions.
833/// This function has limited platform support, and using it can lead to
834/// unnecessary failures and race conditions. It is generally advisable to
835/// avoid this function entirely.
851836///
852837/// See also:
853/// * `realPathAbsolute`.
854/// * `realPath`.
855pub fn realPathAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 {
838/// * `realPathFileAbsolute`.
839/// * `realPathFile`.
840pub fn realPathFileAbsoluteAlloc(io: Io, absolute_path: []const u8, allocator: Allocator) RealPathFileAllocError![:0]u8 {
856841 var buffer: [max_path_bytes]u8 = undefined;
857 const n = try realPathAbsolute(io, absolute_path, &buffer);
842 const n = try realPathFileAbsolute(io, absolute_path, &buffer);
858843 return allocator.dupeZ(u8, buffer[0..n]);
859844}
860845
......@@ -1765,7 +1750,7 @@ pub const SetFilePermissionsError = PathNameError || SetPermissionsError || erro
17651750 SystemFdQuotaExceeded,
17661751 /// `SetFilePermissionsOptions.follow_symlinks` was set to false, which is
17671752 /// not allowed by the file system or operating system.
1768 OperationNotSupported,
1753 OperationUnsupported,
17691754};
17701755
17711756pub const SetFilePermissionsOptions = struct {
lib/std/Io/File.zig+49
......@@ -627,6 +627,55 @@ pub fn downgradeLock(file: File, io: Io) LockError!void {
627627 return io.vtable.fileDowngradeLock(io.userdata, file);
628628}
629629
630pub const RealPathError = error{
631 /// This operating system, file system, or `Io` implementation does not
632 /// support realpath operations.
633 OperationUnsupported,
634 /// The full file system path could not fit into the provided buffer, or
635 /// due to its length could not be obtained via realpath functions no
636 /// matter the buffer size provided.
637 NameTooLong,
638 FileNotFound,
639 AccessDenied,
640 PermissionDenied,
641 NotDir,
642 SymLinkLoop,
643 InputOutput,
644 FileTooBig,
645 IsDir,
646 ProcessFdQuotaExceeded,
647 SystemFdQuotaExceeded,
648 NoDevice,
649 SystemResources,
650 NoSpaceLeft,
651 FileSystem,
652 DeviceBusy,
653 SharingViolation,
654 PipeBusy,
655 /// On Windows, `\\server` or `\\server\share` was not found.
656 NetworkNotFound,
657 PathAlreadyExists,
658 /// On Windows, antivirus software is enabled by default. It can be
659 /// disabled, but Windows Update sometimes ignores the user's preference
660 /// and re-enables it. When enabled, antivirus software on Windows
661 /// intercepts file system operations and makes them significantly slower
662 /// in addition to possibly failing with this error code.
663 AntivirusInterference,
664 /// On Windows, the volume does not contain a recognized file system. File
665 /// system drivers might not be loaded, or the volume may be corrupt.
666 UnrecognizedVolume,
667} || Io.Cancelable || Io.UnexpectedError;
668
669/// Obtains the canonicalized absolute path name corresponding to an open file
670/// handle.
671///
672/// This function has limited platform support, and using it can lead to
673/// unnecessary failures and race conditions. It is generally advisable to
674/// avoid this function entirely.
675pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
676 return io.vtable.fileRealPath(io.userdata, file, out_buffer);
677}
678
630679test {
631680 _ = Reader;
632681 _ = Writer;
lib/std/Io/Threaded.zig+50-16
......@@ -716,6 +716,7 @@ pub fn io(t: *Threaded) Io {
716716 .dirClose = dirClose,
717717 .dirRead = dirRead,
718718 .dirRealPath = dirRealPath,
719 .dirRealPathFile = dirRealPathFile,
719720 .dirDeleteFile = dirDeleteFile,
720721 .dirDeleteDir = dirDeleteDir,
721722 .dirRename = dirRename,
......@@ -752,6 +753,7 @@ pub fn io(t: *Threaded) Io {
752753 .fileTryLock = fileTryLock,
753754 .fileUnlock = fileUnlock,
754755 .fileDowngradeLock = fileDowngradeLock,
756 .fileRealPath = fileRealPath,
755757
756758 .processExecutableOpen = processExecutableOpen,
757759 .processExecutablePath = processExecutablePath,
......@@ -848,6 +850,7 @@ pub fn ioBasic(t: *Threaded) Io {
848850 .dirClose = dirClose,
849851 .dirRead = dirRead,
850852 .dirRealPath = dirRealPath,
853 .dirRealPathFile = dirRealPathFile,
851854 .dirDeleteFile = dirDeleteFile,
852855 .dirDeleteDir = dirDeleteDir,
853856 .dirRename = dirRename,
......@@ -884,6 +887,7 @@ pub fn ioBasic(t: *Threaded) Io {
884887 .fileTryLock = fileTryLock,
885888 .fileUnlock = fileUnlock,
886889 .fileDowngradeLock = fileDowngradeLock,
890 .fileRealPath = fileRealPath,
887891
888892 .processExecutableOpen = processExecutableOpen,
889893 .processExecutablePath = processExecutablePath,
......@@ -3848,22 +3852,21 @@ fn dirReadUnimplemented(userdata: ?*anyopaque, dir_reader: *Dir.Reader, buffer:
38483852 return error.Unimplemented;
38493853}
38503854
3851const dirRealPath = switch (native_os) {
3852 .windows => dirRealPathWindows,
3853 else => dirRealPathPosix,
3855const dirRealPathFile = switch (native_os) {
3856 .windows => dirRealPathFileWindows,
3857 else => dirRealPathFilePosix,
38543858};
38553859
3856fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize {
3860fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
38573861 const t: *Threaded = @ptrCast(@alignCast(userdata));
3858 const w = windows;
38593862 const current_thread = Thread.getCurrent(t);
38603863
38613864 try current_thread.checkCancel();
38623865
3863 var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path);
3866 var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
38643867
38653868 const h_file = blk: {
3866 const res = w.OpenFile(path_name_w.span(), .{
3869 const res = windows.OpenFile(path_name_w.span(), .{
38673870 .dir = dir.handle,
38683871 .access_mask = .{
38693872 .GENERIC = .{ .READ = true },
......@@ -3877,9 +3880,13 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out
38773880 };
38783881 break :blk res;
38793882 };
3880 defer w.CloseHandle(h_file);
3883 defer windows.CloseHandle(h_file);
3884 return realPathWindows(current_thread, h_file, out_buffer);
3885}
38813886
3882 const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
3887fn realPathWindows(current_thread: *Thread, h_file: windows.HANDLE, out_buffer: []u8) Dir.RealPathFileError {
3888 _ = current_thread; // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
3889 const wide_slice = windows.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
38833890
38843891 const len = std.unicode.calcWtf8Len(wide_slice);
38853892 if (len > out_buffer.len)
......@@ -3888,9 +3895,8 @@ fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out
38883895 return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
38893896}
38903897
3891fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathError!usize {
3892 if (native_os == .wasi) @compileError("unsupported operating system");
3893 const max_path_bytes = std.fs.max_path_bytes;
3898fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize {
3899 if (native_os == .wasi) return error.OperationUnsupported;
38943900
38953901 const t: *Threaded = @ptrCast(@alignCast(userdata));
38963902 const current_thread = Thread.getCurrent(t);
......@@ -3949,7 +3955,35 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b
39493955 }
39503956 };
39513957 errdefer posix.close(fd);
3958 return realPathPosix(current_thread, fd, out_buffer);
3959}
3960
3961const dirRealPath = switch (native_os) {
3962 .windows => dirRealPathWindows,
3963 else => dirRealPathPosix,
3964};
3965
3966fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
3967 if (native_os == .wasi) return error.OperationUnsupported;
3968 const t: *Threaded = @ptrCast(@alignCast(userdata));
3969 const current_thread = Thread.getCurrent(t);
3970 return realPathPosix(current_thread, dir.handle, out_buffer);
3971}
3972
3973fn dirRealPathWindows(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
3974 const t: *Threaded = @ptrCast(@alignCast(userdata));
3975 const current_thread = Thread.getCurrent(t);
3976 return realPathWindows(current_thread, dir.handle, out_buffer);
3977}
3978
3979fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
3980 if (native_os == .wasi) return error.OperationUnsupported;
3981 const t: *Threaded = @ptrCast(@alignCast(userdata));
3982 const current_thread = Thread.getCurrent(t);
3983 return realPathPosix(current_thread, file.handle, out_buffer);
3984}
39523985
3986fn realPathPosix(current_thread: *Thread, fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
39533987 switch (native_os) {
39543988 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
39553989 // On macOS, we can use F.GETPATH fcntl command to query the OS for
......@@ -4049,7 +4083,7 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b
40494083 return len;
40504084 },
40514085 .netbsd, .dragonfly => {
4052 @memset(out_buffer[0..max_path_bytes], 0);
4086 @memset(out_buffer[0..Dir.max_path_bytes], 0);
40534087 try current_thread.beginSyscall();
40544088 while (true) {
40554089 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
......@@ -4077,7 +4111,7 @@ fn dirRealPathPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_b
40774111 }
40784112 return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len;
40794113 },
4080 else => @compileError("unsupported OS"),
4114 else => return error.OperationUnsupported,
40814115 }
40824116 comptime unreachable;
40834117}
......@@ -5108,7 +5142,7 @@ fn posixFchmodat(
51085142 .NOENT => return error.FileNotFound,
51095143 .NOTDIR => return error.FileNotFound,
51105144 .NOMEM => return error.SystemResources,
5111 .OPNOTSUPP => return error.OperationNotSupported,
5145 .OPNOTSUPP => return error.OperationUnsupported,
51125146 .PERM => return error.PermissionDenied,
51135147 .ROFS => return error.ReadOnlyFileSystem,
51145148 else => |err| return posix.unexpectedErrno(err),
......@@ -5144,7 +5178,7 @@ fn posixFchmodat(
51445178 .NOENT => return error.FileNotFound,
51455179 .NOMEM => return error.SystemResources,
51465180 .NOTDIR => return error.FileNotFound,
5147 .OPNOTSUPP => return error.OperationNotSupported,
5181 .OPNOTSUPP => return error.OperationUnsupported,
51485182 .PERM => return error.PermissionDenied,
51495183 .ROFS => return error.ReadOnlyFileSystem,
51505184 .NOSYS => {
lib/std/fs/test.zig+53-57
......@@ -4,6 +4,7 @@ const native_os = builtin.os.tag;
44const std = @import("../std.zig");
55const Io = std.Io;
66const mem = std.mem;
7const Allocator = std.mem.Allocator;
78const wasi = std.os.wasi;
89const windows = std.os.windows;
910const ArenaAllocator = std.heap.ArenaAllocator;
......@@ -27,38 +28,57 @@ const PathType = enum {
2728 fn isSupported(self: PathType, target_os: std.Target.Os) bool {
2829 return switch (self) {
2930 .relative => true,
30 .absolute => target_os.tag == .windows, // TODO: implement getPathForHandle for other targets
31 .absolute => switch (target_os.tag) {
32 .windows,
33 .driverkit,
34 .ios,
35 .maccatalyst,
36 .macos,
37 .tvos,
38 .visionos,
39 .watchos,
40 .linux,
41 .illumos,
42 .freebsd,
43 .serenity,
44 => true,
45
46 .dragonfly => target_os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
47 .netbsd => target_os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
48 else => false,
49 },
3150 .unc => target_os.tag == .windows,
3251 };
3352 }
3453
3554 const TransformError = Dir.RealPathError || error{OutOfMemory};
36 const TransformFn = fn (allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
55 const TransformFn = fn (Allocator, Io, Dir, relative_path: [:0]const u8) TransformError![:0]const u8;
3756
3857 fn getTransformFn(comptime path_type: PathType) TransformFn {
3958 switch (path_type) {
4059 .relative => return struct {
41 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
60 fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
4261 _ = allocator;
62 _ = io;
4363 _ = dir;
4464 return relative_path;
4565 }
4666 }.transform,
4767 .absolute => return struct {
48 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
68 fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
4969 // The final path may not actually exist which would cause realpath to fail.
5070 // So instead, we get the path of the dir and join it with the relative path.
5171 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
52 const dir_path = try getPathForHandle(dir.handle, &fd_path_buf);
72 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
5373 return Dir.path.joinZ(allocator, &.{ dir_path, relative_path });
5474 }
5575 }.transform,
5676 .unc => return struct {
57 fn transform(allocator: mem.Allocator, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
77 fn transform(allocator: Allocator, io: Io, dir: Dir, relative_path: [:0]const u8) TransformError![:0]const u8 {
5878 // Any drive absolute path (C:\foo) can be converted into a UNC path by
5979 // using '127.0.0.1' as the server name and '<drive letter>$' as the share name.
6080 var fd_path_buf: [Dir.max_path_bytes]u8 = undefined;
61 const dir_path = try getPathForHandle(dir.handle, &fd_path_buf);
81 const dir_path = fd_path_buf[0..try dir.realPath(io, &fd_path_buf)];
6282 const windows_path_type = windows.getWin32PathType(u8, dir_path);
6383 switch (windows_path_type) {
6484 .unc_absolute => return Dir.path.joinZ(allocator, &.{ dir_path, relative_path }),
......@@ -77,19 +97,6 @@ const PathType = enum {
7797 }
7898};
7999
80fn getPathForHandle(handle: File.Handle, out_buffer: *[Dir.max_path_bytes]u8) ![]u8 {
81 switch (native_os) {
82 .windows => {
83 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
84 const wide_slice = try windows.GetFinalPathNameByHandle(handle, .{}, wide_buf[0..]);
85
86 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
87 return out_buffer[0..end_index];
88 },
89 else => @compileError("TODO or unsupported"),
90 }
91}
92
93100const TestContext = struct {
94101 io: Io,
95102 path_type: PathType,
......@@ -99,7 +106,7 @@ const TestContext = struct {
99106 dir: Dir,
100107 transform_fn: *const PathType.TransformFn,
101108
102 pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
109 pub fn init(path_type: PathType, path_sep: u8, allocator: Allocator, transform_fn: *const PathType.TransformFn) TestContext {
103110 const tmp = tmpDir(.{ .iterate = true });
104111 return .{
105112 .io = testing.io,
......@@ -123,7 +130,7 @@ const TestContext = struct {
123130 /// `TestContext.deinit`.
124131 pub fn transformPath(self: *TestContext, relative_path: [:0]const u8) ![:0]const u8 {
125132 const allocator = self.arena.allocator();
126 const transformed_path = try self.transform_fn(allocator, self.dir, relative_path);
133 const transformed_path = try self.transform_fn(allocator, self.io, self.dir, relative_path);
127134 if (native_os == .windows) {
128135 const transformed_sep_path = try allocator.dupeZ(u8, transformed_path);
129136 std.mem.replaceScalar(u8, transformed_sep_path, switch (self.path_sep) {
......@@ -268,7 +275,7 @@ fn testReadLink(io: Io, dir: Dir, target_path: []const u8, symlink_path: []const
268275 try expectEqualStrings(target_path, actual);
269276}
270277
271fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
278fn testReadLinkW(allocator: Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
272279 const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path);
273280 defer allocator.free(target_path_w);
274281 // Calling the W functions directly requires the path to be NT-prefixed
......@@ -281,7 +288,7 @@ fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, sy
281288
282289fn testReadLinkAbsolute(io: Io, target_path: []const u8, symlink_path: []const u8) !void {
283290 var buffer: [Dir.max_path_bytes]u8 = undefined;
284 const given = try Dir.readLinkAbsolute(io, symlink_path, buffer[0..]);
291 const given = buffer[0..try Dir.readLinkAbsolute(io, symlink_path, &buffer)];
285292 try expectEqualStrings(target_path, given);
286293}
287294
......@@ -339,7 +346,7 @@ test "accessAbsolute" {
339346 var tmp = tmpDir(.{});
340347 defer tmp.cleanup();
341348
342 const base_path = try tmp.dir.realPathAlloc(io, ".", gpa);
349 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
343350 defer gpa.free(base_path);
344351
345352 try Dir.accessAbsolute(io, base_path, .{});
......@@ -358,7 +365,7 @@ test "openDirAbsolute" {
358365 const tmp_ino = (try tmp.dir.stat(io)).inode;
359366
360367 try tmp.dir.makeDir(io, "subdir", .default_dir);
361 const sub_path = try tmp.dir.realPathAlloc(io, "subdir", gpa);
368 const sub_path = try tmp.dir.realPathFileAlloc(io, "subdir", gpa);
362369 defer gpa.free(sub_path);
363370
364371 // Can open sub_path
......@@ -434,10 +441,10 @@ test "openDir non-cwd parent '..'" {
434441 var dir = try subdir.openDir(io, "..", .{});
435442 defer dir.close(io);
436443
437 const expected_path = try tmp.dir.realPathAlloc(io, ".", gpa);
444 const expected_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
438445 defer gpa.free(expected_path);
439446
440 const actual_path = try dir.realPathAlloc(io, ".", gpa);
447 const actual_path = try dir.realPathFileAlloc(io, ".", gpa);
441448 defer gpa.free(actual_path);
442449
443450 try expectEqualStrings(expected_path, actual_path);
......@@ -461,7 +468,7 @@ test "readLinkAbsolute" {
461468 defer arena_allocator.deinit();
462469 const arena = arena_allocator.allocator();
463470
464 const base_path = try tmp.dir.realPathAlloc(io, ".", arena);
471 const base_path = try tmp.dir.realPathFileAlloc(io, ".", arena);
465472
466473 {
467474 const target_path = try Dir.path.join(arena, &.{ base_path, "file.txt" });
......@@ -647,11 +654,6 @@ test "Dir.Iterator but dir is deleted during iteration" {
647654 // Now, when we try to iterate, the next call should return null immediately.
648655 const entry = try iterator.next(io);
649656 try testing.expect(entry == null);
650
651 // On Linux, we can opt-in to receiving a more specific error by calling `nextLinux`
652 if (native_os == .linux) {
653 try expectError(error.DirNotFound, iterator.nextLinux());
654 }
655657}
656658
657659fn entryEql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
......@@ -671,47 +673,41 @@ test "Dir.realPath smoke test" {
671673 try testWithAllSupportedPathTypes(struct {
672674 fn impl(ctx: *TestContext) !void {
673675 const io = ctx.io;
674 const allocator = ctx.arena.allocator();
676 const arena = ctx.arena.allocator();
675677 const test_file_path = try ctx.transformPath("test_file");
676678 const test_dir_path = try ctx.transformPath("test_dir");
677679 var buf: [Dir.max_path_bytes]u8 = undefined;
678680
679681 // FileNotFound if the path doesn't exist
680 try expectError(error.FileNotFound, ctx.dir.realPathAlloc(io, test_file_path, allocator));
681 try expectError(error.FileNotFound, ctx.dir.realPath(io, test_file_path, &buf));
682 try expectError(error.FileNotFound, ctx.dir.realPathAlloc(io, test_dir_path, allocator));
683 try expectError(error.FileNotFound, ctx.dir.realPath(io, test_dir_path, &buf));
682 try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_file_path, arena));
683 try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_file_path, &buf));
684 try expectError(error.FileNotFound, ctx.dir.realPathFileAlloc(io, test_dir_path, arena));
685 try expectError(error.FileNotFound, ctx.dir.realPathFile(io, test_dir_path, &buf));
684686
685687 // Now create the file and dir
686688 try ctx.dir.writeFile(io, .{ .sub_path = test_file_path, .data = "" });
687689 try ctx.dir.makeDir(io, test_dir_path, .default_dir);
688690
689691 const base_path = try ctx.transformPath(".");
690 const base_realpath = try ctx.dir.realPathAlloc(io, base_path, allocator);
691 const expected_file_path = try Dir.path.join(
692 allocator,
693 &.{ base_realpath, "test_file" },
694 );
695 const expected_dir_path = try Dir.path.join(
696 allocator,
697 &.{ base_realpath, "test_dir" },
698 );
692 const base_realpath = try ctx.dir.realPathFileAlloc(io, base_path, arena);
693 const expected_file_path = try Dir.path.join(arena, &.{ base_realpath, "test_file" });
694 const expected_dir_path = try Dir.path.join(arena, &.{ base_realpath, "test_dir" });
699695
700696 // First, test non-alloc version
701697 {
702 const file_path = try ctx.dir.realPath(io, test_file_path, &buf);
698 const file_path = buf[0..try ctx.dir.realPathFile(io, test_file_path, &buf)];
703699 try expectEqualStrings(expected_file_path, file_path);
704700
705 const dir_path = try ctx.dir.realPath(io, test_dir_path, &buf);
701 const dir_path = buf[0..try ctx.dir.realPathFile(io, test_dir_path, &buf)];
706702 try expectEqualStrings(expected_dir_path, dir_path);
707703 }
708704
709705 // Next, test alloc version
710706 {
711 const file_path = try ctx.dir.realPathAlloc(io, test_file_path, allocator);
707 const file_path = try ctx.dir.realPathFileAlloc(io, test_file_path, arena);
712708 try expectEqualStrings(expected_file_path, file_path);
713709
714 const dir_path = try ctx.dir.realPathAlloc(io, test_dir_path, allocator);
710 const dir_path = try ctx.dir.realPathFileAlloc(io, test_dir_path, arena);
715711 try expectEqualStrings(expected_dir_path, dir_path);
716712 }
717713 }
......@@ -1114,7 +1110,7 @@ test "renameAbsolute" {
11141110 defer arena.deinit();
11151111 const allocator = arena.allocator();
11161112
1117 const base_path = try tmp_dir.dir.realPathAlloc(io, ".", allocator);
1113 const base_path = try tmp_dir.dir.realPathFileAlloc(io, ".", allocator);
11181114
11191115 try expectError(error.FileNotFound, Dir.renameAbsolute(
11201116 try Dir.path.join(allocator, &.{ base_path, "missing_file_name" }),
......@@ -2022,7 +2018,7 @@ test "'.' and '..' in absolute functions" {
20222018 defer arena.deinit();
20232019 const allocator = arena.allocator();
20242020
2025 const base_path = try tmp.dir.realPathAlloc(io, ".", allocator);
2021 const base_path = try tmp.dir.realPathFileAlloc(io, ".", allocator);
20262022
20272023 const subdir_path = try Dir.path.join(allocator, &.{ base_path, "./subdir" });
20282024 try Dir.makeDirAbsolute(io, subdir_path, .default_dir);
......@@ -2140,8 +2136,8 @@ test "invalid UTF-8/WTF-8 paths" {
21402136 try expectError(expected_err, ctx.dir.statFile(io, invalid_path, .{}));
21412137
21422138 if (native_os != .wasi) {
2143 try expectError(expected_err, ctx.dir.realPath(io, invalid_path, &[_]u8{}));
2144 try expectError(expected_err, ctx.dir.realPathAlloc(io, invalid_path, testing.allocator));
2139 try expectError(expected_err, ctx.dir.realPathFile(io, invalid_path, &[_]u8{}));
2140 try expectError(expected_err, ctx.dir.realPathFileAlloc(io, invalid_path, testing.allocator));
21452141 }
21462142
21472143 try expectError(expected_err, Dir.rename(ctx.dir, invalid_path, ctx.dir, invalid_path, io));
......@@ -2402,7 +2398,7 @@ test "fchmodat smoke test" {
24022398
24032399 var test_link = true;
24042400 tmp.dir.setFilePermissions(io, "symlink", .fromMode(0o600), .{ .follow_symlinks = false }) catch |err| switch (err) {
2405 error.OperationNotSupported => test_link = false,
2401 error.OperationUnsupported => test_link = false,
24062402 else => |e| return e,
24072403 };
24082404 if (test_link) try expectMode(io, tmp.dir, "symlink", .fromMode(0o600));
lib/std/os/windows.zig+2
......@@ -3620,6 +3620,8 @@ pub const GetFinalPathNameByHandleFormat = struct {
36203620/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
36213621/// If DOS volume name format is selected, note that this function does *not* prepend
36223622/// `\\?\` prefix to the resultant path.
3623///
3624/// TODO move this function into std.Io.Threaded and add cancelation checks
36233625pub fn GetFinalPathNameByHandle(
36243626 hFile: HANDLE,
36253627 fmt: GetFinalPathNameByHandleFormat,
lib/std/posix.zig+12-12
......@@ -1527,7 +1527,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
15271527
15281528pub const ListenError = error{
15291529 FileDescriptorNotASocket,
1530 OperationNotSupported,
1530 OperationUnsupported,
15311531} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError;
15321532
15331533pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
......@@ -1540,7 +1540,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
15401540 .ADDRINUSE => return error.AddressInUse,
15411541 .BADF => unreachable,
15421542 .NOTSOCK => return error.FileDescriptorNotASocket,
1543 .OPNOTSUPP => return error.OperationNotSupported,
1543 .OPNOTSUPP => return error.OperationUnsupported,
15441544 else => |err| return unexpectedErrno(err),
15451545 }
15461546 }
......@@ -2202,7 +2202,7 @@ pub const FanotifyMarkError = error{
22022202 SystemResources,
22032203 UserMarkQuotaExceeded,
22042204 NotDir,
2205 OperationNotSupported,
2205 OperationUnsupported,
22062206 PermissionDenied,
22072207 NotSameFileSystem,
22082208 NameTooLong,
......@@ -2242,7 +2242,7 @@ pub fn fanotify_markZ(
22422242 .NOMEM => return error.SystemResources,
22432243 .NOSPC => return error.UserMarkQuotaExceeded,
22442244 .NOTDIR => return error.NotDir,
2245 .OPNOTSUPP => return error.OperationNotSupported,
2245 .OPNOTSUPP => return error.OperationUnsupported,
22462246 .PERM => return error.PermissionDenied,
22472247 .XDEV => return error.NotSameFileSystem,
22482248 else => |err| return unexpectedErrno(err),
......@@ -3466,7 +3466,7 @@ pub const SetSockOptError = error{
34663466 /// Setting the socket option requires more elevated permissions.
34673467 PermissionDenied,
34683468
3469 OperationNotSupported,
3469 OperationUnsupported,
34703470 NetworkDown,
34713471 FileDescriptorNotASocket,
34723472 SocketNotBound,
......@@ -3502,7 +3502,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo
35023502 .NOBUFS => return error.SystemResources,
35033503 .PERM => return error.PermissionDenied,
35043504 .NODEV => return error.NoDevice,
3505 .OPNOTSUPP => return error.OperationNotSupported,
3505 .OPNOTSUPP => return error.OperationUnsupported,
35063506 else => |err| return unexpectedErrno(err),
35073507 }
35083508 }
......@@ -3712,7 +3712,7 @@ pub const PrctlError = error{
37123712 /// or PR_MPX_DISABLE_MANAGEMENT
37133713 UnsupportedFeature,
37143714 /// Can only occur with PR_SET_FP_MODE
3715 OperationNotSupported,
3715 OperationUnsupported,
37163716 PermissionDenied,
37173717} || UnexpectedError;
37183718
......@@ -3736,7 +3736,7 @@ pub fn prctl(option: PR, args: anytype) PrctlError!u31 {
37363736 .FAULT => return error.InvalidAddress,
37373737 .INVAL => unreachable,
37383738 .NODEV, .NXIO => return error.UnsupportedFeature,
3739 .OPNOTSUPP => return error.OperationNotSupported,
3739 .OPNOTSUPP => return error.OperationUnsupported,
37403740 .PERM, .BUSY => return error.PermissionDenied,
37413741 .RANGE => unreachable,
37423742 else => |err| return unexpectedErrno(err),
......@@ -3995,7 +3995,7 @@ pub const PtraceError = error{
39953995 DeviceBusy,
39963996 InputOutput,
39973997 NameTooLong,
3998 OperationNotSupported,
3998 OperationUnsupported,
39993999 OutOfMemory,
40004000 ProcessNotFound,
40014001 PermissionDenied,
......@@ -4097,7 +4097,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo
40974097 .INVAL => unreachable,
40984098 .PERM => error.PermissionDenied,
40994099 .BUSY => error.DeviceBusy,
4100 .NOTSUP => error.OperationNotSupported,
4100 .NOTSUP => error.OperationUnsupported,
41014101 else => |err| return unexpectedErrno(err),
41024102 },
41034103
......@@ -4108,7 +4108,7 @@ pub fn ptrace(request: u32, pid: pid_t, addr: usize, data: usize) PtraceError!vo
41084108pub const NameToFileHandleAtError = error{
41094109 FileNotFound,
41104110 NotDir,
4111 OperationNotSupported,
4111 OperationUnsupported,
41124112 NameTooLong,
41134113 Unexpected,
41144114};
......@@ -4137,7 +4137,7 @@ pub fn name_to_handle_atZ(
41374137 .INVAL => unreachable, // bad flags, or handle_bytes too big
41384138 .NOENT => return error.FileNotFound,
41394139 .NOTDIR => return error.NotDir,
4140 .OPNOTSUPP => return error.OperationNotSupported,
4140 .OPNOTSUPP => return error.OperationUnsupported,
41414141 .OVERFLOW => return error.NameTooLong,
41424142 else => |err| return unexpectedErrno(err),
41434143 }
lib/std/posix/test.zig+1-1
......@@ -503,7 +503,7 @@ test "rename smoke test" {
503503 var tmp = tmpDir(.{});
504504 defer tmp.cleanup();
505505
506 const base_path = try tmp.dir.realPathAlloc(io, ".", gpa);
506 const base_path = try tmp.dir.realPathFileAlloc(io, ".", gpa);
507507 defer gpa.free(base_path);
508508
509509 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;