mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Apple's own headers and tbd files prefer to think of Mac Catalyst as a distinct OS target. Earlier, when DriverKit support was added to LLVM, it was represented a distinct OS. So why Apple decided to only represent Mac Catalyst as an ABI in the target triple is beyond me. But this isn't the first time they've ignored established target triple norms (see: armv7k and aarch64_32) and it probably won't be the last. While doing this, I also audited all Darwin OS prongs throughout the codebase and made sure they cover all the tags.
579 lines
27 KiB
Zig
579 lines
27 KiB
Zig
//! File System.
|
|
const builtin = @import("builtin");
|
|
const native_os = builtin.os.tag;
|
|
|
|
const std = @import("std.zig");
|
|
const Io = std.Io;
|
|
const root = @import("root");
|
|
const mem = std.mem;
|
|
const base64 = std.base64;
|
|
const crypto = std.crypto;
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
const posix = std.posix;
|
|
const windows = std.os.windows;
|
|
|
|
const is_darwin = native_os.isDarwin();
|
|
|
|
pub const AtomicFile = @import("fs/AtomicFile.zig");
|
|
pub const Dir = @import("fs/Dir.zig");
|
|
pub const File = @import("fs/File.zig");
|
|
pub const path = @import("fs/path.zig");
|
|
|
|
pub const has_executable_bit = switch (native_os) {
|
|
.windows, .wasi => false,
|
|
else => true,
|
|
};
|
|
|
|
pub const wasi = @import("fs/wasi.zig");
|
|
|
|
// TODO audit these APIs with respect to Dir and absolute paths
|
|
|
|
pub const realpath = posix.realpath;
|
|
pub const realpathZ = posix.realpathZ;
|
|
pub const realpathW = posix.realpathW;
|
|
pub const realpathW2 = posix.realpathW2;
|
|
|
|
pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
|
|
pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
|
|
|
|
/// The maximum length of a file path that the operating system will accept.
|
|
///
|
|
/// Paths, including those returned from file system operations, may be longer
|
|
/// than this length, but such paths cannot be successfully passed back in
|
|
/// other file system operations. However, all path components returned by file
|
|
/// system operations are assumed to fit into a `u8` array of this length.
|
|
///
|
|
/// The byte count includes room for a null sentinel byte.
|
|
///
|
|
/// * On Windows, `[]u8` file paths are encoded as
|
|
/// [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// * On WASI, `[]u8` file paths are encoded as valid UTF-8.
|
|
/// * On other platforms, `[]u8` file paths are opaque sequences of bytes with
|
|
/// no particular encoding.
|
|
pub const max_path_bytes = switch (native_os) {
|
|
.linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .illumos, .plan9, .emscripten, .wasi, .serenity => posix.PATH_MAX,
|
|
// Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes.
|
|
// If it would require 4 WTF-8 bytes, then there would be a surrogate
|
|
// pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
|
|
// +1 for the null byte at the end, which can be encoded in 1 byte.
|
|
.windows => windows.PATH_MAX_WIDE * 3 + 1,
|
|
else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX"))
|
|
root.os.PATH_MAX
|
|
else
|
|
@compileError("PATH_MAX not implemented for " ++ @tagName(native_os)),
|
|
};
|
|
|
|
/// This represents the maximum size of a `[]u8` file name component that
|
|
/// the platform's common file systems support. File name components returned by file system
|
|
/// operations are likely to fit into a `u8` array of this length, but
|
|
/// (depending on the platform) this assumption may not hold for every configuration.
|
|
/// The byte count does not include a null sentinel byte.
|
|
/// On Windows, `[]u8` file name components are encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, file name components are encoded as valid UTF-8.
|
|
/// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding.
|
|
pub const max_name_bytes = switch (native_os) {
|
|
.linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .openbsd, .netbsd, .dragonfly, .illumos, .serenity => posix.NAME_MAX,
|
|
// Haiku's NAME_MAX includes the null terminator, so subtract one.
|
|
.haiku => posix.NAME_MAX - 1,
|
|
// Each WTF-16LE character may be expanded to 3 WTF-8 bytes.
|
|
// If it would require 4 WTF-8 bytes, then there would be a surrogate
|
|
// pair in the WTF-16LE, and we (over)account 3 bytes for it that way.
|
|
.windows => windows.NAME_MAX * 3,
|
|
// For WASI, the MAX_NAME will depend on the host OS, so it needs to be
|
|
// as large as the largest max_name_bytes (Windows) in order to work on any host OS.
|
|
// TODO determine if this is a reasonable approach
|
|
.wasi => windows.NAME_MAX * 3,
|
|
else => if (@hasDecl(root, "os") and @hasDecl(root.os, "NAME_MAX"))
|
|
root.os.NAME_MAX
|
|
else
|
|
@compileError("NAME_MAX not implemented for " ++ @tagName(native_os)),
|
|
};
|
|
|
|
pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;
|
|
|
|
/// Base64 encoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
|
|
pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
|
|
|
|
/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
|
|
pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
|
|
|
|
/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
|
|
/// are absolute. See `Dir.copyFile` for a function that operates on both
|
|
/// absolute and relative paths.
|
|
/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, both paths should be encoded as valid UTF-8.
|
|
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
|
|
pub fn copyFileAbsolute(
|
|
source_path: []const u8,
|
|
dest_path: []const u8,
|
|
args: Dir.CopyFileOptions,
|
|
) !void {
|
|
assert(path.isAbsolute(source_path));
|
|
assert(path.isAbsolute(dest_path));
|
|
const my_cwd = cwd();
|
|
return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
|
|
}
|
|
|
|
test copyFileAbsolute {}
|
|
|
|
/// Create a new directory, based on an absolute path.
|
|
/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
|
|
/// on both absolute and relative paths.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn makeDirAbsolute(absolute_path: []const u8) !void {
|
|
assert(path.isAbsolute(absolute_path));
|
|
return posix.mkdir(absolute_path, Dir.default_mode);
|
|
}
|
|
|
|
test makeDirAbsolute {}
|
|
|
|
/// Same as `makeDirAbsolute` except the parameter is null-terminated.
|
|
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
|
|
assert(path.isAbsoluteZ(absolute_path_z));
|
|
return posix.mkdirZ(absolute_path_z, Dir.default_mode);
|
|
}
|
|
|
|
test makeDirAbsoluteZ {}
|
|
|
|
/// Same as `Dir.deleteDir` except the path is absolute.
|
|
/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `dir_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn deleteDirAbsolute(dir_path: []const u8) !void {
|
|
assert(path.isAbsolute(dir_path));
|
|
return posix.rmdir(dir_path);
|
|
}
|
|
|
|
/// Same as `deleteDirAbsolute` except the path parameter is null-terminated.
|
|
pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
|
|
assert(path.isAbsoluteZ(dir_path));
|
|
return posix.rmdirZ(dir_path);
|
|
}
|
|
|
|
/// Same as `Dir.rename` except the paths are absolute.
|
|
/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, both paths should be encoded as valid UTF-8.
|
|
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
|
|
pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
|
|
assert(path.isAbsolute(old_path));
|
|
assert(path.isAbsolute(new_path));
|
|
return posix.rename(old_path, new_path);
|
|
}
|
|
|
|
/// Same as `renameAbsolute` except the path parameters are null-terminated.
|
|
pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
|
|
assert(path.isAbsoluteZ(old_path));
|
|
assert(path.isAbsoluteZ(new_path));
|
|
return posix.renameZ(old_path, new_path);
|
|
}
|
|
|
|
/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
|
|
pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
|
|
return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
|
|
}
|
|
|
|
/// Same as `rename` except the parameters are null-terminated.
|
|
pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
|
|
return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
|
|
}
|
|
|
|
/// Deprecated in favor of `Io.Dir.cwd`.
|
|
pub fn cwd() Dir {
|
|
if (native_os == .windows) {
|
|
return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };
|
|
} else if (native_os == .wasi) {
|
|
return .{ .fd = std.options.wasiCwd() };
|
|
} else {
|
|
return .{ .fd = posix.AT.FDCWD };
|
|
}
|
|
}
|
|
|
|
pub fn defaultWasiCwd() std.os.wasi.fd_t {
|
|
// Expect the first preopen to be current working directory.
|
|
return 3;
|
|
}
|
|
|
|
/// Opens a directory at the given path. The directory is a system resource that remains
|
|
/// open until `close` is called on the result.
|
|
/// See `openDirAbsoluteZ` for a function that accepts a null-terminated path.
|
|
///
|
|
/// Asserts that the path parameter has no null bytes.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
|
|
assert(path.isAbsolute(absolute_path));
|
|
return cwd().openDir(absolute_path, flags);
|
|
}
|
|
|
|
/// Same as `openDirAbsolute` but the path parameter is null-terminated.
|
|
pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
|
|
assert(path.isAbsoluteZ(absolute_path_c));
|
|
return cwd().openDirZ(absolute_path_c, flags);
|
|
}
|
|
/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
|
|
/// Call `File.close` to release the resource.
|
|
/// Asserts that the path is absolute. See `Dir.openFile` for a function that
|
|
/// operates on both absolute and relative paths.
|
|
/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
|
|
/// that accepts a null-terminated path.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
|
assert(path.isAbsolute(absolute_path));
|
|
return cwd().openFile(absolute_path, flags);
|
|
}
|
|
|
|
/// Test accessing `path`.
|
|
/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
|
|
/// For example, instead of testing if a file exists and then opening it, just
|
|
/// open it and handle the error for file not found.
|
|
/// See `accessAbsoluteZ` for a function that accepts a null-terminated path.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void {
|
|
assert(path.isAbsolute(absolute_path));
|
|
try cwd().access(absolute_path, flags);
|
|
}
|
|
/// Creates, opens, or overwrites a file with write access, based on an absolute path.
|
|
/// Call `File.close` to release the resource.
|
|
/// Asserts that the path is absolute. See `Dir.createFile` for a function that
|
|
/// operates on both absolute and relative paths.
|
|
/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function
|
|
/// that accepts a null-terminated path.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
|
assert(path.isAbsolute(absolute_path));
|
|
return cwd().createFile(absolute_path, flags);
|
|
}
|
|
|
|
/// Delete a file name and possibly the file it refers to, based on an absolute path.
|
|
/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
|
|
/// operates on both absolute and relative paths.
|
|
/// Asserts that the path parameter has no null bytes.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
|
|
assert(path.isAbsolute(absolute_path));
|
|
return cwd().deleteFile(absolute_path);
|
|
}
|
|
|
|
/// Removes a symlink, file, or directory.
|
|
/// This is equivalent to `Dir.deleteTree` with the base directory.
|
|
/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
|
|
/// operates on both absolute and relative paths.
|
|
/// Asserts that the path parameter has no null bytes.
|
|
/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `absolute_path` should be encoded as valid UTF-8.
|
|
/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
|
|
assert(path.isAbsolute(absolute_path));
|
|
const dirname = path.dirname(absolute_path) orelse return error{
|
|
/// Attempt to remove the root file system path.
|
|
/// This error is unreachable if `absolute_path` is relative.
|
|
CannotDeleteRootDirectory,
|
|
}.CannotDeleteRootDirectory;
|
|
|
|
var dir = try cwd().openDir(dirname, .{});
|
|
defer dir.close();
|
|
|
|
return dir.deleteTree(path.basename(absolute_path));
|
|
}
|
|
|
|
/// Same as `Dir.readLink`, except it asserts the path is absolute.
|
|
/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, `pathname` should be encoded as valid UTF-8.
|
|
/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
|
|
pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8 {
|
|
assert(path.isAbsolute(pathname));
|
|
return posix.readlink(pathname, buffer);
|
|
}
|
|
|
|
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
|
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
|
/// one; the latter case is known as a dangling link.
|
|
/// If `sym_link_path` exists, it will not be overwritten.
|
|
/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
|
|
/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On WASI, both paths should be encoded as valid UTF-8.
|
|
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
|
|
pub fn symLinkAbsolute(
|
|
target_path: []const u8,
|
|
sym_link_path: []const u8,
|
|
flags: Dir.SymLinkFlags,
|
|
) !void {
|
|
assert(path.isAbsolute(target_path));
|
|
assert(path.isAbsolute(sym_link_path));
|
|
if (native_os == .windows) {
|
|
const target_path_w = try windows.sliceToPrefixedFileW(null, target_path);
|
|
const sym_link_path_w = try windows.sliceToPrefixedFileW(null, sym_link_path);
|
|
return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
|
|
}
|
|
return posix.symlink(target_path, sym_link_path);
|
|
}
|
|
|
|
/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 LE encoded.
|
|
/// Note that this function will by default try creating a symbolic link to a file. If you would
|
|
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
|
|
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
|
|
pub fn symLinkAbsoluteW(
|
|
target_path_w: [*:0]const u16,
|
|
sym_link_path_w: [*:0]const u16,
|
|
flags: Dir.SymLinkFlags,
|
|
) !void {
|
|
assert(path.isAbsoluteWindowsW(target_path_w));
|
|
assert(path.isAbsoluteWindowsW(sym_link_path_w));
|
|
return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);
|
|
}
|
|
|
|
pub const OpenSelfExeError = Io.File.OpenSelfExeError;
|
|
|
|
/// Deprecated in favor of `Io.File.openSelfExe`.
|
|
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
|
|
if (native_os == .linux or native_os == .serenity or native_os == .windows) {
|
|
var threaded: Io.Threaded = .init_single_threaded;
|
|
const io = threaded.ioBasic();
|
|
return .adaptFromNewApi(try Io.File.openSelfExe(io, flags));
|
|
}
|
|
// Use of max_path_bytes here is valid as the resulting path is immediately
|
|
// opened with no modification.
|
|
var buf: [max_path_bytes]u8 = undefined;
|
|
const self_exe_path = try selfExePath(&buf);
|
|
buf[self_exe_path.len] = 0;
|
|
return openFileAbsolute(buf[0..self_exe_path.len :0], flags);
|
|
}
|
|
|
|
// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded
|
|
pub const SelfExePathError = error{
|
|
FileNotFound,
|
|
AccessDenied,
|
|
NameTooLong,
|
|
NotSupported,
|
|
NotDir,
|
|
SymLinkLoop,
|
|
InputOutput,
|
|
FileTooBig,
|
|
IsDir,
|
|
ProcessFdQuotaExceeded,
|
|
SystemFdQuotaExceeded,
|
|
NoDevice,
|
|
SystemResources,
|
|
NoSpaceLeft,
|
|
FileSystem,
|
|
BadPathName,
|
|
DeviceBusy,
|
|
SharingViolation,
|
|
PipeBusy,
|
|
NotLink,
|
|
PathAlreadyExists,
|
|
|
|
/// On Windows, `\\server` or `\\server\share` was not found.
|
|
NetworkNotFound,
|
|
ProcessNotFound,
|
|
|
|
/// On Windows, antivirus software is enabled by default. It can be
|
|
/// disabled, but Windows Update sometimes ignores the user's preference
|
|
/// and re-enables it. When enabled, antivirus software on Windows
|
|
/// intercepts file system operations and makes them significantly slower
|
|
/// in addition to possibly failing with this error code.
|
|
AntivirusInterference,
|
|
|
|
/// On Windows, the volume does not contain a recognized file system. File
|
|
/// system drivers might not be loaded, or the volume may be corrupt.
|
|
UnrecognizedVolume,
|
|
|
|
Canceled,
|
|
} || posix.SysCtlError;
|
|
|
|
/// `selfExePath` except allocates the result on the heap.
|
|
/// Caller owns returned memory.
|
|
pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
|
|
// Use of max_path_bytes here is justified as, at least on one tested Linux
|
|
// system, readlink will completely fail to return a result larger than
|
|
// PATH_MAX even if given a sufficiently large buffer. This makes it
|
|
// fundamentally impossible to get the selfExePath of a program running in
|
|
// a very deeply nested directory chain in this way.
|
|
// TODO(#4812): Investigate other systems and whether it is possible to get
|
|
// this path by trying larger and larger buffers until one succeeds.
|
|
var buf: [max_path_bytes]u8 = undefined;
|
|
return allocator.dupe(u8, try selfExePath(&buf));
|
|
}
|
|
|
|
/// Get the path to the current executable. Follows symlinks.
|
|
/// If you only need the directory, use selfExeDirPath.
|
|
/// If you only want an open file handle, use openSelfExe.
|
|
/// This function may return an error if the current executable
|
|
/// was deleted after spawning.
|
|
/// Returned value is a slice of out_buffer.
|
|
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
|
///
|
|
/// On Linux, depends on procfs being mounted. If the currently executing binary has
|
|
/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
|
|
/// TODO make the return type of this a null terminated pointer
|
|
pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
|
|
if (is_darwin) {
|
|
// Note that _NSGetExecutablePath() will return "a path" to
|
|
// the executable not a "real path" to the executable.
|
|
var symlink_path_buf: [max_path_bytes:0]u8 = undefined;
|
|
var u32_len: u32 = max_path_bytes + 1; // include the sentinel
|
|
const rc = std.c._NSGetExecutablePath(&symlink_path_buf, &u32_len);
|
|
if (rc != 0) return error.NameTooLong;
|
|
|
|
var real_path_buf: [max_path_bytes]u8 = undefined;
|
|
const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
|
|
error.NetworkNotFound => unreachable, // Windows-only
|
|
else => |e| return e,
|
|
};
|
|
if (real_path.len > out_buffer.len) return error.NameTooLong;
|
|
const result = out_buffer[0..real_path.len];
|
|
@memcpy(result, real_path);
|
|
return result;
|
|
}
|
|
switch (native_os) {
|
|
.linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
|
|
error.UnsupportedReparsePointType => unreachable, // Windows-only
|
|
error.NetworkNotFound => unreachable, // Windows-only
|
|
else => |e| return e,
|
|
},
|
|
.illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
|
|
error.UnsupportedReparsePointType => unreachable, // Windows-only
|
|
error.NetworkNotFound => unreachable, // Windows-only
|
|
else => |e| return e,
|
|
},
|
|
.freebsd, .dragonfly => {
|
|
var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 };
|
|
var out_len: usize = out_buffer.len;
|
|
try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
|
|
// TODO could this slice from 0 to out_len instead?
|
|
return mem.sliceTo(out_buffer, 0);
|
|
},
|
|
.netbsd => {
|
|
var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME };
|
|
var out_len: usize = out_buffer.len;
|
|
try posix.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
|
|
// TODO could this slice from 0 to out_len instead?
|
|
return mem.sliceTo(out_buffer, 0);
|
|
},
|
|
.openbsd, .haiku => {
|
|
// OpenBSD doesn't support getting the path of a running process, so try to guess it
|
|
if (std.os.argv.len == 0)
|
|
return error.FileNotFound;
|
|
|
|
const argv0 = mem.span(std.os.argv[0]);
|
|
if (mem.indexOf(u8, argv0, "/") != null) {
|
|
// argv[0] is a path (relative or absolute): use realpath(3) directly
|
|
var real_path_buf: [max_path_bytes]u8 = undefined;
|
|
const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) {
|
|
error.NetworkNotFound => unreachable, // Windows-only
|
|
else => |e| return e,
|
|
};
|
|
if (real_path.len > out_buffer.len)
|
|
return error.NameTooLong;
|
|
const result = out_buffer[0..real_path.len];
|
|
@memcpy(result, real_path);
|
|
return result;
|
|
} else if (argv0.len != 0) {
|
|
// argv[0] is not empty (and not a path): search it inside PATH
|
|
const PATH = posix.getenvZ("PATH") orelse return error.FileNotFound;
|
|
var path_it = mem.tokenizeScalar(u8, PATH, path.delimiter);
|
|
while (path_it.next()) |a_path| {
|
|
var resolved_path_buf: [max_path_bytes - 1:0]u8 = undefined;
|
|
const resolved_path = std.fmt.bufPrintSentinel(&resolved_path_buf, "{s}/{s}", .{
|
|
a_path,
|
|
std.os.argv[0],
|
|
0,
|
|
}) catch continue;
|
|
|
|
var real_path_buf: [max_path_bytes]u8 = undefined;
|
|
if (posix.realpathZ(resolved_path, &real_path_buf)) |real_path| {
|
|
// found a file, and hope it is the right file
|
|
if (real_path.len > out_buffer.len)
|
|
return error.NameTooLong;
|
|
const result = out_buffer[0..real_path.len];
|
|
@memcpy(result, real_path);
|
|
return result;
|
|
} else |_| continue;
|
|
}
|
|
}
|
|
return error.FileNotFound;
|
|
},
|
|
.windows => {
|
|
const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
|
|
const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
|
|
|
|
// If ImagePathName is a symlink, then it will contain the path of the
|
|
// symlink, not the path that the symlink points to. We want the path
|
|
// that the symlink points to, though, so we need to get the realpath.
|
|
var pathname_w = try windows.wToPrefixedFileW(null, image_path_name);
|
|
|
|
const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data);
|
|
|
|
const len = std.unicode.calcWtf8Len(wide_slice);
|
|
if (len > out_buffer.len)
|
|
return error.NameTooLong;
|
|
|
|
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
|
|
return out_buffer[0..end_index];
|
|
},
|
|
else => @compileError("std.fs.selfExePath not supported for this target"),
|
|
}
|
|
}
|
|
|
|
/// `selfExeDirPath` except allocates the result on the heap.
|
|
/// Caller owns returned memory.
|
|
pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
|
|
// Use of max_path_bytes here is justified as, at least on one tested Linux
|
|
// system, readlink will completely fail to return a result larger than
|
|
// PATH_MAX even if given a sufficiently large buffer. This makes it
|
|
// fundamentally impossible to get the selfExeDirPath of a program running
|
|
// in a very deeply nested directory chain in this way.
|
|
// TODO(#4812): Investigate other systems and whether it is possible to get
|
|
// this path by trying larger and larger buffers until one succeeds.
|
|
var buf: [max_path_bytes]u8 = undefined;
|
|
return allocator.dupe(u8, try selfExeDirPath(&buf));
|
|
}
|
|
|
|
/// Get the directory path that contains the current executable.
|
|
/// Returned value is a slice of out_buffer.
|
|
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
|
pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
|
|
const self_exe_path = try selfExePath(out_buffer);
|
|
// Assume that the OS APIs return absolute paths, and therefore dirname
|
|
// will not return null.
|
|
return path.dirname(self_exe_path).?;
|
|
}
|
|
|
|
/// `realpath`, except caller must free the returned memory.
|
|
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
|
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
|
/// See also `Dir.realpath`.
|
|
pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
|
|
// Use of max_path_bytes here is valid as the realpath function does not
|
|
// have a variant that takes an arbitrary-size buffer.
|
|
// TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
|
|
// NULL out parameter (GNU's canonicalize_file_name) to handle overelong
|
|
// paths. musl supports passing NULL but restricts the output to PATH_MAX
|
|
// anyway.
|
|
var buf: [max_path_bytes]u8 = undefined;
|
|
return allocator.dupe(u8, try posix.realpath(pathname, &buf));
|
|
}
|
|
|
|
test {
|
|
_ = AtomicFile;
|
|
_ = Dir;
|
|
_ = File;
|
|
_ = path;
|
|
_ = @import("fs/test.zig");
|
|
_ = @import("fs/get_app_data_dir.zig");
|
|
}
|