master
  1//! File System.
  2const builtin = @import("builtin");
  3const native_os = builtin.os.tag;
  4
  5const std = @import("std.zig");
  6const Io = std.Io;
  7const root = @import("root");
  8const mem = std.mem;
  9const base64 = std.base64;
 10const crypto = std.crypto;
 11const Allocator = std.mem.Allocator;
 12const assert = std.debug.assert;
 13const posix = std.posix;
 14const windows = std.os.windows;
 15
 16const is_darwin = native_os.isDarwin();
 17
 18pub const AtomicFile = @import("fs/AtomicFile.zig");
 19pub const Dir = @import("fs/Dir.zig");
 20pub const File = @import("fs/File.zig");
 21pub const path = @import("fs/path.zig");
 22
 23pub const has_executable_bit = switch (native_os) {
 24    .windows, .wasi => false,
 25    else => true,
 26};
 27
 28pub const wasi = @import("fs/wasi.zig");
 29
 30// TODO audit these APIs with respect to Dir and absolute paths
 31
 32pub const realpath = posix.realpath;
 33pub const realpathZ = posix.realpathZ;
 34pub const realpathW = posix.realpathW;
 35pub const realpathW2 = posix.realpathW2;
 36
 37pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
 38pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
 39
 40/// The maximum length of a file path that the operating system will accept.
 41///
 42/// Paths, including those returned from file system operations, may be longer
 43/// than this length, but such paths cannot be successfully passed back in
 44/// other file system operations. However, all path components returned by file
 45/// system operations are assumed to fit into a `u8` array of this length.
 46///
 47/// The byte count includes room for a null sentinel byte.
 48///
 49/// * On Windows, `[]u8` file paths are encoded as
 50///   [WTF-8](https://wtf-8.codeberg.page/).
 51/// * On WASI, `[]u8` file paths are encoded as valid UTF-8.
 52/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with
 53///   no particular encoding.
 54pub const max_path_bytes = switch (native_os) {
 55    .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => posix.PATH_MAX,
 56    // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
 57    // If it would require 4 WTF-8 bytes, then there would be a surrogate
 58    // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
 59    // +1 for the null byte at the end, which can be encoded in 1 byte.
 60    .windows => windows.PATH_MAX_WIDE * 3 + 1,
 61    else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX"))
 62        root.os.PATH_MAX
 63    else
 64        @compileError("PATH_MAX not implemented for " ++ @tagName(native_os)),
 65};
 66
 67/// This represents the maximum size of a `[]u8` file name component that
 68/// the platform's common file systems support. File name components returned by file system
 69/// operations are likely to fit into a `u8` array of this length, but
 70/// (depending on the platform) this assumption may not hold for every configuration.
 71/// The byte count does not include a null sentinel byte.
 72/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/).
 73/// On WASI, file name components are encoded as valid UTF-8.
 74/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
 75pub const max_name_bytes = switch (native_os) {
 76    .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity => posix.NAME_MAX,
 77    // Haiku's NAME_MAX includes the null terminator, so subtract one.
 78    .haiku => posix.NAME_MAX - 1,
 79    // Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
 80    // If it would require 4 WTF-8 bytes, then there would be a surrogate
 81    // pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
 82    .windows => windows.NAME_MAX * 3,
 83    // For WASI, the MAX_NAME will depend on the host OS, so it needs to be
 84    // as large as the largest max_name_bytes (Windows) in order to work on any host OS.
 85    // TODO determine if this is a reasonable approach
 86    .wasi => windows.NAME_MAX * 3,
 87    else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX"))
 88        root.os.NAME_MAX
 89    else
 90        @compileError("NAME_MAX not implemented for " ++ @tagName(native_os)),
 91};
 92
 93pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;
 94
 95/// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
 96pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
 97
 98/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
 99pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
