const std = @import("std.zig");
const builtin = @import("builtin");
const root = @import("root");
const os = std.os;
const mem = std.mem;
const base64 = std.base64;
const crypto = std.crypto;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const is_darwin = builtin.os.tag.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 (builtin.os.tag) {
.windows, .wasi => false,
else => true,
};
pub const wasi = @import("fs/wasi.zig");
pub const realpath = os.realpath;
pub const realpathZ = os.realpathZ;
pub const realpathW = os.realpathW;
pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
pub const Watch = @import("fs/watch.zig").Watch;
pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
.linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9 => os.PATH_MAX,
.windows => os.windows.PATH_MAX_WIDE * 3 + 1,
.wasi => 4096,
else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX"))
root.os.PATH_MAX
else
@compileError("PATH_MAX not implemented for " ++ @tagName(builtin.os.tag)),
};
pub const MAX_NAME_BYTES = switch (builtin.os.tag) {
.linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => os.NAME_MAX,
.haiku => os.NAME_MAX - 1,
.windows => os.windows.NAME_MAX * 3,
.wasi => os.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(builtin.os.tag)),
};
pub const base64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*;
pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
.windows, .other => false,
else => true,
};
pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
if (cwd().symLink(existing_path, new_path, .{})) {
return;
} else |err| switch (err) {
error.PathAlreadyExists => {},
else => return err,
}
const dirname = path.dirname(new_path) orelse ".";
var rand_buf: [AtomicFile.random_bytes_len]u8 = undefined;
const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len));
defer allocator.free(tmp_path);
@memcpy(tmp_path[0..dirname.len], dirname);
tmp_path[dirname.len] = path.sep;
while (true) {
crypto.random.bytes(rand_buf[0..]);
_ = base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
if (cwd().symLink(existing_path, tmp_path, .{})) {
return cwd().rename(tmp_path, new_path);
} else |err| switch (err) {
error.PathAlreadyExists => continue,
else => return err,
}
}
}
pub fn updateFileAbsolute(
source_path: []const u8,
dest_path: []const u8,
args: Dir.CopyFileOptions,
) !Dir.PrevStatus {
assert(path.isAbsolute(source_path));
assert(path.isAbsolute(dest_path));
const my_cwd = cwd();
return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
}
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);
}
pub fn makeDirAbsolute(absolute_path: []const u8) !void {
assert(path.isAbsolute(absolute_path));
return os.mkdir(absolute_path, Dir.default_mode);
}
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
assert(path.isAbsoluteZ(absolute_path_z));
return os.mkdirZ(absolute_path_z, Dir.default_mode);
}
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(absolute_path_w));
return os.mkdirW(absolute_path_w, Dir.default_mode);
}
pub fn deleteDirAbsolute(dir_path: []const u8) !void {
assert(path.isAbsolute(dir_path));
return os.rmdir(dir_path);
}
pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
assert(path.isAbsoluteZ(dir_path));
return os.rmdirZ(dir_path);
}
pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(dir_path));
return os.rmdirW(dir_path);
}
pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
assert(path.isAbsolute(old_path));
assert(path.isAbsolute(new_path));
return os.rename(old_path, new_path);
}
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 os.renameZ(old_path, new_path);
}
pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(old_path));
assert(path.isAbsoluteWindowsW(new_path));
return os.renameW(old_path, new_path);
}
pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
}
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 os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
}
pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
}
pub fn cwd() Dir {
if (builtin.os.tag == .windows) {
return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
} else if (builtin.os.tag == .wasi) {
return std.options.wasiCwd();
} else {
return Dir{ .fd = os.AT.FDCWD };
}
}
pub fn defaultWasiCwd() Dir {
return .{ .fd = 3 };
}
pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
assert(path.isAbsolute(absolute_path));
return cwd().openDir(absolute_path, flags);
}
pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenDirOptions) File.OpenError!Dir {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().openDirZ(absolute_path_c, flags);
}
pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenDirOptions) File.OpenError!Dir {
assert(path.isAbsoluteWindowsW(absolute_path_c));
return cwd().openDirW(absolute_path_c, flags);
}
pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsolute(absolute_path));
return cwd().openFile(absolute_path, flags);
}
pub fn openFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().openFileZ(absolute_path_c, flags);
}
pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsoluteWindowsWTF16(absolute_path_w));
return cwd().openFileW(absolute_path_w, flags);
}
pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
assert(path.isAbsolute(absolute_path));
try cwd().access(absolute_path, flags);
}
pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
assert(path.isAbsoluteZ(absolute_path));
try cwd().accessZ(absolute_path, flags);
}
pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir.AccessError!void {
assert(path.isAbsoluteWindowsW(absolute_path));
try cwd().accessW(absolute_path, flags);
}
pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
assert(path.isAbsolute(absolute_path));
return cwd().createFile(absolute_path, flags);
}
pub fn createFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().createFileZ(absolute_path_c, flags);
}
pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {
assert(path.isAbsoluteWindowsW(absolute_path_w));
return cwd().createFileW(absolute_path_w, flags);
}
pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
assert(path.isAbsolute(absolute_path));
return cwd().deleteFile(absolute_path);
}
pub fn deleteFileAbsoluteZ(absolute_path_c: [*:0]const u8) Dir.DeleteFileError!void {
assert(path.isAbsoluteZ(absolute_path_c));
return cwd().deleteFileZ(absolute_path_c);
}
pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void {
assert(path.isAbsoluteWindowsW(absolute_path_w));
return cwd().deleteFileW(absolute_path_w);
}
pub fn deleteTreeAbsolute(absolute_path: []const u8) !void {
assert(path.isAbsolute(absolute_path));
const dirname = path.dirname(absolute_path) orelse return error{
CannotDeleteRootDirectory,
}.CannotDeleteRootDirectory;
var dir = try cwd().openDir(dirname, .{});
defer dir.close();
return dir.deleteTree(path.basename(absolute_path));
}
pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
assert(path.isAbsolute(pathname));
return os.readlink(pathname, buffer);
}
pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
assert(path.isAbsoluteWindowsW(pathname_w));
return os.readlinkW(pathname_w, buffer);
}
pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
assert(path.isAbsoluteZ(pathname_c));
return os.readlinkZ(pathname_c, buffer);
}
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 (builtin.os.tag == .windows) {
const target_path_w = try os.windows.sliceToPrefixedFileW(null, target_path);
const sym_link_path_w = try os.windows.sliceToPrefixedFileW(null, sym_link_path);
return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlink(target_path, sym_link_path);
}
pub fn symLinkAbsoluteW(
target_path_w: []const u16,
sym_link_path_w: []const u16,
flags: Dir.SymLinkFlags,
) !void {
assert(path.isAbsoluteWindowsWTF16(target_path_w));
assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
}
pub fn symLinkAbsoluteZ(
target_path_c: [*:0]const u8,
sym_link_path_c: [*:0]const u8,
flags: Dir.SymLinkFlags,
) !void {
assert(path.isAbsoluteZ(target_path_c));
assert(path.isAbsoluteZ(sym_link_path_c));
if (builtin.os.tag == .windows) {
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlinkZ(target_path_c, sym_link_path_c);
}
pub const OpenSelfExeError = error{
SharingViolation,
PathAlreadyExists,
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
InvalidUtf8,
BadPathName,
Unexpected,
} || os.OpenError || SelfExePathError || os.FlockError;
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
if (builtin.os.tag == .linux) {
return openFileAbsoluteZ("/proc/self/exe", flags);
}
if (builtin.os.tag == .windows) {
const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name);
return cwd().openFileW(prefixed_path_w.span(), flags);
}
var buf: [MAX_PATH_BYTES]u8 = undefined;
const self_exe_path = try selfExePath(&buf);
buf[self_exe_path.len] = 0;
return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
}
pub const SelfExePathError = os.ReadLinkError || os.SysCtlError || os.RealPathError;
pub fn selfExePathAlloc(allocator: Allocator) ![]u8 {
var buf: [MAX_PATH_BYTES]u8 = undefined;
return allocator.dupe(u8, try selfExePath(&buf));
}
pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
if (is_darwin) {
var symlink_path_buf: [MAX_PATH_BYTES:0]u8 = undefined;
var u32_len: u32 = MAX_PATH_BYTES + 1;
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 = try std.os.realpathZ(&symlink_path_buf, &real_path_buf);
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 (builtin.os.tag) {
.linux => return os.readlinkZ("/proc/self/exe", out_buffer),
.solaris, .illumos => return os.readlinkZ("/proc/self/path/a.out", out_buffer),
.freebsd, .dragonfly => {
var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
var out_len: usize = out_buffer.len;
try os.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
return mem.sliceTo(out_buffer, 0);
},
.netbsd => {
var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC_ARGS, -1, os.KERN.PROC_PATHNAME };
var out_len: usize = out_buffer.len;
try os.sysctl(&mib, out_buffer.ptr, &out_len, null, 0);
return mem.sliceTo(out_buffer, 0);
},
.openbsd, .haiku => {
if (os.argv.len == 0)
return error.FileNotFound;
const argv0 = mem.span(os.argv[0]);
if (mem.indexOf(u8, argv0, "/") != null) {
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
const real_path = try os.realpathZ(os.argv[0], &real_path_buf);
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) {
const PATH = std.os.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.bufPrintZ(&resolved_path_buf, "{s}/{s}", .{
a_path,
os.argv[0],
}) catch continue;
var real_path_buf: [MAX_PATH_BYTES]u8 = undefined;
if (os.realpathZ(resolved_path, &real_path_buf)) |real_path| {
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 = &os.windows.peb().ProcessParameters.ImagePathName;
const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
const pathname_w = try os.windows.wToPrefixedFileW(null, image_path_name);
return std.fs.cwd().realpathW(pathname_w.span(), out_buffer);
},
else => @compileError("std.fs.selfExePath not supported for this target"),
}
}
pub const selfExePathW = @compileError("deprecated; use selfExePath instead");
pub fn selfExeDirPathAlloc(allocator: Allocator) ![]u8 {
var buf: [MAX_PATH_BYTES]u8 = undefined;
return allocator.dupe(u8, try selfExeDirPath(&buf));
}
pub fn selfExeDirPath(out_buffer: []u8) SelfExePathError![]const u8 {
const self_exe_path = try selfExePath(out_buffer);
return path.dirname(self_exe_path).?;
}
pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
var buf: [MAX_PATH_BYTES]u8 = undefined;
return allocator.dupe(u8, try os.realpath(pathname, &buf));
}
test {
if (builtin.os.tag != .wasi) {
_ = &makeDirAbsolute;
_ = &makeDirAbsoluteZ;
_ = ©FileAbsolute;
_ = &updateFileAbsolute;
}
_ = &AtomicFile;
_ = &Dir;
_ = &File;
_ = &path;
_ = @import("fs/test.zig");
_ = @import("fs/get_app_data_dir.zig");
_ = @import("fs/watch.zig");
}