| author | |
| committer | |
| log | 877032ec6a0007316f42658d12042f1473de4856 |
| tree | d6541c2050b24f97ef80895061e881bc31fc5449 |
| parent | 8328de24f13e21e325207b19288a143854df50df |
12 files changed, 333 insertions(+), 315 deletions(-)
lib/std/Io.zig+3-1| ... | @@ -697,7 +697,6 @@ pub const VTable = struct { | ... | @@ -697,7 +697,6 @@ pub const VTable = struct { |
| 697 | fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize, | 697 | fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize, |
| 698 | fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, | 698 | fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, |
| 699 | fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, | 699 | fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, |
| 700 | openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File, | ||
| 701 | fileSync: *const fn (?*anyopaque, File) File.SyncError!void, | 700 | fileSync: *const fn (?*anyopaque, File) File.SyncError!void, |
| 702 | fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool, | 701 | fileIsTty: *const fn (?*anyopaque, File) Cancelable!bool, |
| 703 | fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void, | 702 | fileEnableAnsiEscapeCodes: *const fn (?*anyopaque, File) File.EnableAnsiEscapeCodesError!void, |
| ... | @@ -712,6 +711,9 @@ pub const VTable = struct { | ... | @@ -712,6 +711,9 @@ pub const VTable = struct { |
| 712 | fileUnlock: *const fn (?*anyopaque, File) void, | 711 | fileUnlock: *const fn (?*anyopaque, File) void, |
| 713 | fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, | 712 | fileDowngradeLock: *const fn (?*anyopaque, File) File.DowngradeLockError!void, |
| 714 | 713 | ||
| 714 | processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, | ||
| 715 | processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, | ||
| 716 | |||
| 715 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, | 717 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, |
| 716 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, | 718 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, |
| 717 | 719 |
lib/std/Io/Dir.zig+27-12| ... | @@ -694,24 +694,29 @@ pub const RealPathError = error{ | ... | @@ -694,24 +694,29 @@ pub const RealPathError = error{ |
| 694 | /// supported hosts are: Linux, macOS, and Windows. | 694 | /// supported hosts are: Linux, macOS, and Windows. |
| 695 | /// | 695 | /// |
| 696 | /// See also: | 696 | /// See also: |
| 697 | /// * `realpathAlloc`. | 697 | /// * `realPathAlloc`. |
| 698 | pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize { | 698 | pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize { |
| 699 | return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer); | 699 | return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer); |
| 700 | } | 700 | } |
| 701 | 701 | ||
| 702 | pub const RealPathAllocError = RealPathError || Allocator.Error; | 702 | pub const RealPathAllocError = RealPathError || Allocator.Error; |
| 703 | 703 | ||
| 704 | /// Same as `Dir.realpath` except caller must free the returned memory. | 704 | /// Same as `realPath` except allocates result. |
| 705 | /// See also `Dir.realpath`. | 705 | pub fn realPathAlloc(dir: Dir, io: Io, sub_path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { |
| 706 | pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 { | 706 | var buffer: [std.fs.max_path_bytes]u8 = undefined; |
| 707 | // Use of max_path_bytes here is valid as the realpath function does not | 707 | const n = try realPath(dir, io, sub_path, &buffer); |
| 708 | // have a variant that takes an arbitrary-size buffer. | 708 | return allocator.dupeZ(u8, buffer[0..n]); |
| 709 | // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 | 709 | } |
| 710 | // NULL out parameter (GNU's canonicalize_file_name) to handle overelong | 710 | |
| 711 | // paths. musl supports passing NULL but restricts the output to PATH_MAX | 711 | pub fn realPathAbsolute(io: Io, path: []const u8, out_buffer: []u8) RealPathError!usize { |
| 712 | // anyway. | 712 | return io.vtable.dirRealPath(io.userdata, .cwd(), path, out_buffer); |
| 713 | var buf: [std.fs.max_path_bytes]u8 = undefined; | 713 | } |
| 714 | return allocator.dupe(u8, try self.realpath(pathname, &buf)); | 714 | |
| 715 | /// Same as `realPathAbsolute` except allocates result. | ||
| 716 | pub fn realPathAbsoluteAlloc(io: Io, path: []const u8, allocator: Allocator) RealPathAllocError![:0]u8 { | ||
| 717 | var buffer: [std.fs.max_path_bytes]u8 = undefined; | ||
| 718 | const n = try realPathAbsolute(io, path, &buffer); | ||
| 719 | return allocator.dupeZ(u8, buffer[0..n]); | ||
| 715 | } | 720 | } |
| 716 | 721 | ||
| 717 | pub const DeleteFileError = error{ | 722 | pub const DeleteFileError = error{ |
| ... | @@ -975,6 +980,16 @@ pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkEr | ... | @@ -975,6 +980,16 @@ pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkEr |
| 975 | return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer); | 980 | return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer); |
| 976 | } | 981 | } |
| 977 | 982 | ||
| 983 | /// Same as `readLink`, except it asserts the path is absolute. | ||
| 984 | /// | ||
| 985 | /// On Windows, `path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 986 | /// On WASI, `path` should be encoded as valid UTF-8. | ||
| 987 | /// On other platforms, `path` is an opaque sequence of bytes with no particular encoding. | ||
| 988 | pub fn readLinkAbsolute(io: Io, path: []const u8, buffer: []u8) ReadLinkError!usize { | ||
| 989 | assert(std.fs.path.isAbsolute(path)); | ||
| 990 | return io.vtable.dirReadLink(io.userdata, .cwd(), path, buffer); | ||
| 991 | } | ||
| 992 | |||
| 978 | pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{ | 993 | pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{ |
| 979 | /// File size reached or exceeded the provided limit. | 994 | /// File size reached or exceeded the provided limit. |
| 980 | StreamTooLong, | 995 | StreamTooLong, |
lib/std/Io/File.zig-6| ... | @@ -464,12 +464,6 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { | ... | @@ -464,12 +464,6 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { |
| 464 | return io.vtable.fileSetTimestampsNow(io.userdata, file); | 464 | return io.vtable.fileSetTimestampsNow(io.userdata, file); |
| 465 | } | 465 | } |
| 466 | 466 | ||
| 467 | pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || LockError; | ||
| 468 | |||
| 469 | pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File { | ||
| 470 | return io.vtable.openSelfExe(io.userdata, flags); | ||
| 471 | } | ||
| 472 | |||
| 473 | pub const ReadPositionalError = Reader.Error || error{Unseekable}; | 467 | pub const ReadPositionalError = Reader.Error || error{Unseekable}; |
| 474 | 468 | ||
| 475 | pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize { | 469 | pub fn readPositional(file: File, io: Io, buffer: [][]u8, offset: u64) ReadPositionalError!usize { |
lib/std/Io/Kqueue.zig+2-2| ... | @@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io { | ... | @@ -888,7 +888,7 @@ pub fn io(k: *Kqueue) Io { |
| 888 | .fileReadPositional = fileReadPositional, | 888 | .fileReadPositional = fileReadPositional, |
| 889 | .fileSeekBy = fileSeekBy, | 889 | .fileSeekBy = fileSeekBy, |
| 890 | .fileSeekTo = fileSeekTo, | 890 | .fileSeekTo = fileSeekTo, |
| 891 | .openSelfExe = openSelfExe, | 891 | .openExecutable = openExecutable, |
| 892 | 892 | ||
| 893 | .now = now, | 893 | .now = now, |
| 894 | .sleep = sleep, | 894 | .sleep = sleep, |
| ... | @@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek | ... | @@ -1246,7 +1246,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.Seek |
| 1246 | _ = absolute_offset; | 1246 | _ = absolute_offset; |
| 1247 | @panic("TODO"); | 1247 | @panic("TODO"); |
| 1248 | } | 1248 | } |
| 1249 | fn openSelfExe(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenSelfExeError!File { | 1249 | fn openExecutable(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenExecutableError!File { |
| 1250 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); | 1250 | const k: *Kqueue = @ptrCast(@alignCast(userdata)); |
| 1251 | _ = k; | 1251 | _ = k; |
| 1252 | _ = file; | 1252 | _ = file; |
lib/std/Io/Threaded.zig+148-4| ... | @@ -717,7 +717,6 @@ pub fn io(t: *Threaded) Io { | ... | @@ -717,7 +717,6 @@ pub fn io(t: *Threaded) Io { |
| 717 | .fileReadPositional = fileReadPositional, | 717 | .fileReadPositional = fileReadPositional, |
| 718 | .fileSeekBy = fileSeekBy, | 718 | .fileSeekBy = fileSeekBy, |
| 719 | .fileSeekTo = fileSeekTo, | 719 | .fileSeekTo = fileSeekTo, |
| 720 | .openSelfExe = openSelfExe, | ||
| 721 | .fileSync = fileSync, | 720 | .fileSync = fileSync, |
| 722 | .fileIsTty = fileIsTty, | 721 | .fileIsTty = fileIsTty, |
| 723 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, | 722 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, |
| ... | @@ -732,6 +731,9 @@ pub fn io(t: *Threaded) Io { | ... | @@ -732,6 +731,9 @@ pub fn io(t: *Threaded) Io { |
| 732 | .fileUnlock = fileUnlock, | 731 | .fileUnlock = fileUnlock, |
| 733 | .fileDowngradeLock = fileDowngradeLock, | 732 | .fileDowngradeLock = fileDowngradeLock, |
| 734 | 733 | ||
| 734 | .processExecutableOpen = processExecutableOpen, | ||
| 735 | .processExecutablePath = processExecutablePath, | ||
| 736 | |||
| 735 | .now = now, | 737 | .now = now, |
| 736 | .sleep = sleep, | 738 | .sleep = sleep, |
| 737 | 739 | ||
| ... | @@ -839,7 +841,6 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -839,7 +841,6 @@ pub fn ioBasic(t: *Threaded) Io { |
| 839 | .fileReadPositional = fileReadPositional, | 841 | .fileReadPositional = fileReadPositional, |
| 840 | .fileSeekBy = fileSeekBy, | 842 | .fileSeekBy = fileSeekBy, |
| 841 | .fileSeekTo = fileSeekTo, | 843 | .fileSeekTo = fileSeekTo, |
| 842 | .openSelfExe = openSelfExe, | ||
| 843 | .fileSync = fileSync, | 844 | .fileSync = fileSync, |
| 844 | .fileIsTty = fileIsTty, | 845 | .fileIsTty = fileIsTty, |
| 845 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, | 846 | .fileEnableAnsiEscapeCodes = fileEnableAnsiEscapeCodes, |
| ... | @@ -854,6 +855,9 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -854,6 +855,9 @@ pub fn ioBasic(t: *Threaded) Io { |
| 854 | .fileUnlock = fileUnlock, | 855 | .fileUnlock = fileUnlock, |
| 855 | .fileDowngradeLock = fileDowngradeLock, | 856 | .fileDowngradeLock = fileDowngradeLock, |
| 856 | 857 | ||
| 858 | .processExecutableOpen = processExecutableOpen, | ||
| 859 | .processExecutablePath = processExecutablePath, | ||
| 860 | |||
| 857 | .now = now, | 861 | .now = now, |
| 858 | .sleep = sleep, | 862 | .sleep = sleep, |
| 859 | 863 | ||
| ... | @@ -5932,7 +5936,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi | ... | @@ -5932,7 +5936,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi |
| 5932 | } | 5936 | } |
| 5933 | } | 5937 | } |
| 5934 | 5938 | ||
| 5935 | fn openSelfExe(userdata: ?*anyopaque, flags: File.OpenFlags) File.OpenSelfExeError!File { | 5939 | fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) std.process.OpenExecutableError!File { |
| 5936 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | 5940 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 5937 | switch (native_os) { | 5941 | switch (native_os) { |
| 5938 | .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), | 5942 | .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags), |
| ... | @@ -5945,7 +5949,147 @@ fn openSelfExe(userdata: ?*anyopaque, flags: File.OpenFlags) File.OpenSelfExeErr | ... | @@ -5945,7 +5949,147 @@ fn openSelfExe(userdata: ?*anyopaque, flags: File.OpenFlags) File.OpenSelfExeErr |
| 5945 | const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); | 5949 | const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); |
| 5946 | return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); | 5950 | return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags); |
| 5947 | }, | 5951 | }, |
| 5948 | else => @panic("TODO implement openSelfExe"), | 5952 | else => @panic("TODO implement processExecutableOpen"), |
| 5953 | } | ||
| 5954 | } | ||
| 5955 | |||
| 5956 | fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.ExecutablePathError!usize { | ||
| 5957 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | ||
| 5958 | const max_path_bytes = std.fs.max_path_bytes; | ||
| 5959 | |||
| 5960 | switch (native_os) { | ||
| 5961 | .driverkit, | ||
| 5962 | .ios, | ||
| 5963 | .maccatalyst, | ||
| 5964 | .macos, | ||
| 5965 | .tvos, | ||
| 5966 | .visionos, | ||
| 5967 | .watchos, | ||
| 5968 | => { | ||
| 5969 | // Note that _NSGetExecutablePath() will return "a path" to | ||
| 5970 | // the executable not a "real path" to the executable. | ||
| 5971 | var symlink_path_buf: [max_path_bytes:0]u8 = undefined; | ||
| 5972 | var u32_len: u32 = max_path_bytes + 1; // include the sentinel | ||
| 5973 | const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); | ||
| 5974 | if (rc != 0) return error.NameTooLong; | ||
| 5975 | |||
| 5976 | var real_path_buf: [max_path_bytes]u8 = undefined; | ||
| 5977 | const real_path = Io.Dir.realPathAbsolute(ioBasic(t), &symlink_path_buf, &real_path_buf) catch |err| switch (err) { | ||
| 5978 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 5979 | else => |e| return e, | ||
| 5980 | }; | ||
| 5981 | if (real_path.len > out_buffer.len) return error.NameTooLong; | ||
| 5982 | const result = out_buffer[0..real_path.len]; | ||
| 5983 | @memcpy(result, real_path); | ||
| 5984 | return result.len; | ||
| 5985 | }, | ||
| 5986 | .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { | ||
| 5987 | error.UnsupportedReparsePointType => unreachable, // Windows-only | ||
| 5988 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 5989 | else => |e| return e, | ||
| 5990 | }, | ||
| 5991 | .illumos => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { | ||
| 5992 | error.UnsupportedReparsePointType => unreachable, // Windows-only | ||
| 5993 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 5994 | else => |e| return e, | ||
| 5995 | }, | ||
| 5996 | .freebsd, .dragonfly => { | ||
| 5997 | const current_thread = Thread.getCurrent(t); | ||
| 5998 | try current_thread.checkCancel(); | ||
| 5999 | var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; | ||
| 6000 | var out_len: usize = out_buffer.len; | ||
| 6001 | try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); | ||
| 6002 | return out_len; | ||
| 6003 | }, | ||
| 6004 | .netbsd => { | ||
| 6005 | const current_thread = Thread.getCurrent(t); | ||
| 6006 | try current_thread.checkCancel(); | ||
| 6007 | var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; | ||
| 6008 | var out_len: usize = out_buffer.len; | ||
| 6009 | try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); | ||
| 6010 | return out_len; | ||
| 6011 | }, | ||
| 6012 | .openbsd, .haiku => { | ||
| 6013 | // OpenBSD doesn't support getting the path of a running process, so try to guess it | ||
| 6014 | if (std.os.argv.len == 0) | ||
| 6015 | return error.FileNotFound; | ||
| 6016 | |||
| 6017 | const argv0 = std.mem.span(std.os.argv[0]); | ||
| 6018 | if (std.mem.indexOf(u8, argv0, "/") != null) { | ||
| 6019 | // argv[0] is a path (relative or absolute): use realpath(3) directly | ||
| 6020 | var real_path_buf: [max_path_bytes]u8 = undefined; | ||
| 6021 | const real_path = Io.Dir.realPathAbsolute(ioBasic(t), std.os.argv[0], &real_path_buf) catch |err| switch (err) { | ||
| 6022 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 6023 | else => |e| return e, | ||
| 6024 | }; | ||
| 6025 | if (real_path.len > out_buffer.len) | ||
| 6026 | return error.NameTooLong; | ||
| 6027 | const result = out_buffer[0..real_path.len]; | ||
| 6028 | @memcpy(result, real_path); | ||
| 6029 | return result.len; | ||
| 6030 | } else if (argv0.len != 0) { | ||
| 6031 | // argv[0] is not empty (and not a path): search it inside PATH | ||
| 6032 | const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; | ||
| 6033 | var path_it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); | ||
| 6034 | while (path_it.next()) |a_path| { | ||
| 6035 | var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined; | ||
| 6036 | const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ | ||
| 6037 | a_path, std.os.argv[0], | ||
| 6038 | }, 0) catch continue; | ||
| 6039 | |||
| 6040 | var real_path_buf: [max_path_bytes]u8 = undefined; | ||
| 6041 | if (Io.Dir.realPathAbsolute(ioBasic(t), resolved_path, &real_path_buf)) |real_path| { | ||
| 6042 | // found a file, and hope it is the right file | ||
| 6043 | if (real_path.len > out_buffer.len) | ||
| 6044 | return error.NameTooLong; | ||
| 6045 | const result = out_buffer[0..real_path.len]; | ||
| 6046 | @memcpy(result, real_path); | ||
| 6047 | return result.len; | ||
| 6048 | } else |_| continue; | ||
| 6049 | } | ||
| 6050 | } | ||
| 6051 | return error.FileNotFound; | ||
| 6052 | }, | ||
| 6053 | .windows => { | ||
| 6054 | const current_thread = Thread.getCurrent(t); | ||
| 6055 | try current_thread.checkCancel(); | ||
| 6056 | const w = windows; | ||
| 6057 | const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; | ||
| 6058 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; | ||
| 6059 | |||
| 6060 | // If ImagePathName is a symlink, then it will contain the path of the | ||
| 6061 | // symlink, not the path that the symlink points to. We want the path | ||
| 6062 | // that the symlink points to, though, so we need to get the realpath. | ||
| 6063 | var path_name_w = try w.wToPrefixedFileW(null, image_path_name); | ||
| 6064 | |||
| 6065 | const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; | ||
| 6066 | const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE; | ||
| 6067 | const creation = w.FILE_OPEN; | ||
| 6068 | const h_file = blk: { | ||
| 6069 | const res = w.OpenFile(path_name_w.span(), .{ | ||
| 6070 | .dir = null, | ||
| 6071 | .access_mask = access_mask, | ||
| 6072 | .share_access = share_access, | ||
| 6073 | .creation = creation, | ||
| 6074 | .filter = .any, | ||
| 6075 | }) catch |err| switch (err) { | ||
| 6076 | error.WouldBlock => unreachable, | ||
| 6077 | else => |e| return e, | ||
| 6078 | }; | ||
| 6079 | break :blk res; | ||
| 6080 | }; | ||
| 6081 | defer w.CloseHandle(h_file); | ||
| 6082 | |||
| 6083 | const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer); | ||
| 6084 | |||
| 6085 | const len = std.unicode.calcWtf8Len(wide_slice); | ||
| 6086 | if (len > out_buffer.len) | ||
| 6087 | return error.NameTooLong; | ||
| 6088 | |||
| 6089 | const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); | ||
| 6090 | return end_index; | ||
| 6091 | }, | ||
| 6092 | else => @compileError("unsupported OS"), | ||
| 5949 | } | 6093 | } |
| 5950 | } | 6094 | } |
| 5951 | 6095 |
lib/std/debug/SelfInfo/Elf.zig+1-1| ... | @@ -329,7 +329,7 @@ const Module = struct { | ... | @@ -329,7 +329,7 @@ const Module = struct { |
| 329 | defer file.close(io); | 329 | defer file.close(io); |
| 330 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); | 330 | break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name)); |
| 331 | } else res: { | 331 | } else res: { |
| 332 | const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) { | 332 | const path = std.process.executablePathAlloc(io, gpa) catch |err| switch (err) { |
| 333 | error.OutOfMemory => |e| return e, | 333 | error.OutOfMemory => |e| return e, |
| 334 | else => return error.ReadFailed, | 334 | else => return error.ReadFailed, |
| 335 | }; | 335 | }; |
lib/std/debug/SelfInfo/Windows.zig+1-1| ... | @@ -434,7 +434,7 @@ const Module = struct { | ... | @@ -434,7 +434,7 @@ const Module = struct { |
| 434 | const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { | 434 | const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: { |
| 435 | break :res std.fs.cwd().openFile(io, path, .{}); | 435 | break :res std.fs.cwd().openFile(io, path, .{}); |
| 436 | } else res: { | 436 | } else res: { |
| 437 | const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) { | 437 | const self_dir = std.process.executableDirPathAlloc(io, gpa) catch |err| switch (err) { |
| 438 | error.OutOfMemory, error.Unexpected => |e| return e, | 438 | error.OutOfMemory, error.Unexpected => |e| return e, |
| 439 | else => return error.ReadFailed, | 439 | else => return error.ReadFailed, |
| 440 | }; | 440 | }; |
lib/std/fs.zig-232| ... | @@ -25,13 +25,6 @@ pub const File = std.Io.File; | ... | @@ -25,13 +25,6 @@ pub const File = std.Io.File; |
| 25 | pub const path = @import("fs/path.zig"); | 25 | pub const path = @import("fs/path.zig"); |
| 26 | pub const wasi = @import("fs/wasi.zig"); | 26 | pub const wasi = @import("fs/wasi.zig"); |
| 27 | 27 | ||
| 28 | // TODO audit these APIs with respect to Dir and absolute paths | ||
| 29 | |||
| 30 | pub const realpath = posix.realpath; | ||
| 31 | pub const realpathZ = posix.realpathZ; | ||
| 32 | pub const realpathW = posix.realpathW; | ||
| 33 | pub const realpathW2 = posix.realpathW2; | ||
| 34 | |||
| 35 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; | 28 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; |
| 36 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; | 29 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; |
| 37 | 30 | ||
| ... | @@ -241,15 +234,6 @@ pub fn deleteTreeAbsolute(io: Io, absolute_path: []const u8) !void { | ... | @@ -241,15 +234,6 @@ pub fn deleteTreeAbsolute(io: Io, absolute_path: []const u8) !void { |
| 241 | return dir.deleteTree(path.basename(absolute_path)); | 234 | return dir.deleteTree(path.basename(absolute_path)); |
| 242 | } | 235 | } |
| 243 | 236 | ||
| 244 | /// Same as `Dir.readLink`, except it asserts the path is absolute. | ||
| 245 | /// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 246 | /// On WASI, `pathname` should be encoded as valid UTF-8. | ||
| 247 | /// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding. | ||
| 248 | pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8 { | ||
| 249 | assert(path.isAbsolute(pathname)); | ||
| 250 | return posix.readlink(pathname, buffer); | ||
| 251 | } | ||
| 252 | |||
| 253 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. | 237 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. |
| 254 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent | 238 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 255 | /// one; the latter case is known as a dangling link. | 239 | /// one; the latter case is known as a dangling link. |
| ... | @@ -287,222 +271,6 @@ pub fn symLinkAbsoluteW( | ... | @@ -287,222 +271,6 @@ pub fn symLinkAbsoluteW( |
| 287 | return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory); | 271 | return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory); |
| 288 | } | 272 | } |
| 289 | 273 | ||
| 290 | // This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded | ||
| 291 | pub const SelfExePathError = error{ | ||
| 292 | FileNotFound, | ||
| 293 | AccessDenied, | ||
| 294 | NameTooLong, | ||
| 295 | NotSupported, | ||
| 296 | NotDir, | ||
| 297 | SymLinkLoop, | ||
| 298 | InputOutput, | ||
| 299 | FileTooBig, | ||
| 300 | IsDir, | ||
| 301 | ProcessFdQuotaExceeded, | ||
| 302 | SystemFdQuotaExceeded, | ||
| 303 | NoDevice, | ||
| 304 | SystemResources, | ||
| 305 | NoSpaceLeft, | ||
| 306 | FileSystem, | ||
| 307 | BadPathName, | ||
| 308 | DeviceBusy, | ||
| 309 | SharingViolation, | ||
| 310 | PipeBusy, | ||
| 311 | NotLink, | ||
| 312 | PathAlreadyExists, | ||
| 313 | |||
| 314 | /// On Windows, `\\server` or `\\server\share` was not found. | ||
| 315 | NetworkNotFound, | ||
| 316 | ProcessNotFound, | ||
| 317 | |||
| 318 | /// On Windows, antivirus software is enabled by default. It can be | ||
| 319 | /// disabled, but Windows Update sometimes ignores the user's preference | ||
| 320 | /// and re-enables it. When enabled, antivirus software on Windows | ||
| 321 | /// intercepts file system operations and makes them significantly slower | ||
| 322 | /// in addition to possibly failing with this error code. | ||
| 323 | AntivirusInterference, | ||
| 324 | |||
| 325 | /// On Windows, the volume does not contain a recognized file system. File | ||
| 326 | /// system drivers might not be loaded, or the volume may be corrupt. | ||
| 327 | UnrecognizedVolume, | ||
| 328 | |||
| 329 | Canceled, | ||
| 330 | } || posix.SysCtlError; | ||
| 331 | |||
| 332 | /// `selfExePath` except allocates the result on the heap. | ||
| 333 | /// Caller owns returned memory. | ||
| 334 | pub fn selfExePathAlloc(allocator: Allocator) ![]u8 { | ||
| 335 | // Use of max_path_bytes here is justified as, at least on one tested Linux | ||
| 336 | // system, readlink will completely fail to return a result larger than | ||
| 337 | // PATH_MAX even if given a sufficiently large buffer. This makes it | ||
| 338 | // fundamentally impossible to get the selfExePath of a program running in | ||
| 339 | // a very deeply nested directory chain in this way. | ||
| 340 | // TODO(#4812): Investigate other systems and whether it is possible to get | ||
| 341 | // this path by trying larger and larger buffers until one succeeds. | ||
| 342 | var buf: [max_path_bytes]u8 = undefined; | ||
| 343 | return allocator.dupe(u8, try selfExePath(&buf)); | ||
| 344 | } | ||
| 345 | |||
| 346 | /// Get the path to the current executable. Follows symlinks. | ||
| 347 | /// If you only need the directory, use selfExeDirPath. | ||
| 348 | /// If you only want an open file handle, use openSelfExe. | ||
| 349 | /// This function may return an error if the current executable | ||
| 350 | /// was deleted after spawning. | ||
| 351 | /// Returned value is a slice of out_buffer. | ||
| 352 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 353 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 354 | /// | ||
| 355 | /// On Linux, depends on procfs being mounted. If the currently executing binary has | ||
| 356 | /// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. | ||
| 357 | /// TODO make the return type of this a null terminated pointer | ||
| 358 | pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { | ||
| 359 | if (is_darwin) { | ||
| 360 | // Note that _NSGetExecutablePath() will return "a path" to | ||
| 361 | // the executable not a "real path" to the executable. | ||
| 362 | var symlink_path_buf: [max_path_bytes:0]u8 = undefined; | ||
| 363 | var u32_len: u32 = max_path_bytes + 1; // include the sentinel | ||
| 364 | const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len); | ||
| 365 | if (rc != 0) return error.NameTooLong; | ||
| 366 | |||
| 367 | var real_path_buf: [max_path_bytes]u8 = undefined; | ||
| 368 | const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) { | ||
| 369 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 370 | else => |e| return e, | ||
| 371 | }; | ||
| 372 | if (real_path.len > out_buffer.len) return error.NameTooLong; | ||
| 373 | const result = out_buffer[0..real_path.len]; | ||
| 374 | @memcpy(result, real_path); | ||
| 375 | return result; | ||
| 376 | } | ||
| 377 | switch (native_os) { | ||
| 378 | .linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) { | ||
| 379 | error.UnsupportedReparsePointType => unreachable, // Windows-only | ||
| 380 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 381 | else => |e| return e, | ||
| 382 | }, | ||
| 383 | .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) { | ||
| 384 | error.UnsupportedReparsePointType => unreachable, // Windows-only | ||
| 385 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 386 | else => |e| return e, | ||
| 387 | }, | ||
| 388 | .freebsd, .dragonfly => { | ||
| 389 | var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; | ||
| 390 | var out_len: usize = out_buffer.len; | ||
| 391 | try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); | ||
| 392 | // TODO could this slice from 0 to out_len instead? | ||
| 393 | return mem.sliceTo(out_buffer, 0); | ||
| 394 | }, | ||
| 395 | .netbsd => { | ||
| 396 | var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; | ||
| 397 | var out_len: usize = out_buffer.len; | ||
| 398 | try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0); | ||
| 399 | // TODO could this slice from 0 to out_len instead? | ||
| 400 | return mem.sliceTo(out_buffer, 0); | ||
| 401 | }, | ||
| 402 | .openbsd, .haiku => { | ||
| 403 | // OpenBSD doesn't support getting the path of a running process, so try to guess it | ||
| 404 | if (std.os.argv.len == 0) | ||
| 405 | return error.FileNotFound; | ||
| 406 | |||
| 407 | const argv0 = mem.span(std.os.argv[0]); | ||
| 408 | if (mem.find(u8, argv0, "/") != null) { | ||
| 409 | // argv[0] is a path (relative or absolute): use realpath(3) directly | ||
| 410 | var real_path_buf: [max_path_bytes]u8 = undefined; | ||
| 411 | const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) { | ||
| 412 | error.NetworkNotFound => unreachable, // Windows-only | ||
| 413 | else => |e| return e, | ||
| 414 | }; | ||
| 415 | if (real_path.len > out_buffer.len) | ||
| 416 | return error.NameTooLong; | ||
| 417 | const result = out_buffer[0..real_path.len]; | ||
| 418 | @memcpy(result, real_path); | ||
| 419 | return result; | ||
| 420 | } else if (argv0.len != 0) { | ||
| 421 | // argv[0] is not empty (and not a path): search it inside PATH | ||
| 422 | const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound; | ||
| 423 | var path_it = mem.tokenizeScalar(u8, PATH, path.delimiter); | ||
| 424 | while (path_it.next()) |a_path| { | ||
| 425 | var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined; | ||
| 426 | const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{ | ||
| 427 | a_path, | ||
| 428 | std.os.argv[0], | ||
| 429 | }, 0) catch continue; | ||
| 430 | |||
| 431 | var real_path_buf: [max_path_bytes]u8 = undefined; | ||
| 432 | if (posix.realpathZ(resolved_path, &real_path_buf)) |real_path| { | ||
| 433 | // found a file, and hope it is the right file | ||
| 434 | if (real_path.len > out_buffer.len) | ||
| 435 | return error.NameTooLong; | ||
| 436 | const result = out_buffer[0..real_path.len]; | ||
| 437 | @memcpy(result, real_path); | ||
| 438 | return result; | ||
| 439 | } else |_| continue; | ||
| 440 | } | ||
| 441 | } | ||
| 442 | return error.FileNotFound; | ||
| 443 | }, | ||
| 444 | .windows => { | ||
| 445 | const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; | ||
| 446 | const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; | ||
| 447 | |||
| 448 | // If ImagePathName is a symlink, then it will contain the path of the | ||
| 449 | // symlink, not the path that the symlink points to. We want the path | ||
| 450 | // that the symlink points to, though, so we need to get the realpath. | ||
| 451 | var pathname_w = try windows.wToPrefixedFileW(null, image_path_name); | ||
| 452 | |||
| 453 | const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data); | ||
| 454 | |||
| 455 | const len = std.unicode.calcWtf8Len(wide_slice); | ||
| 456 | if (len > out_buffer.len) | ||
| 457 | return error.NameTooLong; | ||
| 458 | |||
| 459 | const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); | ||
| 460 | return out_buffer[0..end_index]; | ||
| 461 | }, | ||
| 462 | else => @compileError("std.fs.selfExePath not supported for this target"), | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | /// `selfExeDirPath` except allocates the result on the heap. | ||
| 467 | /// Caller owns returned memory. | ||
| 468 | pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 { | ||
| 469 | // Use of max_path_bytes here is justified as, at least on one tested Linux | ||
| 470 | // system, readlink will completely fail to return a result larger than | ||
| 471 | // PATH_MAX even if given a sufficiently large buffer. This makes it | ||
| 472 | // fundamentally impossible to get the selfExeDirPath of a program running | ||
| 473 | // in a very deeply nested directory chain in this way. | ||
| 474 | // TODO(#4812): Investigate other systems and whether it is possible to get | ||
| 475 | // this path by trying larger and larger buffers until one succeeds. | ||
| 476 | var buf: [max_path_bytes]u8 = undefined; | ||
| 477 | return allocator.dupe(u8, try selfExeDirPath(&buf)); | ||
| 478 | } | ||
| 479 | |||
| 480 | /// Get the directory path that contains the current executable. | ||
| 481 | /// Returned value is a slice of out_buffer. | ||
| 482 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 483 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 484 | pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 { | ||
| 485 | const self_exe_path = try selfExePath(out_buffer); | ||
| 486 | // Assume that the OS APIs return absolute paths, and therefore dirname | ||
| 487 | // will not return null. | ||
| 488 | return path.dirname(self_exe_path).?; | ||
| 489 | } | ||
| 490 | |||
| 491 | /// `realpath`, except caller must free the returned memory. | ||
| 492 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 493 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 494 | /// See also `Dir.realpath`. | ||
| 495 | pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 { | ||
| 496 | // Use of max_path_bytes here is valid as the realpath function does not | ||
| 497 | // have a variant that takes an arbitrary-size buffer. | ||
| 498 | // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008 | ||
| 499 | // NULL out parameter (GNU's canonicalize_file_name) to handle overelong | ||
| 500 | // paths. musl supports passing NULL but restricts the output to PATH_MAX | ||
| 501 | // anyway. | ||
| 502 | var buf: [max_path_bytes]u8 = undefined; | ||
| 503 | return allocator.dupe(u8, try posix.realpath(pathname, &buf)); | ||
| 504 | } | ||
| 505 | |||
| 506 | test { | 274 | test { |
| 507 | _ = AtomicFile; | 275 | _ = AtomicFile; |
| 508 | _ = Dir; | 276 | _ = Dir; |
lib/std/fs/test.zig+50-5| ... | @@ -4,6 +4,7 @@ const native_os = builtin.os.tag; | ... | @@ -4,6 +4,7 @@ const native_os = builtin.os.tag; |
| 4 | const std = @import("../std.zig"); | 4 | const std = @import("../std.zig"); |
| 5 | const Io = std.Io; | 5 | const Io = std.Io; |
| 6 | const testing = std.testing; | 6 | const testing = std.testing; |
| 7 | const expect = std.testing.expect; | ||
| 7 | const fs = std.fs; | 8 | const fs = std.fs; |
| 8 | const mem = std.mem; | 9 | const mem = std.mem; |
| 9 | const wasi = std.os.wasi; | 10 | const wasi = std.os.wasi; |
| ... | @@ -1177,21 +1178,22 @@ test "renameAbsolute" { | ... | @@ -1177,21 +1178,22 @@ test "renameAbsolute" { |
| 1177 | dir.close(io); | 1178 | dir.close(io); |
| 1178 | } | 1179 | } |
| 1179 | 1180 | ||
| 1180 | test "openSelfExe" { | 1181 | test "openExecutable" { |
| 1181 | if (native_os == .wasi) return error.SkipZigTest; | 1182 | if (native_os == .wasi) return error.SkipZigTest; |
| 1182 | 1183 | ||
| 1183 | const io = testing.io; | 1184 | const io = testing.io; |
| 1184 | 1185 | ||
| 1185 | const self_exe_file = try std.fs.openSelfExe(.{}); | 1186 | const self_exe_file = try std.fs.openExecutable(.{}); |
| 1186 | self_exe_file.close(io); | 1187 | self_exe_file.close(io); |
| 1187 | } | 1188 | } |
| 1188 | 1189 | ||
| 1189 | test "selfExePath" { | 1190 | test "executablePath" { |
| 1190 | if (native_os == .wasi) return error.SkipZigTest; | 1191 | if (native_os == .wasi) return error.SkipZigTest; |
| 1191 | 1192 | ||
| 1193 | const io = testing.io; | ||
| 1192 | var buf: [fs.max_path_bytes]u8 = undefined; | 1194 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 1193 | const buf_self_exe_path = try std.fs.selfExePath(&buf); | 1195 | const buf_self_exe_path = try std.process.executablePath(io, &buf); |
| 1194 | const alloc_self_exe_path = try std.fs.selfExePathAlloc(testing.allocator); | 1196 | const alloc_self_exe_path = try std.process.executablePathAlloc(io, testing.allocator); |
| 1195 | defer testing.allocator.free(alloc_self_exe_path); | 1197 | defer testing.allocator.free(alloc_self_exe_path); |
| 1196 | try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); | 1198 | try testing.expectEqualSlices(u8, buf_self_exe_path, alloc_self_exe_path); |
| 1197 | } | 1199 | } |
| ... | @@ -2371,3 +2373,46 @@ test "File.Writer sendfile with buffered contents" { | ... | @@ -2371,3 +2373,46 @@ test "File.Writer sendfile with buffered contents" { |
| 2371 | try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); | 2373 | try testing.expectEqualStrings("abcd", try check_r.interface.take(4)); |
| 2372 | try testing.expectError(error.EndOfStream, check_r.interface.takeByte()); | 2374 | try testing.expectError(error.EndOfStream, check_r.interface.takeByte()); |
| 2373 | } | 2375 | } |
| 2376 | |||
| 2377 | test "readlink on Windows" { | ||
| 2378 | if (native_os != .windows) return error.SkipZigTest; | ||
| 2379 | |||
| 2380 | try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); | ||
| 2381 | try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); | ||
| 2382 | try testReadlink("C:\\Users", "C:\\Documents and Settings"); | ||
| 2383 | } | ||
| 2384 | |||
| 2385 | fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { | ||
| 2386 | var buffer: [fs.max_path_bytes]u8 = undefined; | ||
| 2387 | const given = try Dir.readLinkAbsolute(symlink_path, buffer[0..]); | ||
| 2388 | try expect(mem.eql(u8, target_path, given)); | ||
| 2389 | } | ||
| 2390 | |||
| 2391 | test "readlinkat" { | ||
| 2392 | var tmp = tmpDir(.{}); | ||
| 2393 | defer tmp.cleanup(); | ||
| 2394 | |||
| 2395 | // create file | ||
| 2396 | try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); | ||
| 2397 | |||
| 2398 | // create a symbolic link | ||
| 2399 | if (native_os == .windows) { | ||
| 2400 | std.os.windows.CreateSymbolicLink( | ||
| 2401 | tmp.dir.fd, | ||
| 2402 | &[_]u16{ 'l', 'i', 'n', 'k' }, | ||
| 2403 | &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, | ||
| 2404 | false, | ||
| 2405 | ) catch |err| switch (err) { | ||
| 2406 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | ||
| 2407 | error.AccessDenied => return error.SkipZigTest, | ||
| 2408 | else => return err, | ||
| 2409 | }; | ||
| 2410 | } else { | ||
| 2411 | try posix.symlinkat("file.txt", tmp.dir.fd, "link"); | ||
| 2412 | } | ||
| 2413 | |||
| 2414 | // read the link | ||
| 2415 | var buffer: [fs.max_path_bytes]u8 = undefined; | ||
| 2416 | const read_link = try tmp.dir.readLink("link", &buffer); | ||
| 2417 | try expect(mem.eql(u8, "file.txt", read_link)); | ||
| 2418 | } |
lib/std/posix/test.zig-43| ... | @@ -111,20 +111,6 @@ test "open smoke test" { | ... | @@ -111,20 +111,6 @@ test "open smoke test" { |
| 111 | } | 111 | } |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | test "readlink on Windows" { | ||
| 115 | if (native_os != .windows) return error.SkipZigTest; | ||
| 116 | |||
| 117 | try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); | ||
| 118 | try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); | ||
| 119 | try testReadlink("C:\\Users", "C:\\Documents and Settings"); | ||
| 120 | } | ||
| 121 | |||
| 122 | fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { | ||
| 123 | var buffer: [fs.max_path_bytes]u8 = undefined; | ||
| 124 | const given = try posix.readlink(symlink_path, buffer[0..]); | ||
| 125 | try expect(mem.eql(u8, target_path, given)); | ||
| 126 | } | ||
| 127 | |||
| 128 | fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } { | 114 | fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } { |
| 129 | if (native_os == .linux) { | 115 | if (native_os == .linux) { |
| 130 | const stx = try linux.wrapped.statx( | 116 | const stx = try linux.wrapped.statx( |
| ... | @@ -216,35 +202,6 @@ test "fstatat" { | ... | @@ -216,35 +202,6 @@ test "fstatat" { |
| 216 | // try expectEqual(stat.blocks, statat.blocks); | 202 | // try expectEqual(stat.blocks, statat.blocks); |
| 217 | } | 203 | } |
| 218 | 204 | ||
| 219 | test "readlinkat" { | ||
| 220 | var tmp = tmpDir(.{}); | ||
| 221 | defer tmp.cleanup(); | ||
| 222 | |||
| 223 | // create file | ||
| 224 | try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = "nonsense" }); | ||
| 225 | |||
| 226 | // create a symbolic link | ||
| 227 | if (native_os == .windows) { | ||
| 228 | std.os.windows.CreateSymbolicLink( | ||
| 229 | tmp.dir.fd, | ||
| 230 | &[_]u16{ 'l', 'i', 'n', 'k' }, | ||
| 231 | &[_:0]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, | ||
| 232 | false, | ||
| 233 | ) catch |err| switch (err) { | ||
| 234 | // Symlink requires admin privileges on windows, so this test can legitimately fail. | ||
| 235 | error.AccessDenied => return error.SkipZigTest, | ||
| 236 | else => return err, | ||
| 237 | }; | ||
| 238 | } else { | ||
| 239 | try posix.symlinkat("file.txt", tmp.dir.fd, "link"); | ||
| 240 | } | ||
| 241 | |||
| 242 | // read the link | ||
| 243 | var buffer: [fs.max_path_bytes]u8 = undefined; | ||
| 244 | const read_link = try posix.readlinkat(tmp.dir.fd, "link", buffer[0..]); | ||
| 245 | try expect(mem.eql(u8, "file.txt", read_link)); | ||
| 246 | } | ||
| 247 | |||
| 248 | test "getrandom" { | 205 | test "getrandom" { |
| 249 | var buf_a: [50]u8 = undefined; | 206 | var buf_a: [50]u8 = undefined; |
| 250 | var buf_b: [50]u8 = undefined; | 207 | var buf_b: [50]u8 = undefined; |
lib/std/process.zig+97-1| ... | @@ -3,6 +3,7 @@ const native_os = builtin.os.tag; | ... | @@ -3,6 +3,7 @@ const native_os = builtin.os.tag; |
| 3 | 3 | ||
| 4 | const std = @import("std.zig"); | 4 | const std = @import("std.zig"); |
| 5 | const Io = std.Io; | 5 | const Io = std.Io; |
| 6 | const File = std.Io.File; | ||
| 6 | const fs = std.fs; | 7 | const fs = std.fs; |
| 7 | const mem = std.mem; | 8 | const mem = std.mem; |
| 8 | const math = std.math; | 9 | const math = std.math; |
| ... | @@ -12,6 +13,7 @@ const testing = std.testing; | ... | @@ -12,6 +13,7 @@ const testing = std.testing; |
| 12 | const posix = std.posix; | 13 | const posix = std.posix; |
| 13 | const windows = std.os.windows; | 14 | const windows = std.os.windows; |
| 14 | const unicode = std.unicode; | 15 | const unicode = std.unicode; |
| 16 | const max_path_bytes = std.fs.max_path_bytes; | ||
| 15 | 17 | ||
| 16 | pub const Child = @import("process/Child.zig"); | 18 | pub const Child = @import("process/Child.zig"); |
| 17 | pub const abort = posix.abort; | 19 | pub const abort = posix.abort; |
| ... | @@ -37,7 +39,7 @@ pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnl | ... | @@ -37,7 +39,7 @@ pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnl |
| 37 | pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 { | 39 | pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 { |
| 38 | // The use of max_path_bytes here is just a heuristic: most paths will fit | 40 | // The use of max_path_bytes here is just a heuristic: most paths will fit |
| 39 | // in stack_buf, avoiding an extra allocation in the common case. | 41 | // in stack_buf, avoiding an extra allocation in the common case. |
| 40 | var stack_buf: [fs.max_path_bytes]u8 = undefined; | 42 | var stack_buf: [max_path_bytes]u8 = undefined; |
| 41 | var heap_buf: ?[]u8 = null; | 43 | var heap_buf: ?[]u8 = null; |
| 42 | defer if (heap_buf) |buf| allocator.free(buf); | 44 | defer if (heap_buf) |buf| allocator.free(buf); |
| 43 | 45 | ||
| ... | @@ -2112,3 +2114,97 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { | ... | @@ -2112,3 +2114,97 @@ pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn { |
| 2112 | std.log.err(format, format_arguments); | 2114 | std.log.err(format, format_arguments); |
| 2113 | exit(1); | 2115 | exit(1); |
| 2114 | } | 2116 | } |
| 2117 | |||
| 2118 | pub const ExecutablePathBaseError = error{ | ||
| 2119 | FileNotFound, | ||
| 2120 | AccessDenied, | ||
| 2121 | NotSupported, | ||
| 2122 | NotDir, | ||
| 2123 | SymLinkLoop, | ||
| 2124 | InputOutput, | ||
| 2125 | FileTooBig, | ||
| 2126 | IsDir, | ||
| 2127 | ProcessFdQuotaExceeded, | ||
| 2128 | SystemFdQuotaExceeded, | ||
| 2129 | NoDevice, | ||
| 2130 | SystemResources, | ||
| 2131 | NoSpaceLeft, | ||
| 2132 | FileSystem, | ||
| 2133 | BadPathName, | ||
| 2134 | DeviceBusy, | ||
| 2135 | SharingViolation, | ||
| 2136 | PipeBusy, | ||
| 2137 | NotLink, | ||
| 2138 | PathAlreadyExists, | ||
| 2139 | /// On Windows, `\\server` or `\\server\share` was not found. | ||
| 2140 | NetworkNotFound, | ||
| 2141 | ProcessNotFound, | ||
| 2142 | /// On Windows, antivirus software is enabled by default. It can be | ||
| 2143 | /// disabled, but Windows Update sometimes ignores the user's preference | ||
| 2144 | /// and re-enables it. When enabled, antivirus software on Windows | ||
| 2145 | /// intercepts file system operations and makes them significantly slower | ||
| 2146 | /// in addition to possibly failing with this error code. | ||
| 2147 | AntivirusInterference, | ||
| 2148 | /// On Windows, the volume does not contain a recognized file system. File | ||
| 2149 | /// system drivers might not be loaded, or the volume may be corrupt. | ||
| 2150 | UnrecognizedVolume, | ||
| 2151 | PermissionDenied, | ||
| 2152 | } || Io.Cancelable || Io.UnexpectedError; | ||
| 2153 | |||
| 2154 | pub const ExecutablePathAllocError = ExecutablePathBaseError || Allocator.Error; | ||
| 2155 | |||
| 2156 | pub fn executablePathAlloc(io: Io, allocator: Allocator) ExecutablePathAllocError![:0]u8 { | ||
| 2157 | var buffer: [max_path_bytes]u8 = undefined; | ||
| 2158 | const n = executablePath(io, &buffer) catch |err| switch (err) { | ||
| 2159 | error.NameTooLong => unreachable, | ||
| 2160 | else => |e| return e, | ||
| 2161 | }; | ||
| 2162 | return allocator.dupeZ(u8, buffer[0..n]); | ||
| 2163 | } | ||
| 2164 | |||
| 2165 | pub const ExecutablePathError = ExecutablePathBaseError || error{NameTooLong}; | ||
| 2166 | |||
| 2167 | /// Get the path to the current executable, following symlinks. | ||
| 2168 | /// | ||
| 2169 | /// This function may return an error if the current executable | ||
| 2170 | /// was deleted after spawning. | ||
| 2171 | /// | ||
| 2172 | /// Returned value is a slice of out_buffer. | ||
| 2173 | /// | ||
| 2174 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 2175 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 2176 | /// | ||
| 2177 | /// On Linux, depends on procfs being mounted. If the currently executing binary has | ||
| 2178 | /// been deleted, the file path looks something like "/a/b/c/exe (deleted)". | ||
| 2179 | /// | ||
| 2180 | /// See also: | ||
| 2181 | /// * `executableDirPath` - to obtain only the directory | ||
| 2182 | /// * `openExecutable` - to obtain only an open file handle | ||
| 2183 | pub fn executablePath(io: Io, out_buffer: []u8) ExecutablePathError!usize { | ||
| 2184 | return io.vtable.processExecutablePath(io.userdata, out_buffer); | ||
| 2185 | } | ||
| 2186 | |||
| 2187 | /// Get the directory path that contains the current executable. | ||
| 2188 | /// | ||
| 2189 | /// Returns index into `out_buffer`. | ||
| 2190 | /// | ||
| 2191 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). | ||
| 2192 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. | ||
| 2193 | pub fn executableDirPath(out_buffer: []u8) ExecutablePathError!usize { | ||
| 2194 | const n = try executablePath(out_buffer); | ||
| 2195 | // Assert that the OS APIs return absolute paths, and therefore dirname | ||
| 2196 | // will not return null. | ||
| 2197 | return std.fs.path.dirname(out_buffer[0..n]).?; | ||
| 2198 | } | ||
| 2199 | |||
| 2200 | /// Same as `executableDirPath` except allocates the result. | ||
| 2201 | pub fn executableDirPathAlloc(allocator: Allocator) ![]u8 { | ||
| 2202 | var buffer: [max_path_bytes]u8 = undefined; | ||
| 2203 | return allocator.dupe(u8, try executableDirPath(&buffer)); | ||
| 2204 | } | ||
| 2205 | |||
| 2206 | pub const OpenExecutableError = File.OpenError || ExecutablePathError || File.LockError; | ||
| 2207 | |||
| 2208 | pub fn openExecutable(io: Io, flags: File.OpenFlags) OpenExecutableError!File { | ||
| 2209 | return io.vtable.processExecutableOpen(io.userdata, flags); | ||
| 2210 | } |
lib/std/zig/system.zig+4-7| ... | @@ -209,7 +209,6 @@ pub const DetectError = error{ | ... | @@ -209,7 +209,6 @@ pub const DetectError = error{ |
| 209 | DeviceBusy, | 209 | DeviceBusy, |
| 210 | OSVersionDetectionFail, | 210 | OSVersionDetectionFail, |
| 211 | Unexpected, | 211 | Unexpected, |
| 212 | ProcessNotFound, | ||
| 213 | } || Io.Cancelable; | 212 | } || Io.Cancelable; |
| 214 | 213 | ||
| 215 | /// Given a `Target.Query`, which specifies in detail which parts of the | 214 | /// Given a `Target.Query`, which specifies in detail which parts of the |
| ... | @@ -422,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { | ... | @@ -422,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { |
| 422 | error.SocketUnconnected => return error.Unexpected, | 421 | error.SocketUnconnected => return error.Unexpected, |
| 423 | 422 | ||
| 424 | error.AccessDenied, | 423 | error.AccessDenied, |
| 425 | error.ProcessNotFound, | ||
| 426 | error.SymLinkLoop, | 424 | error.SymLinkLoop, |
| 427 | error.ProcessFdQuotaExceeded, | 425 | error.ProcessFdQuotaExceeded, |
| 428 | error.SystemFdQuotaExceeded, | 426 | error.SystemFdQuotaExceeded, |
| ... | @@ -553,7 +551,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{ | ... | @@ -553,7 +551,6 @@ pub const AbiAndDynamicLinkerFromFileError = error{ |
| 553 | SystemResources, | 551 | SystemResources, |
| 554 | ProcessFdQuotaExceeded, | 552 | ProcessFdQuotaExceeded, |
| 555 | SystemFdQuotaExceeded, | 553 | SystemFdQuotaExceeded, |
| 556 | ProcessNotFound, | ||
| 557 | IsDir, | 554 | IsDir, |
| 558 | WouldBlock, | 555 | WouldBlock, |
| 559 | InputOutput, | 556 | InputOutput, |
| ... | @@ -693,8 +690,10 @@ fn abiAndDynamicLinkerFromFile( | ... | @@ -693,8 +690,10 @@ fn abiAndDynamicLinkerFromFile( |
| 693 | 690 | ||
| 694 | // So far, no luck. Next we try to see if the information is | 691 | // So far, no luck. Next we try to see if the information is |
| 695 | // present in the symlink data for the dynamic linker path. | 692 | // present in the symlink data for the dynamic linker path. |
| 696 | var link_buf: [posix.PATH_MAX]u8 = undefined; | 693 | var link_buffer: [posix.PATH_MAX]u8 = undefined; |
| 697 | const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) { | 694 | const link_name = if (Io.Dir.readLinkAbsolute(io, dl_path, &link_buffer)) |n| |
| 695 | link_buffer[0..n] | ||
| 696 | else |err| switch (err) { | ||
| 698 | error.NameTooLong => unreachable, | 697 | error.NameTooLong => unreachable, |
| 699 | error.BadPathName => unreachable, // Windows only | 698 | error.BadPathName => unreachable, // Windows only |
| 700 | error.UnsupportedReparsePointType => unreachable, // Windows only | 699 | error.UnsupportedReparsePointType => unreachable, // Windows only |
| ... | @@ -839,7 +838,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { | ... | @@ -839,7 +838,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 839 | error.NotDir => return error.GLibCNotFound, | 838 | error.NotDir => return error.GLibCNotFound, |
| 840 | error.IsDir => return error.GLibCNotFound, | 839 | error.IsDir => return error.GLibCNotFound, |
| 841 | 840 | ||
| 842 | error.ProcessNotFound => |e| return e, | ||
| 843 | error.ProcessFdQuotaExceeded => |e| return e, | 841 | error.ProcessFdQuotaExceeded => |e| return e, |
| 844 | error.SystemFdQuotaExceeded => |e| return e, | 842 | error.SystemFdQuotaExceeded => |e| return e, |
| 845 | error.SystemResources => |e| return e, | 843 | error.SystemResources => |e| return e, |
| ... | @@ -1103,7 +1101,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ | ... | @@ -1103,7 +1101,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ |
| 1103 | error.SymLinkLoop, | 1101 | error.SymLinkLoop, |
| 1104 | error.ProcessFdQuotaExceeded, | 1102 | error.ProcessFdQuotaExceeded, |
| 1105 | error.SystemFdQuotaExceeded, | 1103 | error.SystemFdQuotaExceeded, |
| 1106 | error.ProcessNotFound, | ||
| 1107 | error.Canceled, | 1104 | error.Canceled, |
| 1108 | => |e| return e, | 1105 | => |e| return e, |
| 1109 | 1106 |