100
101/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
102/// are absolute. See `Dir.copyFile` for a function that operates on both
103/// absolute and relative paths.
104/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
105/// On WASI, both paths should be encoded as valid UTF-8.
106/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
107pub fn copyFileAbsolute(
108    source_path: []const u8,
109    dest_path: []const u8,
110    args: Dir.CopyFileOptions,
111) !void {
112    assert(path.isAbsolute(source_path));
113    assert(path.isAbsolute(dest_path));
114    const my_cwd = cwd();
115    return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
116}
117
118test copyFileAbsolute {}
119
120/// Create a new directory, based on an absolute path.
121/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
122/// on both absolute and relative paths.
123/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
124/// On WASI, `absolute_path` should be encoded as valid UTF-8.
125/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
126pub fn makeDirAbsolute(absolute_path: []const u8) !void {
127    assert(path.isAbsolute(absolute_path));
128    return posix.mkdir(absolute_path, Dir.default_mode);
129}
130
131test makeDirAbsolute {}
132
133/// Same as `makeDirAbsolute` except the parameter is null-terminated.
134pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
135    assert(path.isAbsoluteZ(absolute_path_z));
136    return posix.mkdirZ(absolute_path_z, Dir.default_mode);
137}
138
139test makeDirAbsoluteZ {}
140
141/// Same as `Dir.deleteDir` except the path is absolute.
142/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
143/// On WASI, `dir_path` should be encoded as valid UTF-8.
144/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
145pub fn deleteDirAbsolute(dir_path: []const u8) !void {
146    assert(path.isAbsolute(dir_path));
147    return posix.rmdir(dir_path);
148}
149
150/// Same as `deleteDirAbsolute` except the path parameter is null-terminated.
151pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
152    assert(path.isAbsoluteZ(dir_path));
153    return posix.rmdirZ(dir_path);
154}
155
156/// Same as `Dir.rename` except the paths are absolute.
157/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
158/// On WASI, both paths should be encoded as valid UTF-8.
159/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
160pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
161    assert(path.isAbsolute(old_path));
162    assert(path.isAbsolute(new_path));
163    return posix.rename(old_path, new_path);
164}
165
166/// Same as `renameAbsolute` except the path parameters are null-terminated.
167pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
168    assert(path.isAbsoluteZ(old_path));
169    assert(path.isAbsoluteZ(new_path));
170    return posix.renameZ(old_path, new_path);
171}
172
173/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
174pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
175    return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
176}
177
178/// Same as `rename` except the parameters are null-terminated.
179pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
180    return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
181}
182
183/// Deprecated in favor of `Io.Dir.cwd`.
184pub fn cwd() Dir {
185    if (native_os == .windows) {
186        return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };
187    } else if (native_os == .wasi) {
188        return .{ .fd = std.options.wasiCwd() };
189    } else {
190        return .{ .fd = posix.AT.FDCWD };
191    }
192}
193
194pub fn defaultWasiCwd() std.os.wasi.fd_t {
195    // Expect the first preopen to be current working directory.
196    return 3;
197}
198
199/// Opens a directory at the given path. The directory is a system resource that remains
200/// open until `close` is called on the result.
201/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
202///
203/// Asserts that the path parameter has no null bytes.
204/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
205/// On WASI, `absolute_path` should be encoded as valid UTF-8.
206/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
207pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
208    assert(path.isAbsolute(absolute_path));
209    return cwd().openDir(absolute_path, flags);
210}
211
212/// Same as `openDirAbsolute` but the path parameter is null-terminated.
213pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
214    assert(path.isAbsoluteZ(absolute_path_c));
215    return cwd().openDirZ(absolute_path_c, flags);
216}
217/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
218/// Call `File.close` to release the resource.
219/// Asserts that the path is absolute. See `Dir.openFile` for a function that
220/// operates on both absolute and relative paths.
221/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
222/// that accepts a null-terminated path.
223/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
224/// On WASI, `absolute_path` should be encoded as valid UTF-8.
225/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
226pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
227    assert(path.isAbsolute(absolute_path));
228    return cwd().openFile(absolute_path, flags);
229}
230
231/// Test accessing `path`.
232/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
233/// For example, instead of testing if a file exists and then opening it, just
234/// open it and handle the error for file not found.
235/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
236/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
237/// On WASI, `absolute_path` should be encoded as valid UTF-8.
238/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
239pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void {
240    assert(path.isAbsolute(absolute_path));
241    try cwd().access(absolute_path, flags);
242}
243/// Creates, opens, or overwrites a file with write access, based on an absolute path.
244/// Call `File.close` to release the resource.
245/// Asserts that the path is absolute. See `Dir.createFile` for a function that
246/// operates on both absolute and relative paths.
247/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
248/// that accepts a null-terminated path.
249/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
250/// On WASI, `absolute_path` should be encoded as valid UTF-8.
251/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
252pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
253    assert(path.isAbsolute(absolute_path));
254    return cwd().createFile(absolute_path, flags);
255}
256
257/// Delete a file name and possibly the file it refers to, based on an absolute path.
258/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
259/// operates on both absolute and relative paths.
260/// Asserts that the path parameter has no null bytes.
261/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
262/// On WASI, `absolute_path` should be encoded as valid UTF-8.
263/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
264pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
265    assert(path.isAbsolute(absolute_path));
266    return cwd().deleteFile(absolute_path);
267}
268
269/// Removes a symlink, file, or directory.
270/// This is equivalent to `Dir.deleteTree` with the base directory.
271/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
272/// operates on both absolute and relative paths.
273/// Asserts that the path parameter has no null bytes.
274/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
275/// On WASI, `absolute_path` should be encoded as valid UTF-8.
276/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
277pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
278    assert(path.isAbsolute(absolute_path));
279    const dirname = path.dirname(absolute_path) orelse return error{
280        /// Attempt to remove the root file system path.
281        /// This error is unreachable if `absolute_path` is relative.
282        CannotDeleteRootDirectory,
283    }.CannotDeleteRootDirectory;
284
285    var dir = try cwd().openDir(dirname, .{});
286    defer dir.close();
287
288    return dir.deleteTree(path.basename(absolute_path));
289}
290
291/// Same as `Dir.readLink`, except it asserts the path is absolute.
292/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
293/// On WASI, `pathname` should be encoded as valid UTF-8.
294/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
295pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8 {
296    assert(path.isAbsolute(pathname));
297    return posix.readlink(pathname, buffer);
298}
299
300/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
301/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
302/// one; the latter case is known as a dangling link.
303/// If `sym_link_path` exists, it will not be overwritten.
304/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
305/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
306/// On WASI, both paths should be encoded as valid UTF-8.
307/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
308pub fn symLinkAbsolute(
309    target_path: []const u8,
310    sym_link_path: []const u8,
311    flags: Dir.SymLinkFlags,
312) !void {
313    assert(path.isAbsolute(target_path));
314    assert(path.isAbsolute(sym_link_path));
315    if (native_os == .windows) {
316        const target_path_w = try windows.sliceToPrefixedFileW(null, target_path);
317        const sym_link_path_w = try windows.sliceToPrefixedFileW(null, sym_link_path);
318        return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
319    }
320    return posix.symlink(target_path, sym_link_path);
321}
322
323/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
324/// Note that this function will by default try creating a symbolic link to a file. If you would
325/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
326/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
327pub fn symLinkAbsoluteW(
328    target_path_w: [*:0]const u16,
329    sym_link_path_w: [*:0]const u16,
330    flags: Dir.SymLinkFlags,
331) !void {
332    assert(path.isAbsoluteWindowsW(target_path_w));
333    assert(path.isAbsoluteWindowsW(sym_link_path_w));
334    return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);
335}
336
337pub const OpenSelfExeError = Io.File.OpenSelfExeError;
338
339/// Deprecated in favor of `Io.File.openSelfExe`.
340pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
341    if (native_os == .linux or native_os == .serenity or native_os == .windows) {
342        var threaded: Io.Threaded = .init_single_threaded;
343        const io = threaded.ioBasic();
344        return .adaptFromNewApi(try Io.File.openSelfExe(io, flags));
345    }
346    // Use of max_path_bytes here is valid as the resulting path is immediately
347    // opened with no modification.
348    var buf: [max_path_bytes]u8 = undefined;
349    const self_exe_path = try selfExePath(&buf);
350    buf[self_exe_path.len] = 0;
351    return openFileAbsolute(buf[0..self_exe_path.len :0], flags);
352}
353
354// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded
355pub const SelfExePathError = error{
356    FileNotFound,
357    AccessDenied,
358    NameTooLong,
359    NotSupported,
360    NotDir,
361    SymLinkLoop,
362    InputOutput,
363    FileTooBig,
364    IsDir,
365    ProcessFdQuotaExceeded,
366    SystemFdQuotaExceeded,
367    NoDevice,
368    SystemResources,
369    NoSpaceLeft,
370    FileSystem,
371    BadPathName,
372    DeviceBusy,
373    SharingViolation,
374    PipeBusy,
375    NotLink,
376    PathAlreadyExists,
377
378    /// On Windows, `\\server` or `\\server\share` was not found.
379    NetworkNotFound,
380    ProcessNotFound,
381
382    /// On Windows, antivirus software is enabled by default. It can be
383    /// disabled, but Windows Update sometimes ignores the user's preference
384    /// and re-enables it. When enabled, antivirus software on Windows
385    /// intercepts file system operations and makes them significantly slower
386    /// in addition to possibly failing with this error code.
387    AntivirusInterference,
388
389    /// On Windows, the volume does not contain a recognized file system. File
390    /// system drivers might not be loaded, or the volume may be corrupt.
391    UnrecognizedVolume,
392
393    Canceled,
394} || posix.SysCtlError;
395
396/// `selfExePath` except allocates the result on the heap.
397/// Caller owns returned memory.
398pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
399    // Use of max_path_bytes here is justified as, at least on one tested Linux
400    // system, readlink will completely fail to return a result larger than
401    // PATH_MAX even if given a sufficiently large buffer. This makes it
402    // fundamentally impossible to get the selfExePath of a program running in
403    // a very deeply nested directory chain in this way.
404    // TODO(#4812): Investigate other systems and whether it is possible to get
405    // this path by trying larger and larger buffers until one succeeds.
406    var buf: [max_path_bytes]u8 = undefined;
407    return allocator.dupe(u8, try selfExePath(&buf));
408}
409
410/// Get the path to the current executable. Follows symlinks.
411/// If you only need the directory, use selfExeDirPath.
412/// If you only want an open file handle, use openSelfExe.
413/// This function may return an error if the current executable
414/// was deleted after spawning.
415/// Returned value is a slice of out_buffer.
416/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
417/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
418///
419/// On Linux, depends on procfs being mounted. If the currently executing binary has
420/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
421/// TODO make the return type of this a null terminated pointer
422pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
423    if (is_darwin) {
424        // Note that _NSGetExecutablePath() will return "a path" to
425        // the executable not a "real path" to the executable.
426        var symlink_path_buf: [max_path_bytes:0]u8 = undefined;
427        var u32_len: u32 = max_path_bytes + 1; // include the sentinel
428        const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len);
429        if (rc != 0) return error.NameTooLong;
430
431        var real_path_buf: [max_path_bytes]u8 = undefined;
432        const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
433            error.NetworkNotFound => unreachable, // Windows-only
434            else => |e| return e,
435        };
436        if (real_path.len > out_buffer.len) return error.NameTooLong;
437        const result = out_buffer[0..real_path.len];
438        @memcpy(result, real_path);
439        return result;
440    }
441    switch (native_os) {
442        .linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
443            error.UnsupportedReparsePointType => unreachable, // Windows-only
444            error.NetworkNotFound => unreachable, // Windows-only
445            else => |e| return e,
446        },
447        .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
448            error.UnsupportedReparsePointType => unreachable, // Windows-only
449            error.NetworkNotFound => unreachable, // Windows-only
450            else => |e| return e,
451        },
452        .freebsd, .dragonfly => {
453            var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
454            var out_len: usize = out_buffer.len;
455            try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
456            // TODO could this slice from 0 to out_len instead?
457            return mem.sliceTo(out_buffer, 0);
458        },
459        .netbsd => {
460            var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
461            var out_len: usize = out_buffer.len;
462            try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
463            // TODO could this slice from 0 to out_len instead?
464            return mem.sliceTo(out_buffer, 0);
465        },
466        .openbsd, .haiku => {
467            // OpenBSD doesn't support getting the path of a running process, so try to guess it
468            if (std.os.argv.len == 0)
469                return error.FileNotFound;
470
471            const argv0 = mem.span(std.os.argv[0]);
472            if (mem.indexOf(u8, argv0, "/") != null) {
473                // argv[0] is a path (relative or absolute): use realpath(3) directly
474                var real_path_buf: [max_path_bytes]u8 = undefined;
475                const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) {
476                    error.NetworkNotFound => unreachable, // Windows-only
477                    else => |e| return e,
478                };
479                if (real_path.len > out_buffer.len)
480                    return error.NameTooLong;
481                const result = out_buffer[0..real_path.len];
482                @memcpy(result, real_path);
483                return result;
484            } else if (argv0.len != 0) {
485                // argv[0] is not empty (and not a path): search it inside PATH
486                const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound;
487                var path_it = mem.tokenizeScalar(u8, PATH, path.delimiter);
488                while (path_it.next()) |a_path| {
489                    var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined;
490                    const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{
491                        a_path,
492                        std.os.argv[0],
493                        0,
494                    }) catch continue;
495
496                    var real_path_buf: [max_path_bytes]u8 = undefined;
497                    if (posix.realpathZ(resolved_path, &real_path_buf)) |real_path| {
498                        // found a file, and hope it is the right file
499                        if (real_path.len > out_buffer.len)
500                            return error.NameTooLong;
501                        const result = out_buffer[0..real_path.len];
502                        @memcpy(result, real_path);
503                        return result;
504                    } else |_| continue;
505                }
506            }
507            return error.FileNotFound;
508        },
509        .windows => {
510            const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
511            const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
512
513            // If ImagePathName is a symlink, then it will contain the path of the
514            // symlink, not the path that the symlink points to. We want the path
515            // that the symlink points to, though, so we need to get the realpath.
516            var pathname_w = try windows.wToPrefixedFileW(null, image_path_name);
517
518            const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data);
519
520            const len = std.unicode.calcWtf8Len(wide_slice);
521            if (len > out_buffer.len)
522                return error.NameTooLong;
523
524            const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
525            return out_buffer[0..end_index];
526        },
527        else => @compileError("std.fs.selfExePath not supported for this target"),
528    }
529}
530
531/// `selfExeDirPath` except allocates the result on the heap.
532/// Caller owns returned memory.
533pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
534    // Use of max_path_bytes here is justified as, at least on one tested Linux
535    // system, readlink will completely fail to return a result larger than
536    // PATH_MAX even if given a sufficiently large buffer. This makes it
537    // fundamentally impossible to get the selfExeDirPath of a program running
538    // in a very deeply nested directory chain in this way.
539    // TODO(#4812): Investigate other systems and whether it is possible to get
540    // this path by trying larger and larger buffers until one succeeds.
541    var buf: [max_path_bytes]u8 = undefined;
542    return allocator.dupe(u8, try selfExeDirPath(&buf));
543}
544
545/// Get the directory path that contains the current executable.
546/// Returned value is a slice of out_buffer.
547/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
548/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
549pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
550    const self_exe_path = try selfExePath(out_buffer);
551    // Assume that the OS APIs return absolute paths, and therefore dirname
552    // will not return null.
553    return path.dirname(self_exe_path).?;
554}
555
556/// `realpath`, except caller must free the returned memory.
557/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
558/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
559/// See also `Dir.realpath`.
560pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
561    // Use of max_path_bytes here is valid as the realpath function does not
562    // have a variant that takes an arbitrary-size buffer.
563    // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
564    // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
565    // paths. musl supports passing NULL but restricts the output to PATH_MAX
566    // anyway.
567    var buf: [max_path_bytes]u8 = undefined;
568    return allocator.dupe(u8, try posix.realpath(pathname, &buf));
569}
570
571test {
572    _ = AtomicFile;
573    _ = Dir;
574    _ = File;
575    _ = path;
576    _ = @import("fs/test.zig");
577    _ = @import("fs/get_app_data_dir.zig");
578}