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}