mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-26 13:01:34 +03:00
std: all Dir functions moved to std.Io
This commit is contained in:
@@ -436,8 +436,6 @@ set(ZIG_STAGE2_SOURCES
|
||||
lib/std/fmt.zig
|
||||
lib/std/fmt/parse_float.zig
|
||||
lib/std/fs.zig
|
||||
lib/std/fs/AtomicFile.zig
|
||||
lib/std/fs/Dir.zig
|
||||
lib/std/fs/File.zig
|
||||
lib/std/fs/get_app_data_dir.zig
|
||||
lib/std/fs/path.zig
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
const std = @import("std");
|
||||
const builtin = std.builtin;
|
||||
const tests = @import("test/tests.zig");
|
||||
const BufMap = std.BufMap;
|
||||
const mem = std.mem;
|
||||
const io = std.io;
|
||||
const fs = std.fs;
|
||||
const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;
|
||||
const assert = std.debug.assert;
|
||||
const Io = std.Io;
|
||||
|
||||
const tests = @import("test/tests.zig");
|
||||
const DevEnv = @import("src/dev.zig").Env;
|
||||
const ValueInterpretMode = enum { direct, by_name };
|
||||
|
||||
const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 };
|
||||
const stack_size = 46 * 1024 * 1024;
|
||||
|
||||
const ValueInterpretMode = enum { direct, by_name };
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;
|
||||
const target = b.standardTargetOptions(.{
|
||||
@@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
if (enable_llvm) {
|
||||
const cmake_cfg = if (static_llvm) null else blk: {
|
||||
const io = b.graph.io;
|
||||
const cwd: Io.Dir = .cwd();
|
||||
if (findConfigH(b, config_h_path_option)) |config_h_path| {
|
||||
const file_contents = fs.cwd().readFileAlloc(config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
|
||||
const file_contents = cwd.readFileAlloc(io, config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
|
||||
break :blk parseConfigH(b, file_contents);
|
||||
} else {
|
||||
std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{});
|
||||
@@ -1153,10 +1157,13 @@ const CMakeConfig = struct {
|
||||
const max_config_h_bytes = 1 * 1024 * 1024;
|
||||
|
||||
fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
|
||||
const io = b.graph.io;
|
||||
const cwd: Io.Dir = .cwd();
|
||||
|
||||
if (config_h_path_option) |path| {
|
||||
var config_h_or_err = fs.cwd().openFile(path, .{});
|
||||
var config_h_or_err = cwd.openFile(io, path, .{});
|
||||
if (config_h_or_err) |*file| {
|
||||
file.close();
|
||||
file.close(io);
|
||||
return path;
|
||||
} else |_| {
|
||||
std.log.err("Could not open provided config.h: \"{s}\"", .{path});
|
||||
@@ -1166,13 +1173,13 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
|
||||
|
||||
var check_dir = fs.path.dirname(b.graph.zig_exe).?;
|
||||
while (true) {
|
||||
var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable;
|
||||
defer dir.close();
|
||||
var dir = cwd.openDir(io, check_dir, .{}) catch unreachable;
|
||||
defer dir.close(io);
|
||||
|
||||
// Check if config.h is present in dir
|
||||
var config_h_or_err = dir.openFile("config.h", .{});
|
||||
var config_h_or_err = dir.openFile(io, "config.h", .{});
|
||||
if (config_h_or_err) |*file| {
|
||||
file.close();
|
||||
file.close(io);
|
||||
return fs.path.join(
|
||||
b.allocator,
|
||||
&[_][]const u8{ check_dir, "config.h" },
|
||||
@@ -1183,9 +1190,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
|
||||
}
|
||||
|
||||
// Check if we reached the source root by looking for .git, and bail if so
|
||||
var git_dir_or_err = dir.openDir(".git", .{});
|
||||
var git_dir_or_err = dir.openDir(io, ".git", .{});
|
||||
if (git_dir_or_err) |*git_dir| {
|
||||
git_dir.close();
|
||||
git_dir.close(io);
|
||||
return null;
|
||||
} else |_| {}
|
||||
|
||||
@@ -1581,6 +1588,8 @@ const llvm_libs_xtensa = [_][]const u8{
|
||||
};
|
||||
|
||||
fn generateLangRef(b: *std.Build) std.Build.LazyPath {
|
||||
const io = b.graph.io;
|
||||
|
||||
const doctest_exe = b.addExecutable(.{
|
||||
.name = "doctest",
|
||||
.root_module = b.createModule(.{
|
||||
@@ -1590,7 +1599,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
|
||||
}),
|
||||
});
|
||||
|
||||
var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {
|
||||
var dir = b.build_root.handle.openDir(io, "doc/langref", .{ .iterate = true }) catch |err| {
|
||||
std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
|
||||
b.build_root, @errorName(err),
|
||||
});
|
||||
|
||||
@@ -53,24 +53,26 @@ pub fn main() !void {
|
||||
const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
|
||||
const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
|
||||
|
||||
const cwd: Io.Dir = .cwd();
|
||||
|
||||
const zig_lib_directory: std.Build.Cache.Directory = .{
|
||||
.path = zig_lib_dir,
|
||||
.handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
|
||||
.handle = try cwd.openDir(io, zig_lib_dir, .{}),
|
||||
};
|
||||
|
||||
const build_root_directory: std.Build.Cache.Directory = .{
|
||||
.path = build_root,
|
||||
.handle = try std.fs.cwd().openDir(build_root, .{}),
|
||||
.handle = try cwd.openDir(io, build_root, .{}),
|
||||
};
|
||||
|
||||
const local_cache_directory: std.Build.Cache.Directory = .{
|
||||
.path = cache_root,
|
||||
.handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
|
||||
.handle = try cwd.makeOpenPath(io, cache_root, .{}),
|
||||
};
|
||||
|
||||
const global_cache_directory: std.Build.Cache.Directory = .{
|
||||
.path = global_cache_root,
|
||||
.handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
|
||||
.handle = try cwd.makeOpenPath(io, global_cache_root, .{}),
|
||||
};
|
||||
|
||||
var graph: std.Build.Graph = .{
|
||||
@@ -79,7 +81,7 @@ pub fn main() !void {
|
||||
.cache = .{
|
||||
.io = io,
|
||||
.gpa = arena,
|
||||
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
|
||||
.manifest_dir = try local_cache_directory.handle.makeOpenPath(io, "h", .{}),
|
||||
},
|
||||
.zig_exe = zig_exe,
|
||||
.env_map = try process.getEnvMap(arena),
|
||||
@@ -92,7 +94,7 @@ pub fn main() !void {
|
||||
.time_report = false,
|
||||
};
|
||||
|
||||
graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
|
||||
graph.cache.addPrefix(.{ .path = null, .handle = cwd });
|
||||
graph.cache.addPrefix(build_root_directory);
|
||||
graph.cache.addPrefix(local_cache_directory);
|
||||
graph.cache.addPrefix(global_cache_directory);
|
||||
|
||||
+3
-4
@@ -1700,9 +1700,8 @@ pub fn addCheckFile(
|
||||
}
|
||||
|
||||
pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void {
|
||||
if (b.verbose) {
|
||||
log.info("truncate {s}", .{dest_path});
|
||||
}
|
||||
const io = b.graph.io;
|
||||
if (b.verbose) log.info("truncate {s}", .{dest_path});
|
||||
const cwd = fs.cwd();
|
||||
var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => blk: {
|
||||
@@ -1713,7 +1712,7 @@ pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Di
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
src_file.close();
|
||||
src_file.close(io);
|
||||
}
|
||||
|
||||
/// References a file or directory relative to the source root.
|
||||
|
||||
+43
-40
@@ -8,7 +8,6 @@ const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const crypto = std.crypto;
|
||||
const fs = std.fs;
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const mem = std.mem;
|
||||
@@ -18,7 +17,7 @@ const log = std.log.scoped(.cache);
|
||||
|
||||
gpa: Allocator,
|
||||
io: Io,
|
||||
manifest_dir: fs.Dir,
|
||||
manifest_dir: Io.Dir,
|
||||
hash: HashHelper = .{},
|
||||
/// This value is accessed from multiple threads, protected by mutex.
|
||||
recent_problematic_timestamp: Io.Timestamp = .zero,
|
||||
@@ -71,7 +70,7 @@ const PrefixedPath = struct {
|
||||
|
||||
fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
|
||||
const gpa = cache.gpa;
|
||||
const resolved_path = try fs.path.resolve(gpa, &.{file_path});
|
||||
const resolved_path = try std.fs.path.resolve(gpa, &.{file_path});
|
||||
errdefer gpa.free(resolved_path);
|
||||
return findPrefixResolved(cache, resolved_path);
|
||||
}
|
||||
@@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
|
||||
}
|
||||
|
||||
fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
|
||||
const relative = try fs.path.relative(allocator, prefix, path);
|
||||
const relative = try std.fs.path.relative(allocator, prefix, path);
|
||||
errdefer allocator.free(relative);
|
||||
var component_iterator = fs.path.NativeComponentIterator.init(relative);
|
||||
var component_iterator = std.fs.path.NativeComponentIterator.init(relative);
|
||||
if (component_iterator.root() != null) {
|
||||
return error.NotASubPath;
|
||||
}
|
||||
@@ -145,17 +144,17 @@ pub const File = struct {
|
||||
max_file_size: ?usize,
|
||||
/// Populated if the user calls `addOpenedFile`.
|
||||
/// The handle is not owned here.
|
||||
handle: ?fs.File,
|
||||
handle: ?Io.File,
|
||||
stat: Stat,
|
||||
bin_digest: BinDigest,
|
||||
contents: ?[]const u8,
|
||||
|
||||
pub const Stat = struct {
|
||||
inode: fs.File.INode,
|
||||
inode: Io.File.INode,
|
||||
size: u64,
|
||||
mtime: Io.Timestamp,
|
||||
|
||||
pub fn fromFs(fs_stat: fs.File.Stat) Stat {
|
||||
pub fn fromFs(fs_stat: Io.File.Stat) Stat {
|
||||
return .{
|
||||
.inode = fs_stat.inode,
|
||||
.size = fs_stat.size,
|
||||
@@ -178,7 +177,7 @@ pub const File = struct {
|
||||
file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;
|
||||
}
|
||||
|
||||
pub fn updateHandle(file: *File, new_handle: ?fs.File) void {
|
||||
pub fn updateHandle(file: *File, new_handle: ?Io.File) void {
|
||||
const handle = new_handle orelse return;
|
||||
file.handle = handle;
|
||||
}
|
||||
@@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest {
|
||||
}
|
||||
|
||||
pub const Lock = struct {
|
||||
manifest_file: fs.File,
|
||||
manifest_file: Io.File,
|
||||
|
||||
pub fn release(lock: *Lock) void {
|
||||
pub fn release(lock: *Lock, io: Io) void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
// Windows does not guarantee that locks are immediately unlocked when
|
||||
// the file handle is closed. See LockFileEx documentation.
|
||||
lock.manifest_file.unlock();
|
||||
}
|
||||
|
||||
lock.manifest_file.close();
|
||||
lock.manifest_file.close(io);
|
||||
lock.* = undefined;
|
||||
}
|
||||
};
|
||||
@@ -311,7 +310,7 @@ pub const Manifest = struct {
|
||||
cache: *Cache,
|
||||
/// Current state for incremental hashing.
|
||||
hash: HashHelper,
|
||||
manifest_file: ?fs.File,
|
||||
manifest_file: ?Io.File,
|
||||
manifest_dirty: bool,
|
||||
/// Set this flag to true before calling hit() in order to indicate that
|
||||
/// upon a cache hit, the code using the cache will not modify the files
|
||||
@@ -332,9 +331,9 @@ pub const Manifest = struct {
|
||||
|
||||
pub const Diagnostic = union(enum) {
|
||||
none,
|
||||
manifest_create: fs.File.OpenError,
|
||||
manifest_read: fs.File.ReadError,
|
||||
manifest_lock: fs.File.LockError,
|
||||
manifest_create: Io.File.OpenError,
|
||||
manifest_read: Io.File.Reader.Error,
|
||||
manifest_lock: Io.File.LockError,
|
||||
file_open: FileOp,
|
||||
file_stat: FileOp,
|
||||
file_read: FileOp,
|
||||
@@ -393,10 +392,10 @@ pub const Manifest = struct {
|
||||
}
|
||||
|
||||
/// Same as `addFilePath` except the file has already been opened.
|
||||
pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?fs.File, max_file_size: ?usize) !usize {
|
||||
pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize {
|
||||
const gpa = m.cache.gpa;
|
||||
try m.files.ensureUnusedCapacity(gpa, 1);
|
||||
const resolved_path = try fs.path.resolve(gpa, &.{
|
||||
const resolved_path = try std.fs.path.resolve(gpa, &.{
|
||||
path.root_dir.path orelse ".",
|
||||
path.subPathOrDot(),
|
||||
});
|
||||
@@ -417,7 +416,7 @@ pub const Manifest = struct {
|
||||
return addFileInner(self, prefixed_path, null, max_file_size);
|
||||
}
|
||||
|
||||
fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?fs.File, max_file_size: ?usize) usize {
|
||||
fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize {
|
||||
const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
|
||||
if (gop.found_existing) {
|
||||
self.cache.gpa.free(prefixed_path.sub_path);
|
||||
@@ -460,7 +459,7 @@ pub const Manifest = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
|
||||
pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
|
||||
assert(self.manifest_file == null);
|
||||
return self.addDepFileMaybePost(dir, dep_file_sub_path);
|
||||
}
|
||||
@@ -702,7 +701,7 @@ pub const Manifest = struct {
|
||||
const file_path = iter.rest();
|
||||
|
||||
const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
|
||||
const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
|
||||
const stat_inode = fmt.parseInt(Io.File.INode, inode, 10) catch return error.InvalidFormat;
|
||||
const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
|
||||
const file_bin_digest = b: {
|
||||
if (digest_str.len != hex_digest_len) return error.InvalidFormat;
|
||||
@@ -772,7 +771,7 @@ pub const Manifest = struct {
|
||||
return error.CacheCheckFailed;
|
||||
},
|
||||
};
|
||||
defer this_file.close();
|
||||
defer this_file.close(io);
|
||||
|
||||
const actual_stat = this_file.stat() catch |err| {
|
||||
self.diagnostic = .{ .file_stat = .{
|
||||
@@ -879,7 +878,7 @@ pub const Manifest = struct {
|
||||
error.Canceled => return error.Canceled,
|
||||
else => return true,
|
||||
};
|
||||
defer file.close();
|
||||
defer file.close(io);
|
||||
|
||||
// Save locally and also save globally (we still hold the global lock).
|
||||
const stat = file.stat() catch |err| switch (err) {
|
||||
@@ -894,18 +893,20 @@ pub const Manifest = struct {
|
||||
}
|
||||
|
||||
fn populateFileHash(self: *Manifest, ch_file: *File) !void {
|
||||
const io = self.cache.io;
|
||||
|
||||
if (ch_file.handle) |handle| {
|
||||
return populateFileHashHandle(self, ch_file, handle);
|
||||
} else {
|
||||
const pp = ch_file.prefixed_path;
|
||||
const dir = self.cache.prefixes()[pp.prefix].handle;
|
||||
const handle = try dir.openFile(pp.sub_path, .{});
|
||||
defer handle.close();
|
||||
defer handle.close(io);
|
||||
return populateFileHashHandle(self, ch_file, handle);
|
||||
}
|
||||
}
|
||||
|
||||
fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void {
|
||||
fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: Io.File) !void {
|
||||
const actual_stat = try handle.stat();
|
||||
ch_file.stat = .{
|
||||
.size = actual_stat.size,
|
||||
@@ -1064,12 +1065,12 @@ pub const Manifest = struct {
|
||||
self.hash.hasher.update(&new_file.bin_digest);
|
||||
}
|
||||
|
||||
pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
|
||||
pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
|
||||
assert(self.manifest_file != null);
|
||||
return self.addDepFileMaybePost(dir, dep_file_sub_path);
|
||||
}
|
||||
|
||||
fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
|
||||
fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
|
||||
const gpa = self.cache.gpa;
|
||||
const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max));
|
||||
defer gpa.free(dep_file_contents);
|
||||
@@ -1148,7 +1149,7 @@ pub const Manifest = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void {
|
||||
fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void {
|
||||
try fw.interface.writeAll(manifest_header ++ "\n");
|
||||
for (self.files.keys()) |file| {
|
||||
try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
|
||||
@@ -1214,13 +1215,15 @@ pub const Manifest = struct {
|
||||
/// `Manifest.hit` must be called first.
|
||||
/// Don't forget to call `writeManifest` before this!
|
||||
pub fn deinit(self: *Manifest) void {
|
||||
const io = self.cache.io;
|
||||
|
||||
if (self.manifest_file) |file| {
|
||||
if (builtin.os.tag == .windows) {
|
||||
// See Lock.release for why this is required on Windows
|
||||
file.unlock();
|
||||
}
|
||||
|
||||
file.close();
|
||||
file.close(io);
|
||||
}
|
||||
for (self.files.keys()) |*file| {
|
||||
file.deinit(self.cache.gpa);
|
||||
@@ -1281,7 +1284,7 @@ pub const Manifest = struct {
|
||||
/// On operating systems that support symlinks, does a readlink. On other operating systems,
|
||||
/// uses the file contents. Windows supports symlinks but only with elevated privileges, so
|
||||
/// it is treated as not supporting symlinks.
|
||||
pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
||||
pub fn readSmallFile(dir: Io.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
return dir.readFile(sub_path, buffer);
|
||||
} else {
|
||||
@@ -1293,7 +1296,7 @@ pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
||||
/// uses the file contents. Windows supports symlinks but only with elevated privileges, so
|
||||
/// it is treated as not supporting symlinks.
|
||||
/// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer.
|
||||
pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void {
|
||||
pub fn writeSmallFile(dir: Io.Dir, sub_path: []const u8, data: []const u8) !void {
|
||||
assert(data.len <= 255);
|
||||
if (builtin.os.tag == .windows) {
|
||||
return dir.writeFile(.{ .sub_path = sub_path, .data = data });
|
||||
@@ -1302,7 +1305,7 @@ pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void
|
||||
}
|
||||
}
|
||||
|
||||
fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void {
|
||||
fn hashFile(file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.PReadError!void {
|
||||
var buf: [1024]u8 = undefined;
|
||||
var hasher = hasher_init;
|
||||
var off: u64 = 0;
|
||||
@@ -1316,7 +1319,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
|
||||
}
|
||||
|
||||
// Create/Write a file, close it, then grab its stat.mtime timestamp.
|
||||
fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
|
||||
fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
|
||||
const test_out_file = "test-filetimestamp.tmp";
|
||||
|
||||
var file = try dir.createFile(test_out_file, .{
|
||||
@@ -1324,7 +1327,7 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
|
||||
.truncate = true,
|
||||
});
|
||||
defer {
|
||||
file.close();
|
||||
file.close(io);
|
||||
dir.deleteFile(test_out_file) catch {};
|
||||
}
|
||||
|
||||
@@ -1343,8 +1346,8 @@ test "cache file and then recall it" {
|
||||
try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = "Hello, world!\n" });
|
||||
|
||||
// Wait for file timestamps to tick
|
||||
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
|
||||
while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
|
||||
const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
|
||||
while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
|
||||
try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
|
||||
}
|
||||
|
||||
@@ -1358,7 +1361,7 @@ test "cache file and then recall it" {
|
||||
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close();
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
||||
{
|
||||
var ch = cache.obtain();
|
||||
@@ -1424,7 +1427,7 @@ test "check that changing a file makes cache fail" {
|
||||
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close();
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
||||
{
|
||||
var ch = cache.obtain();
|
||||
@@ -1484,7 +1487,7 @@ test "no file inputs" {
|
||||
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close();
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
||||
{
|
||||
var man = cache.obtain();
|
||||
@@ -1543,7 +1546,7 @@ test "Manifest with files added after initial hash work" {
|
||||
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close();
|
||||
defer cache.manifest_dir.close(io);
|
||||
|
||||
{
|
||||
var ch = cache.obtain();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const Directory = @This();
|
||||
|
||||
const std = @import("../../std.zig");
|
||||
const assert = std.debug.assert;
|
||||
const Io = std.Io;
|
||||
const fs = std.fs;
|
||||
const assert = std.debug.assert;
|
||||
const fmt = std.fmt;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator;
|
||||
/// directly, but it is needed when passing the directory to a child process.
|
||||
/// `null` means cwd.
|
||||
path: ?[]const u8,
|
||||
handle: fs.Dir,
|
||||
handle: Io.Dir,
|
||||
|
||||
pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
|
||||
return .{
|
||||
@@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
|
||||
pub fn cwd() Directory {
|
||||
return .{
|
||||
.path = null,
|
||||
.handle = fs.cwd(),
|
||||
.handle = .cwd(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void
|
||||
}
|
||||
|
||||
pub fn eql(self: Directory, other: Directory) bool {
|
||||
return self.handle.fd == other.handle.fd;
|
||||
return self.handle.handle == other.handle.handle;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ const Path = @This();
|
||||
|
||||
const std = @import("../../std.zig");
|
||||
const Io = std.Io;
|
||||
const assert = std.debug.assert;
|
||||
const fs = std.fs;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Cache = std.Build.Cache;
|
||||
|
||||
@@ -62,8 +62,8 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro
|
||||
pub fn openFile(
|
||||
p: Path,
|
||||
sub_path: []const u8,
|
||||
flags: fs.File.OpenFlags,
|
||||
) !fs.File {
|
||||
flags: Io.File.OpenFlags,
|
||||
) !Io.File {
|
||||
var buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
||||
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
||||
@@ -76,8 +76,8 @@ pub fn openFile(
|
||||
pub fn openDir(
|
||||
p: Path,
|
||||
sub_path: []const u8,
|
||||
args: fs.Dir.OpenOptions,
|
||||
) fs.Dir.OpenError!fs.Dir {
|
||||
args: Io.Dir.OpenOptions,
|
||||
) Io.Dir.OpenError!Io.Dir {
|
||||
var buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
||||
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
||||
@@ -87,7 +87,7 @@ pub fn openDir(
|
||||
return p.root_dir.handle.openDir(joined_path, args);
|
||||
}
|
||||
|
||||
pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {
|
||||
pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir {
|
||||
var buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
||||
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
||||
@@ -97,7 +97,7 @@ pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs
|
||||
return p.root_dir.handle.makeOpenPath(joined_path, opts);
|
||||
}
|
||||
|
||||
pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat {
|
||||
pub fn statFile(p: Path, sub_path: []const u8) !Io.Dir.Stat {
|
||||
var buf: [fs.max_path_bytes]u8 = undefined;
|
||||
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
||||
break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
|
||||
@@ -110,7 +110,7 @@ pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat {
|
||||
pub fn atomicFile(
|
||||
p: Path,
|
||||
sub_path: []const u8,
|
||||
options: fs.Dir.AtomicFileOptions,
|
||||
options: Io.Dir.AtomicFileOptions,
|
||||
buf: *[fs.max_path_bytes]u8,
|
||||
) !fs.AtomicFile {
|
||||
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
|
||||
@@ -180,7 +180,7 @@ pub fn formatEscapeChar(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
|
||||
}
|
||||
|
||||
pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void {
|
||||
if (std.fs.path.isAbsolute(self.sub_path)) {
|
||||
if (fs.path.isAbsolute(self.sub_path)) {
|
||||
try writer.writeAll(self.sub_path);
|
||||
return;
|
||||
}
|
||||
@@ -225,9 +225,9 @@ pub const TableAdapter = struct {
|
||||
|
||||
pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
|
||||
_ = self;
|
||||
const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) {
|
||||
.pointer => @intFromPtr(a.root_dir.handle.fd),
|
||||
.int => @as(u32, @bitCast(a.root_dir.handle.fd)),
|
||||
const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) {
|
||||
.pointer => @intFromPtr(a.root_dir.handle.handle),
|
||||
.int => @as(u32, @bitCast(a.root_dir.handle.handle)),
|
||||
else => @compileError("unimplemented hash function"),
|
||||
};
|
||||
return @truncate(Hash.hash(seed, a.sub_path));
|
||||
|
||||
@@ -510,20 +510,16 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u
|
||||
const io = b.graph.io;
|
||||
const src_path = src_lazy_path.getPath3(b, s);
|
||||
try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
|
||||
return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| {
|
||||
return s.fail("unable to update file from '{f}' to '{s}': {t}", .{
|
||||
src_path, dest_path, err,
|
||||
});
|
||||
};
|
||||
return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|
|
||||
return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });
|
||||
}
|
||||
|
||||
/// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output.
|
||||
pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus {
|
||||
const b = s.owner;
|
||||
try handleVerbose(b, null, &.{ "install", "-d", dest_path });
|
||||
return std.fs.cwd().makePathStatus(dest_path) catch |err| {
|
||||
return std.fs.cwd().makePathStatus(dest_path) catch |err|
|
||||
return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });
|
||||
};
|
||||
}
|
||||
|
||||
fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
const Compile = @This();
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const mem = std.mem;
|
||||
const fs = std.fs;
|
||||
const assert = std.debug.assert;
|
||||
const panic = std.debug.panic;
|
||||
const StringHashMap = std.StringHashMap;
|
||||
const Sha256 = std.crypto.hash.sha2.Sha256;
|
||||
const Allocator = mem.Allocator;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Step = std.Build.Step;
|
||||
const LazyPath = std.Build.LazyPath;
|
||||
const PkgConfigPkg = std.Build.PkgConfigPkg;
|
||||
@@ -15,7 +18,6 @@ const RunError = std.Build.RunError;
|
||||
const Module = std.Build.Module;
|
||||
const InstallDir = std.Build.InstallDir;
|
||||
const GeneratedFile = std.Build.GeneratedFile;
|
||||
const Compile = @This();
|
||||
const Path = std.Build.Cache.Path;
|
||||
|
||||
pub const base_id: Step.Id = .compile;
|
||||
@@ -1561,19 +1563,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
|
||||
}
|
||||
|
||||
// -I and -L arguments that appear after the last --mod argument apply to all modules.
|
||||
const cwd: Io.Dir = .cwd();
|
||||
const io = b.graph.io;
|
||||
|
||||
for (b.search_prefixes.items) |search_prefix| {
|
||||
var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| {
|
||||
var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| {
|
||||
return step.fail("unable to open prefix directory '{s}': {s}", .{
|
||||
search_prefix, @errorName(err),
|
||||
});
|
||||
};
|
||||
defer prefix_dir.close();
|
||||
defer prefix_dir.close(io);
|
||||
|
||||
// Avoid passing -L and -I flags for nonexistent directories.
|
||||
// This prevents a warning, that should probably be upgraded to an error in Zig's
|
||||
// CLI parsing code, when the linker sees an -L directory that does not exist.
|
||||
|
||||
if (prefix_dir.access("lib", .{})) |_| {
|
||||
if (prefix_dir.access(io, "lib", .{})) |_| {
|
||||
try zig_args.appendSlice(&.{
|
||||
"-L", b.pathJoin(&.{ search_prefix, "lib" }),
|
||||
});
|
||||
@@ -1584,7 +1589,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
|
||||
}),
|
||||
}
|
||||
|
||||
if (prefix_dir.access("include", .{})) |_| {
|
||||
if (prefix_dir.access(io, "include", .{})) |_| {
|
||||
try zig_args.appendSlice(&.{
|
||||
"-I", b.pathJoin(&.{ search_prefix, "include" }),
|
||||
});
|
||||
@@ -1660,7 +1665,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
|
||||
args_length += arg.len + 1; // +1 to account for null terminator
|
||||
}
|
||||
if (args_length >= 30 * 1024) {
|
||||
try b.cache_root.handle.makePath("args");
|
||||
try b.cache_root.handle.makePath(io, "args");
|
||||
|
||||
const args_to_escape = zig_args.items[2..];
|
||||
var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len);
|
||||
@@ -1693,18 +1698,18 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
|
||||
_ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash});
|
||||
|
||||
const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
|
||||
if (b.cache_root.handle.access(args_file, .{})) |_| {
|
||||
if (b.cache_root.handle.access(io, args_file, .{})) |_| {
|
||||
// The args file is already present from a previous run.
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => {
|
||||
try b.cache_root.handle.makePath("tmp");
|
||||
try b.cache_root.handle.makePath(io, "tmp");
|
||||
const rand_int = std.crypto.random.int(u64);
|
||||
const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
|
||||
try b.cache_root.handle.writeFile(.{ .sub_path = tmp_path, .data = args });
|
||||
defer b.cache_root.handle.deleteFile(tmp_path) catch {
|
||||
try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
|
||||
defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
|
||||
// It's fine if the temporary file can't be cleaned up.
|
||||
};
|
||||
b.cache_root.handle.rename(tmp_path, args_file) catch |rename_err| switch (rename_err) {
|
||||
b.cache_root.handle.rename(io, tmp_path, args_file) catch |rename_err| switch (rename_err) {
|
||||
error.PathAlreadyExists => {
|
||||
// The args file was created by another concurrent build process.
|
||||
},
|
||||
@@ -1816,18 +1821,20 @@ pub fn doAtomicSymLinks(
|
||||
filename_name_only: []const u8,
|
||||
) !void {
|
||||
const b = step.owner;
|
||||
const io = b.graph.io;
|
||||
const out_dir = fs.path.dirname(output_path) orelse ".";
|
||||
const out_basename = fs.path.basename(output_path);
|
||||
// sym link for libfoo.so.1 to libfoo.so.1.2.3
|
||||
const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });
|
||||
fs.cwd().atomicSymLink(out_basename, major_only_path, .{}) catch |err| {
|
||||
const cwd: Io.Dir = .cwd();
|
||||
cwd.atomicSymLink(io, out_basename, major_only_path, .{}) catch |err| {
|
||||
return step.fail("unable to symlink {s} -> {s}: {s}", .{
|
||||
major_only_path, out_basename, @errorName(err),
|
||||
});
|
||||
};
|
||||
// sym link for libfoo.so to libfoo.so.1
|
||||
const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });
|
||||
fs.cwd().atomicSymLink(filename_major_only, name_only_path, .{}) catch |err| {
|
||||
cwd.atomicSymLink(io, filename_major_only, name_only_path, .{}) catch |err| {
|
||||
return step.fail("Unable to symlink {s} -> {s}: {s}", .{
|
||||
name_only_path, filename_major_only, @errorName(err),
|
||||
});
|
||||
|
||||
@@ -58,16 +58,15 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {
|
||||
fn make(step: *Step, options: Step.MakeOptions) !void {
|
||||
_ = options;
|
||||
const b = step.owner;
|
||||
const io = b.graph.io;
|
||||
const install_dir: *InstallDir = @fieldParentPtr("step", step);
|
||||
step.clearWatchInputs();
|
||||
const arena = b.allocator;
|
||||
const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
|
||||
const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
|
||||
const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);
|
||||
var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
|
||||
return step.fail("unable to open source directory '{f}': {s}", .{
|
||||
src_dir_path, @errorName(err),
|
||||
});
|
||||
var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
|
||||
return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err });
|
||||
};
|
||||
defer src_dir.close();
|
||||
var it = try src_dir.walk(arena);
|
||||
|
||||
@@ -441,6 +441,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
|
||||
_ = make_options;
|
||||
|
||||
const b = step.owner;
|
||||
const io = b.graph.io;
|
||||
const options: *Options = @fieldParentPtr("step", step);
|
||||
|
||||
for (options.args.items) |item| {
|
||||
@@ -468,18 +469,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
|
||||
|
||||
// Optimize for the hot path. Stat the file, and if it already exists,
|
||||
// cache hit.
|
||||
if (b.cache_root.handle.access(sub_path, .{})) |_| {
|
||||
if (b.cache_root.handle.access(io, sub_path, .{})) |_| {
|
||||
// This is the hot path, success.
|
||||
step.result_cached = true;
|
||||
return;
|
||||
} else |outer_err| switch (outer_err) {
|
||||
error.FileNotFound => {
|
||||
const sub_dirname = fs.path.dirname(sub_path).?;
|
||||
b.cache_root.handle.makePath(sub_dirname) catch |e| {
|
||||
return step.fail("unable to make path '{f}{s}': {s}", .{
|
||||
b.cache_root, sub_dirname, @errorName(e),
|
||||
});
|
||||
};
|
||||
b.cache_root.handle.makePath(io, sub_dirname) catch |e|
|
||||
return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
|
||||
|
||||
const rand_int = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
|
||||
@@ -487,40 +485,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
|
||||
basename;
|
||||
const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
|
||||
|
||||
b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| {
|
||||
return step.fail("unable to make temporary directory '{f}{s}': {s}", .{
|
||||
b.cache_root, tmp_sub_path_dirname, @errorName(err),
|
||||
b.cache_root.handle.makePath(io, tmp_sub_path_dirname) catch |err| {
|
||||
return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
|
||||
b.cache_root, tmp_sub_path_dirname, err,
|
||||
});
|
||||
};
|
||||
|
||||
b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
|
||||
return step.fail("unable to write options to '{f}{s}': {s}", .{
|
||||
b.cache_root, tmp_sub_path, @errorName(err),
|
||||
b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
|
||||
return step.fail("unable to write options to '{f}{s}': {t}", .{
|
||||
b.cache_root, tmp_sub_path, err,
|
||||
});
|
||||
};
|
||||
|
||||
b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) {
|
||||
b.cache_root.handle.rename(io, tmp_sub_path, sub_path) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => {
|
||||
// Other process beat us to it. Clean up the temp file.
|
||||
b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| {
|
||||
try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{
|
||||
b.cache_root, tmp_sub_path, @errorName(e),
|
||||
b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
|
||||
try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
|
||||
b.cache_root, tmp_sub_path, e,
|
||||
});
|
||||
};
|
||||
step.result_cached = true;
|
||||
return;
|
||||
},
|
||||
else => {
|
||||
return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{
|
||||
b.cache_root, tmp_sub_path,
|
||||
b.cache_root, sub_path,
|
||||
@errorName(err),
|
||||
return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
|
||||
b.cache_root, tmp_sub_path,
|
||||
b.cache_root, sub_path,
|
||||
err,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{
|
||||
b.cache_root, sub_path, @errorName(e),
|
||||
else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
|
||||
b.cache_root, sub_path, e,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
+19
-8
@@ -662,16 +662,27 @@ pub const VTable = struct {
|
||||
futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
|
||||
futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
|
||||
|
||||
dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
|
||||
dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,
|
||||
dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
|
||||
dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void,
|
||||
dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,
|
||||
dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
|
||||
dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
|
||||
dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
|
||||
dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
|
||||
dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
|
||||
dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
|
||||
dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
|
||||
dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
|
||||
dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
|
||||
dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
|
||||
dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
|
||||
dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
|
||||
dirClose: *const fn (?*anyopaque, Dir) void,
|
||||
dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
|
||||
dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize,
|
||||
dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
|
||||
dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
|
||||
dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
|
||||
dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void,
|
||||
dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
|
||||
dirSetMode: *const fn (?*anyopaque, Dir, File.Mode) Dir.SetModeError!void,
|
||||
dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
|
||||
dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
|
||||
|
||||
fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
|
||||
fileClose: *const fn (?*anyopaque, File) void,
|
||||
fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
|
||||
|
||||
+1202
-2
@@ -6,12 +6,20 @@ const native_os = builtin.os.tag;
|
||||
const std = @import("../std.zig");
|
||||
const Io = std.Io;
|
||||
const File = Io.File;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
handle: Handle,
|
||||
|
||||
pub const Mode = Io.File.Mode;
|
||||
pub const default_mode: Mode = 0o755;
|
||||
|
||||
pub const Entry = struct {
|
||||
name: []const u8,
|
||||
kind: File.Kind,
|
||||
inode: File.INode,
|
||||
};
|
||||
|
||||
/// Returns a handle to the current working directory.
|
||||
///
|
||||
/// It is not opened with iteration capability. Iterating over the result is
|
||||
@@ -20,6 +28,8 @@ pub const default_mode: Mode = 0o755;
|
||||
/// Closing the returned `Dir` is checked illegal behavior.
|
||||
///
|
||||
/// On POSIX targets, this function is comptime-callable.
|
||||
///
|
||||
/// On WASI, the value this returns is application-configurable.
|
||||
pub fn cwd() Dir {
|
||||
return switch (native_os) {
|
||||
.windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
|
||||
@@ -28,6 +38,270 @@ pub fn cwd() Dir {
|
||||
};
|
||||
}
|
||||
|
||||
pub const Reader = struct {
|
||||
dir: Dir,
|
||||
state: State,
|
||||
/// Stores I/O implementation specific data.
|
||||
buffer: [2048]u8 align(@alignOf(usize)),
|
||||
index: usize,
|
||||
|
||||
pub const State = enum {
|
||||
/// Indicates the next call to `read` should rewind and start over the
|
||||
/// directory listing.
|
||||
reset,
|
||||
reading,
|
||||
finished,
|
||||
};
|
||||
|
||||
pub const Error = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
SystemResources,
|
||||
} || Io.UnexpectedError || Io.Cancelable;
|
||||
|
||||
pub fn init(dir: Dir) Reader {
|
||||
return .{
|
||||
.dir = dir,
|
||||
.state = .reset,
|
||||
.index = 0,
|
||||
.buffer = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read(r: *Reader, io: Io, buffer: []Entry) Error!usize {
|
||||
return io.vtable.dirRead(io.userdata, r, buffer);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Iterator = struct {
|
||||
reader: Reader,
|
||||
buffer: [32]Entry,
|
||||
/// Index of next entry in `buffer`.
|
||||
index: usize,
|
||||
/// Fill position of `buffer`.
|
||||
end: usize,
|
||||
|
||||
pub const Error = Reader.Error;
|
||||
|
||||
pub fn init(dir: Dir, reader_state: Reader.State) Iterator {
|
||||
return .{
|
||||
.reader = .{
|
||||
.dir = dir,
|
||||
.state = reader_state,
|
||||
.index = 0,
|
||||
.buffer = undefined,
|
||||
},
|
||||
.buffer = undefined,
|
||||
.index = 0,
|
||||
.end = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn next(it: *Iterator, io: Io) Error!?Entry {
|
||||
if (it.end - it.index == 0) {
|
||||
if (it.reader.state == .finished) return null;
|
||||
it.end = try it.reader.read(io, &it.buffer);
|
||||
it.index = 0;
|
||||
if (it.end - it.index == 0) {
|
||||
assert(it.reader.state == .finished);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const index = it.index;
|
||||
it.index = index + 1;
|
||||
return it.buffer[index];
|
||||
}
|
||||
};
|
||||
|
||||
pub fn iterate(dir: Dir) Iterator {
|
||||
return .init(dir, .reset);
|
||||
}
|
||||
|
||||
/// Like `iterate`, but will not reset the directory cursor before the first
|
||||
/// iteration. This should only be used in cases where it is known that the
|
||||
/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
|
||||
pub fn iterateAssumeFirstIteration(dir: Dir) Iterator {
|
||||
return .init(dir, .reading);
|
||||
}
|
||||
|
||||
pub const SelectiveWalker = struct {
|
||||
stack: std.ArrayList(Walker.StackItem),
|
||||
name_buffer: std.ArrayList(u8),
|
||||
allocator: Allocator,
|
||||
|
||||
pub const Error = Io.Dir.Iterator.Error || Allocator.Error;
|
||||
|
||||
/// After each call to this function, and on deinit(), the memory returned
|
||||
/// from this function becomes invalid. A copy must be made in order to keep
|
||||
/// a reference to the path.
|
||||
pub fn next(self: *SelectiveWalker) Error!?Walker.Entry {
|
||||
while (self.stack.items.len > 0) {
|
||||
const top = &self.stack.items[self.stack.items.len - 1];
|
||||
var dirname_len = top.dirname_len;
|
||||
if (top.iter.next() catch |err| {
|
||||
// If we get an error, then we want the user to be able to continue
|
||||
// walking if they want, which means that we need to pop the directory
|
||||
// that errored from the stack. Otherwise, all future `next` calls would
|
||||
// likely just fail with the same error.
|
||||
var item = self.stack.pop().?;
|
||||
if (self.stack.items.len != 0) {
|
||||
item.iter.dir.close();
|
||||
}
|
||||
return err;
|
||||
}) |entry| {
|
||||
self.name_buffer.shrinkRetainingCapacity(dirname_len);
|
||||
if (self.name_buffer.items.len != 0) {
|
||||
try self.name_buffer.append(self.allocator, std.fs.path.sep);
|
||||
dirname_len += 1;
|
||||
}
|
||||
try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
|
||||
self.name_buffer.appendSliceAssumeCapacity(entry.name);
|
||||
self.name_buffer.appendAssumeCapacity(0);
|
||||
const walker_entry: Walker.Entry = .{
|
||||
.dir = top.iter.dir,
|
||||
.basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
|
||||
.path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
|
||||
.kind = entry.kind,
|
||||
};
|
||||
return walker_entry;
|
||||
} else {
|
||||
var item = self.stack.pop().?;
|
||||
if (self.stack.items.len != 0) {
|
||||
item.iter.dir.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Traverses into the directory, continuing walking one level down.
|
||||
pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void {
|
||||
if (entry.kind != .directory) {
|
||||
@branchHint(.cold);
|
||||
return;
|
||||
}
|
||||
|
||||
var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| {
|
||||
switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
else => |e| return e,
|
||||
}
|
||||
};
|
||||
errdefer new_dir.close();
|
||||
|
||||
try self.stack.append(self.allocator, .{
|
||||
.iter = new_dir.iterateAssumeFirstIteration(),
|
||||
.dirname_len = self.name_buffer.items.len - 1,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SelectiveWalker) void {
|
||||
self.name_buffer.deinit(self.allocator);
|
||||
self.stack.deinit(self.allocator);
|
||||
}
|
||||
|
||||
/// Leaves the current directory, continuing walking one level up.
|
||||
/// If the current entry is a directory entry, then the "current directory"
|
||||
/// will pertain to that entry if `enter` is called before `leave`.
|
||||
pub fn leave(self: *SelectiveWalker) void {
|
||||
var item = self.stack.pop().?;
|
||||
if (self.stack.items.len != 0) {
|
||||
@branchHint(.likely);
|
||||
item.iter.dir.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Recursively iterates over a directory, but requires the user to
|
||||
/// opt-in to recursing into each directory entry.
|
||||
///
|
||||
/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
|
||||
///
|
||||
/// `Walker.deinit` releases allocated memory and directory handles.
|
||||
///
|
||||
/// The order of returned file system entries is undefined.
|
||||
///
|
||||
/// `dir` will not be closed after walking it.
|
||||
///
|
||||
/// See also `walk`.
|
||||
pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {
|
||||
var stack: std.ArrayList(Walker.StackItem) = .empty;
|
||||
|
||||
try stack.append(allocator, .{
|
||||
.iter = dir.iterate(),
|
||||
.dirname_len = 0,
|
||||
});
|
||||
|
||||
return .{
|
||||
.stack = stack,
|
||||
.name_buffer = .{},
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Walker = struct {
|
||||
inner: SelectiveWalker,
|
||||
|
||||
pub const Entry = struct {
|
||||
/// The containing directory. This can be used to operate directly on `basename`
|
||||
/// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
|
||||
/// The directory remains open until `next` or `deinit` is called.
|
||||
dir: Dir,
|
||||
basename: [:0]const u8,
|
||||
path: [:0]const u8,
|
||||
kind: Dir.Entry.Kind,
|
||||
|
||||
/// Returns the depth of the entry relative to the initial directory.
|
||||
/// Returns 1 for a direct child of the initial directory, 2 for an entry
|
||||
/// within a direct child of the initial directory, etc.
|
||||
pub fn depth(self: Walker.Entry) usize {
|
||||
return std.mem.countScalar(u8, self.path, std.fs.path.sep) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
const StackItem = struct {
|
||||
iter: Dir.Iterator,
|
||||
dirname_len: usize,
|
||||
};
|
||||
|
||||
/// After each call to this function, and on deinit(), the memory returned
|
||||
/// from this function becomes invalid. A copy must be made in order to keep
|
||||
/// a reference to the path.
|
||||
pub fn next(self: *Walker) !?Walker.Entry {
|
||||
const entry = try self.inner.next();
|
||||
if (entry != null and entry.?.kind == .directory) {
|
||||
try self.inner.enter(entry.?);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Walker) void {
|
||||
self.inner.deinit();
|
||||
}
|
||||
|
||||
/// Leaves the current directory, continuing walking one level up.
|
||||
/// If the current entry is a directory entry, then the "current directory"
|
||||
/// is the directory pertaining to the current entry.
|
||||
pub fn leave(self: *Walker) void {
|
||||
self.inner.leave();
|
||||
}
|
||||
};
|
||||
|
||||
/// Recursively iterates over a directory.
|
||||
///
|
||||
/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
|
||||
///
|
||||
/// `Walker.deinit` releases allocated memory and directory handles.
|
||||
///
|
||||
/// The order of returned file system entries is undefined.
|
||||
///
|
||||
/// `dir` will not be closed after walking it.
|
||||
///
|
||||
/// See also `walkSelectively`.
|
||||
pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
|
||||
return .{ .inner = try walkSelectively(dir, allocator) };
|
||||
}
|
||||
|
||||
pub const Handle = std.posix.fd_t;
|
||||
|
||||
pub const PathNameError = error{
|
||||
@@ -145,7 +419,7 @@ pub const WriteFileOptions = struct {
|
||||
flags: File.CreateFlags = .{},
|
||||
};
|
||||
|
||||
pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
|
||||
pub const WriteFileError = File.WriteError || File.OpenError;
|
||||
|
||||
/// Writes content to the file system, using the file creation flags provided.
|
||||
pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
|
||||
@@ -179,7 +453,7 @@ pub fn updateFile(
|
||||
dest_dir: Dir,
|
||||
/// If directories in this path do not exist, they are created.
|
||||
dest_path: []const u8,
|
||||
options: std.fs.Dir.CopyFileOptions,
|
||||
options: CopyFileOptions,
|
||||
) !PrevStatus {
|
||||
var src_file = try source_dir.openFile(io, source_path, .{});
|
||||
defer src_file.close(io);
|
||||
@@ -367,3 +641,929 @@ pub const StatPathOptions = struct {
|
||||
pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
|
||||
return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
|
||||
}
|
||||
|
||||
pub const RealPathError = error{
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
NameTooLong,
|
||||
NotSupported,
|
||||
NotDir,
|
||||
SymLinkLoop,
|
||||
InputOutput,
|
||||
FileTooBig,
|
||||
IsDir,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
NoDevice,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
FileSystem,
|
||||
DeviceBusy,
|
||||
ProcessNotFound,
|
||||
SharingViolation,
|
||||
PipeBusy,
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
PathAlreadyExists,
|
||||
/// 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,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// This function returns the canonicalized absolute pathname of `pathname`
|
||||
/// relative to this `Dir`. If `pathname` is absolute, ignores this `Dir`
|
||||
/// handle and returns the canonicalized absolute pathname of `pathname`
|
||||
/// argument.
|
||||
///
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// 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.
|
||||
///
|
||||
/// This function is not universally supported by all platforms. Currently
|
||||
/// supported hosts are: Linux, macOS, and Windows.
|
||||
///
|
||||
/// See also:
|
||||
/// * `realpathAlloc`.
|
||||
pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize {
|
||||
return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer);
|
||||
}
|
||||
|
||||
pub const RealPathAllocError = RealPathError || Allocator.Error;
|
||||
|
||||
/// Same as `Dir.realpath` except caller must free the returned memory.
|
||||
/// See also `Dir.realpath`.
|
||||
pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]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: [std.fs.max_path_bytes]u8 = undefined;
|
||||
return allocator.dupe(u8, try self.realpath(pathname, &buf));
|
||||
}
|
||||
|
||||
pub const DeleteFileError = error{
|
||||
FileNotFound,
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to unlink a resource by path relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
FileSystem,
|
||||
IsDir,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
NotDir,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
/// Windows: file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
|
||||
///
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
///
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void {
|
||||
return io.vtable.dirDeleteFile(io.userdata, dir, sub_path);
|
||||
}
|
||||
|
||||
pub const DeleteDirError = error{
|
||||
DirNotEmpty,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
FileSystem,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
NotDir,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Returns `error.DirNotEmpty` if the directory is not empty.
|
||||
///
|
||||
/// To delete a directory recursively, see `deleteTree`.
|
||||
///
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void {
|
||||
return io.vtable.dirDeleteDir(io.userdata, dir, sub_path);
|
||||
}
|
||||
|
||||
pub const RenameError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to rename a resource by path relative to it.
|
||||
///
|
||||
/// On Windows, this error may be returned instead of PathAlreadyExists when
|
||||
/// renaming a directory over an existing directory.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
DiskQuota,
|
||||
IsDir,
|
||||
SymLinkLoop,
|
||||
LinkQuotaExceeded,
|
||||
NameTooLong,
|
||||
FileNotFound,
|
||||
NotDir,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
PathAlreadyExists,
|
||||
ReadOnlyFileSystem,
|
||||
RenameAcrossMountPoints,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
NoDevice,
|
||||
SharingViolation,
|
||||
PipeBusy,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
/// 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,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Change the name or location of a file or directory.
|
||||
///
|
||||
/// If `new_sub_path` already exists, it will be replaced.
|
||||
///
|
||||
/// Renaming a file over an existing directory or a directory over an existing
|
||||
/// file will fail with `error.IsDir` or `error.NotDir`
|
||||
///
|
||||
/// 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 rename(
|
||||
old_dir: Dir,
|
||||
old_sub_path: []const u8,
|
||||
new_dir: Dir,
|
||||
new_sub_path: []const u8,
|
||||
io: Io,
|
||||
) RenameError!void {
|
||||
return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
|
||||
}
|
||||
|
||||
/// Use with `Dir.symLink`, `Dir.symLinkAtomic`, and `symLinkAbsolute` to
|
||||
/// specify whether the symlink will point to a file or a directory. This value
|
||||
/// is ignored on all hosts except Windows where creating symlinks to different
|
||||
/// resource types, requires different flags. By default, `symLinkAbsolute` is
|
||||
/// assumed to point to a file.
|
||||
pub const SymLinkFlags = struct {
|
||||
is_directory: bool = false,
|
||||
};
|
||||
|
||||
pub const SymLinkError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to create a new symbolic link relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
DiskQuota,
|
||||
PathAlreadyExists,
|
||||
FileSystem,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
ReadOnlyFileSystem,
|
||||
NotDir,
|
||||
NameTooLong,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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 symLink(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: SymLinkFlags,
|
||||
) SymLinkError!void {
|
||||
return io.vtable.dirSymLink(io.userdata, dir, target_path, sym_link_path, flags);
|
||||
}
|
||||
|
||||
/// Same as `symLink`, except tries to create the symbolic link until it
|
||||
/// succeeds or encounters an error other than `error.PathAlreadyExists`.
|
||||
///
|
||||
/// * 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 symLinkAtomic(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
if (dir.symLink(io, target_path, sym_link_path, flags)) {
|
||||
return;
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => {},
|
||||
else => |e| return e,
|
||||
}
|
||||
|
||||
const dirname = std.fs.path.dirname(sym_link_path) orelse ".";
|
||||
|
||||
const rand_len = @sizeOf(u64) * 2;
|
||||
const temp_path_len = dirname.len + 1 + rand_len;
|
||||
var temp_path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
|
||||
if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
|
||||
@memcpy(temp_path_buf[0..dirname.len], dirname);
|
||||
temp_path_buf[dirname.len] = std.fs.path.sep;
|
||||
|
||||
const temp_path = temp_path_buf[0..temp_path_len];
|
||||
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
|
||||
|
||||
if (dir.symLink(io, target_path, temp_path, flags)) {
|
||||
return dir.rename(temp_path, dir, io, sym_link_path);
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const ReadLinkError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to read value of a symbolic link relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileSystem,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NotLink,
|
||||
NotDir,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
/// Windows-only. This error may occur if the opened reparse point is
|
||||
/// of unsupported type.
|
||||
UnsupportedReparsePointType,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
/// 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,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Obtain target of a symbolic link.
|
||||
///
|
||||
/// Returns how many bytes of `buffer` are populated.
|
||||
///
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
///
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkError!usize {
|
||||
return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer);
|
||||
}
|
||||
|
||||
pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
|
||||
/// File size reached or exceeded the provided limit.
|
||||
StreamTooLong,
|
||||
};
|
||||
|
||||
/// Reads all the bytes from the named file. On success, caller owns returned
|
||||
/// buffer.
|
||||
///
|
||||
/// If the file size is already known, a better alternative is to initialize a
|
||||
/// `File.Reader`.
|
||||
///
|
||||
/// If the file size cannot be obtained, an error is returned. If
|
||||
/// this is a realistic possibility, a better alternative is to initialize a
|
||||
/// `File.Reader` which handles this seamlessly.
|
||||
pub fn readFileAlloc(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
/// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, should be encoded as valid UTF-8.
|
||||
/// On other platforms, an opaque sequence of bytes with no particular encoding.
|
||||
sub_path: []const u8,
|
||||
/// Used to allocate the result.
|
||||
gpa: Allocator,
|
||||
/// If reached or exceeded, `error.StreamTooLong` is returned instead.
|
||||
limit: Io.Limit,
|
||||
) ReadFileAllocError![]u8 {
|
||||
return readFileAllocOptions(dir, io, sub_path, gpa, limit, .of(u8), null);
|
||||
}
|
||||
|
||||
/// Reads all the bytes from the named file. On success, caller owns returned
|
||||
/// buffer.
|
||||
///
|
||||
/// If the file size is already known, a better alternative is to initialize a
|
||||
/// `File.Reader`.
|
||||
pub fn readFileAllocOptions(
|
||||
dir: Dir,
|
||||
io: Io,
|
||||
/// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, should be encoded as valid UTF-8.
|
||||
/// On other platforms, an opaque sequence of bytes with no particular encoding.
|
||||
sub_path: []const u8,
|
||||
/// Used to allocate the result.
|
||||
gpa: Allocator,
|
||||
/// If reached or exceeded, `error.StreamTooLong` is returned instead.
|
||||
limit: Io.Limit,
|
||||
comptime alignment: std.mem.Alignment,
|
||||
comptime sentinel: ?u8,
|
||||
) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
|
||||
var file = try dir.openFile(io, sub_path, .{});
|
||||
defer file.close(io);
|
||||
var file_reader = file.reader(io, &.{});
|
||||
return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
|
||||
error.ReadFailed => return file_reader.err.?,
|
||||
error.OutOfMemory, error.StreamTooLong => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const DeleteTreeError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileTooBig,
|
||||
SymLinkLoop,
|
||||
ProcessFdQuotaExceeded,
|
||||
NameTooLong,
|
||||
SystemFdQuotaExceeded,
|
||||
NoDevice,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
FileSystem,
|
||||
FileBusy,
|
||||
DeviceBusy,
|
||||
ProcessNotFound,
|
||||
/// One of the path components was not a directory.
|
||||
/// This error is unreachable if `sub_path` does not contain a path separator.
|
||||
NotDir,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
/// Whether `sub_path` describes a symlink, file, or directory, this function
|
||||
/// removes it. If it cannot be removed because it is a non-empty directory,
|
||||
/// this function recursively removes its entries and then tries again.
|
||||
///
|
||||
/// This operation is not atomic on most file systems.
|
||||
///
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
|
||||
var initial_iterable_dir = (try dir.deleteTreeOpenInitialSubpath(io, sub_path, .file)) orelse return;
|
||||
|
||||
const StackItem = struct {
|
||||
name: []const u8,
|
||||
parent_dir: Dir,
|
||||
iter: Dir.Iterator,
|
||||
|
||||
fn closeAll(inner_io: Io, items: []@This()) void {
|
||||
for (items) |*item| item.iter.dir.close(inner_io);
|
||||
}
|
||||
};
|
||||
|
||||
var stack_buffer: [16]StackItem = undefined;
|
||||
var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
|
||||
defer StackItem.closeAll(io, stack.items);
|
||||
|
||||
stack.appendAssumeCapacity(.{
|
||||
.name = sub_path,
|
||||
.parent_dir = dir,
|
||||
.iter = initial_iterable_dir.iterateAssumeFirstIteration(),
|
||||
});
|
||||
|
||||
process_stack: while (stack.items.len != 0) {
|
||||
var top = &stack.items[stack.items.len - 1];
|
||||
while (try top.iter.next()) |entry| {
|
||||
var treat_as_dir = entry.kind == .directory;
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
if (stack.unusedCapacitySlice().len >= 1) {
|
||||
var iterable_dir = top.iter.dir.openDir(io, entry.name, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
break :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
stack.appendAssumeCapacity(.{
|
||||
.name = entry.name,
|
||||
.parent_dir = top.iter.dir,
|
||||
.iter = iterable_dir.iterateAssumeFirstIteration(),
|
||||
});
|
||||
continue :process_stack;
|
||||
} else {
|
||||
try top.iter.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind);
|
||||
break :handle_entry;
|
||||
}
|
||||
} else {
|
||||
if (top.iter.dir.deleteFile(io, entry.name)) {
|
||||
break :handle_entry;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => break :handle_entry,
|
||||
|
||||
// Impossible because we do not pass any path separators.
|
||||
error.NotDir => unreachable,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On Windows, we can't delete until the dir's handle has been closed, so
|
||||
// close it before we try to delete.
|
||||
top.iter.dir.close(io);
|
||||
|
||||
// In order to avoid double-closing the directory when cleaning up
|
||||
// the stack in the case of an error, we save the relevant portions and
|
||||
// pop the value from the stack.
|
||||
const parent_dir = top.parent_dir;
|
||||
const name = top.name;
|
||||
stack.items.len -= 1;
|
||||
|
||||
var need_to_retry: bool = false;
|
||||
parent_dir.deleteDir(name) catch |err| switch (err) {
|
||||
error.FileNotFound => {},
|
||||
error.DirNotEmpty => need_to_retry = true,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
if (need_to_retry) {
|
||||
// Since we closed the handle that the previous iterator used, we
|
||||
// need to re-open the dir and re-create the iterator.
|
||||
var iterable_dir = iterable_dir: {
|
||||
var treat_as_dir = true;
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
break :iterable_dir parent_dir.openDir(name, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
continue :process_stack;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
} else {
|
||||
if (parent_dir.deleteFile(name)) {
|
||||
continue :process_stack;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => continue :process_stack,
|
||||
|
||||
// Impossible because we do not pass any path separators.
|
||||
error.NotDir => unreachable,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// We know there is room on the stack since we are just re-adding
|
||||
// the StackItem that we previously popped.
|
||||
stack.appendAssumeCapacity(.{
|
||||
.name = name,
|
||||
.parent_dir = parent_dir,
|
||||
.iter = iterable_dir.iterateAssumeFirstIteration(),
|
||||
});
|
||||
continue :process_stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
|
||||
/// This is slower than `deleteTree` but uses less stack space.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
|
||||
return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file);
|
||||
}
|
||||
|
||||
fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
|
||||
start_over: while (true) {
|
||||
var dir = (try parent.deleteTreeOpenInitialSubpath(io, sub_path, kind_hint)) orelse return;
|
||||
var cleanup_dir_parent: ?Dir = null;
|
||||
defer if (cleanup_dir_parent) |*d| d.close();
|
||||
|
||||
var cleanup_dir = true;
|
||||
defer if (cleanup_dir) dir.close();
|
||||
|
||||
// Valid use of max_path_bytes because dir_name_buf will only
|
||||
// ever store a single path component that was returned from the
|
||||
// filesystem.
|
||||
var dir_name_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var dir_name: []const u8 = sub_path;
|
||||
|
||||
// Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
|
||||
// Go through each entry and if it is not a directory, delete it. If it is a directory,
|
||||
// open it, and close the original directory. Repeat. Then start the entire operation over.
|
||||
|
||||
scan_dir: while (true) {
|
||||
var dir_it = dir.iterateAssumeFirstIteration();
|
||||
dir_it: while (try dir_it.next()) |entry| {
|
||||
var treat_as_dir = entry.kind == .directory;
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
const new_dir = dir.openDir(entry.name, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
continue :dir_it;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
if (cleanup_dir_parent) |*d| d.close();
|
||||
cleanup_dir_parent = dir;
|
||||
dir = new_dir;
|
||||
const result = dir_name_buf[0..entry.name.len];
|
||||
@memcpy(result, entry.name);
|
||||
dir_name = result;
|
||||
continue :scan_dir;
|
||||
} else {
|
||||
if (dir.deleteFile(entry.name)) {
|
||||
continue :dir_it;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => continue :dir_it,
|
||||
|
||||
// Impossible because we do not pass any path separators.
|
||||
error.NotDir => unreachable,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reached the end of the directory entries, which means we successfully deleted all of them.
|
||||
// Now to remove the directory itself.
|
||||
dir.close();
|
||||
cleanup_dir = false;
|
||||
|
||||
if (cleanup_dir_parent) |d| {
|
||||
d.deleteDir(io, dir_name) catch |err| switch (err) {
|
||||
// These two things can happen due to file system race conditions.
|
||||
error.FileNotFound, error.DirNotEmpty => continue :start_over,
|
||||
else => |e| return e,
|
||||
};
|
||||
continue :start_over;
|
||||
} else {
|
||||
parent.deleteDir(io, sub_path) catch |err| switch (err) {
|
||||
error.FileNotFound => return,
|
||||
error.DirNotEmpty => continue :start_over,
|
||||
else => |e| return e,
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On successful delete, returns null.
|
||||
fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
|
||||
return iterable_dir: {
|
||||
// Treat as a file by default
|
||||
var treat_as_dir = kind_hint == .directory;
|
||||
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
break :iterable_dir dir.openDir(sub_path, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
return null;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.DeviceBusy,
|
||||
error.NetworkNotFound,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
} else {
|
||||
if (dir.deleteFile(sub_path)) {
|
||||
return null;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.NotDir,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const CopyFileOptions = struct {
|
||||
/// When this is `null` the mode is copied from the source file.
|
||||
override_mode: ?File.Mode = null,
|
||||
};
|
||||
|
||||
pub const CopyFileError = File.OpenError || File.StatError ||
|
||||
File.Atomic.InitError || File.Atomic.FinishError ||
|
||||
File.ReadError || File.WriteError || error{InvalidFileName};
|
||||
|
||||
/// Atomically creates a new file at `dest_path` within `dest_dir` with the
|
||||
/// same contents as `source_path` within `source_dir`, overwriting any already
|
||||
/// existing file.
|
||||
///
|
||||
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
|
||||
/// readily available, there is a possibility of power loss or application
|
||||
/// termination leaving temporary files present in the same directory as
|
||||
/// dest_path.
|
||||
///
|
||||
/// 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 copyFile(
|
||||
source_dir: Dir,
|
||||
source_path: []const u8,
|
||||
dest_dir: Dir,
|
||||
dest_path: []const u8,
|
||||
io: Io,
|
||||
options: CopyFileOptions,
|
||||
) CopyFileError!void {
|
||||
const file = try source_dir.openFile(io, source_path, .{});
|
||||
var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
|
||||
defer file_reader.file.close(io);
|
||||
|
||||
const mode = options.override_mode orelse blk: {
|
||||
const st = try file_reader.file.stat(io);
|
||||
file_reader.size = st.size;
|
||||
break :blk st.mode;
|
||||
};
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
|
||||
.mode = mode,
|
||||
.write_buffer = &buffer,
|
||||
});
|
||||
defer atomic_file.deinit(io);
|
||||
|
||||
_ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
|
||||
error.ReadFailed => return file_reader.err.?,
|
||||
error.WriteFailed => return atomic_file.file_writer.err.?,
|
||||
};
|
||||
|
||||
try atomic_file.finish();
|
||||
}
|
||||
|
||||
pub const AtomicFileOptions = struct {
|
||||
mode: File.Mode = File.default_mode,
|
||||
make_path: bool = false,
|
||||
write_buffer: []u8,
|
||||
};
|
||||
|
||||
/// Directly access the `.file` field, and then call `File.Atomic.finish` to
|
||||
/// atomically replace `dest_path` with contents.
|
||||
///
|
||||
/// Always call `File.Atomic.deinit` to clean up, regardless of whether
|
||||
/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until
|
||||
/// `File.Atomic.deinit` is called.
|
||||
///
|
||||
/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `dest_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {
|
||||
if (std.fs.path.dirname(dest_path)) |dirname| {
|
||||
const dir = if (options.make_path)
|
||||
try parent.makeOpenPath(io, dirname, .{})
|
||||
else
|
||||
try parent.openDir(io, dirname, .{});
|
||||
|
||||
return .init(std.fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
|
||||
} else {
|
||||
return .init(dest_path, options.mode, parent, false, options.write_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub const SetModeError = File.SetModeError;
|
||||
|
||||
/// Also known as "chmod".
|
||||
///
|
||||
/// The process must have the correct privileges in order to do this
|
||||
/// successfully, or must have the effective user ID matching the owner
|
||||
/// of the directory. Additionally, the directory must have been opened
|
||||
/// with `OpenOptions.iterate` set to `true`.
|
||||
pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void {
|
||||
return io.vtable.dirSetMode(io.userdata, dir, new_mode);
|
||||
}
|
||||
|
||||
pub const SetOwnerError = File.SetOwnerError;
|
||||
|
||||
/// Also known as "chown".
|
||||
///
|
||||
/// The process must have the correct privileges in order to do this
|
||||
/// successfully. The group may be changed by the owner of the directory to
|
||||
/// any group of which the owner is a member. Additionally, the directory
|
||||
/// must have been opened with `OpenOptions.iterate` set to `true`. If the
|
||||
/// owner or group is specified as `null`, the ID is not changed.
|
||||
pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerError!void {
|
||||
return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
|
||||
}
|
||||
|
||||
pub const SetPermissionsError = File.SetPermissionsError;
|
||||
pub const Permissions = File.Permissions;
|
||||
|
||||
pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void {
|
||||
return io.vtable.dirSetPermissions(io.userdata, dir, permissions);
|
||||
}
|
||||
|
||||
+184
-6
@@ -7,12 +7,23 @@ const is_windows = native_os == .windows;
|
||||
const std = @import("../std.zig");
|
||||
const Io = std.Io;
|
||||
const assert = std.debug.assert;
|
||||
const Dir = std.Io.Dir;
|
||||
|
||||
handle: Handle,
|
||||
|
||||
pub const Handle = std.posix.fd_t;
|
||||
pub const Mode = std.posix.mode_t;
|
||||
pub const INode = std.posix.ino_t;
|
||||
pub const Uid = std.posix.uid_t;
|
||||
pub const Gid = std.posix.gid_t;
|
||||
|
||||
/// This is the default mode given to POSIX operating systems for creating
|
||||
/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
|
||||
/// since most people would expect "-rw-r--r--", for example, when using
|
||||
/// the `touch` command, which would correspond to `0o644`. However, POSIX
|
||||
/// libc implementations use `0o666` inside `fopen` and then rely on the
|
||||
/// process-scoped "umask" setting to adjust this number for file creation.
|
||||
pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
|
||||
|
||||
pub const Kind = enum {
|
||||
block_device,
|
||||
@@ -92,6 +103,11 @@ pub const Lock = enum {
|
||||
exclusive,
|
||||
};
|
||||
|
||||
pub const LockError = error{
|
||||
SystemResources,
|
||||
FileLocksNotSupported,
|
||||
} || Io.UnexpectedError;
|
||||
|
||||
pub const OpenFlags = struct {
|
||||
mode: OpenMode = .read_only,
|
||||
|
||||
@@ -141,7 +157,53 @@ pub const OpenFlags = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const CreateFlags = std.fs.File.CreateFlags;
|
||||
pub const CreateFlags = struct {
|
||||
/// Whether the file will be created with read access.
|
||||
read: bool = false,
|
||||
|
||||
/// If the file already exists, and is a regular file, and the access
|
||||
/// mode allows writing, it will be truncated to length 0.
|
||||
truncate: bool = true,
|
||||
|
||||
/// Ensures that this open call creates the file, otherwise causes
|
||||
/// `error.PathAlreadyExists` to be returned.
|
||||
exclusive: bool = false,
|
||||
|
||||
/// Open the file with an advisory lock to coordinate with other processes
|
||||
/// accessing it at the same time. An exclusive lock will prevent other
|
||||
/// processes from acquiring a lock. A shared lock will prevent other
|
||||
/// processes from acquiring a exclusive lock, but does not prevent
|
||||
/// other process from getting their own shared locks.
|
||||
///
|
||||
/// The lock is advisory, except on Linux in very specific circumstances[1].
|
||||
/// This means that a process that does not respect the locking API can still get access
|
||||
/// to the file, despite the lock.
|
||||
///
|
||||
/// On these operating systems, the lock is acquired atomically with
|
||||
/// opening the file:
|
||||
/// * Darwin
|
||||
/// * DragonFlyBSD
|
||||
/// * FreeBSD
|
||||
/// * Haiku
|
||||
/// * NetBSD
|
||||
/// * OpenBSD
|
||||
/// On these operating systems, the lock is acquired via a separate syscall
|
||||
/// after opening the file:
|
||||
/// * Linux
|
||||
/// * Windows
|
||||
///
|
||||
/// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
|
||||
lock: Lock = .none,
|
||||
|
||||
/// Sets whether or not to wait until the file is locked to return. If set to true,
|
||||
/// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
|
||||
/// is available to proceed.
|
||||
lock_nonblocking: bool = false,
|
||||
|
||||
/// For POSIX systems this is the file system mode the file will
|
||||
/// be created with. On other systems this is always 0.
|
||||
mode: Mode = default_mode,
|
||||
};
|
||||
|
||||
pub const OpenError = error{
|
||||
SharingViolation,
|
||||
@@ -231,6 +293,17 @@ pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) Wr
|
||||
return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
|
||||
}
|
||||
|
||||
/// Opens a file for reading or writing, without attempting to create a new
|
||||
/// file, based on an absolute path.
|
||||
///
|
||||
/// Returns an open resource to be released with `close`.
|
||||
///
|
||||
/// Asserts that the path is absolute. See `Dir.openFile` 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 openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
|
||||
assert(std.fs.path.isAbsolute(absolute_path));
|
||||
return Io.Dir.cwd().openFile(io, absolute_path, flags);
|
||||
@@ -364,11 +437,6 @@ pub const Reader = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Takes a legacy `std.fs.File` to help with upgrading.
|
||||
pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
|
||||
return .init(.{ .handle = file.handle }, io, buffer);
|
||||
}
|
||||
|
||||
pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
|
||||
return .{
|
||||
.io = io,
|
||||
@@ -652,3 +720,113 @@ pub const Reader = struct {
|
||||
return size - logicalPos(r) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Atomic = struct {
|
||||
file_writer: File.Writer,
|
||||
random_integer: u64,
|
||||
dest_basename: []const u8,
|
||||
file_open: bool,
|
||||
file_exists: bool,
|
||||
close_dir_on_deinit: bool,
|
||||
dir: Dir,
|
||||
|
||||
pub const InitError = File.OpenError;
|
||||
|
||||
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
||||
pub fn init(
|
||||
dest_basename: []const u8,
|
||||
mode: File.Mode,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
write_buffer: []u8,
|
||||
) InitError!Atomic {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file_writer = file.writer(write_buffer),
|
||||
.random_integer = random_integer,
|
||||
.dest_basename = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Always call deinit, even after a successful finish().
|
||||
pub fn deinit(af: *Atomic) void {
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close();
|
||||
af.file_open = false;
|
||||
}
|
||||
if (af.file_exists) {
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
af.dir.deleteFile(&tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
}
|
||||
if (af.close_dir_on_deinit) {
|
||||
af.dir.close();
|
||||
}
|
||||
af.* = undefined;
|
||||
}
|
||||
|
||||
pub const FlushError = File.WriteError;
|
||||
|
||||
pub fn flush(af: *Atomic) FlushError!void {
|
||||
af.file_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return af.file_writer.err.?,
|
||||
};
|
||||
}
|
||||
|
||||
pub const RenameIntoPlaceError = Dir.RenameError;
|
||||
|
||||
/// On Windows, this function introduces a period of time where some file
|
||||
/// system operations on the destination file will result in
|
||||
/// `error.AccessDenied`, including rename operations (such as the one used in
|
||||
/// this function).
|
||||
pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
|
||||
const io = af.file_writer.io;
|
||||
assert(af.file_exists);
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close();
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
|
||||
af.file_exists = false;
|
||||
}
|
||||
|
||||
pub const FinishError = FlushError || RenameIntoPlaceError;
|
||||
|
||||
/// Combination of `flush` followed by `renameIntoPlace`.
|
||||
pub fn finish(af: *Atomic) FinishError!void {
|
||||
try af.flush();
|
||||
try af.renameIntoPlace();
|
||||
}
|
||||
};
|
||||
|
||||
pub const SetModeError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
pub const SetOwnerError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || Io.Cancelable || Io.UnexpectedError;
|
||||
|
||||
+1445
-6
@@ -657,12 +657,22 @@ pub fn io(t: *Threaded) Io {
|
||||
.dirMakeOpenPath = dirMakeOpenPath,
|
||||
.dirStat = dirStat,
|
||||
.dirStatPath = dirStatPath,
|
||||
.fileStat = fileStat,
|
||||
.dirAccess = dirAccess,
|
||||
.dirCreateFile = dirCreateFile,
|
||||
.dirOpenFile = dirOpenFile,
|
||||
.dirOpenDir = dirOpenDir,
|
||||
.dirClose = dirClose,
|
||||
.dirRealPath = dirRealPath,
|
||||
.dirDeleteFile = dirDeleteFile,
|
||||
.dirDeleteDir = dirDeleteDir,
|
||||
.dirRename = dirRename,
|
||||
.dirSymLink = dirSymLink,
|
||||
.dirReadLink = dirReadLink,
|
||||
.dirSetMode = dirSetMode,
|
||||
.dirSetOwner = dirSetOwner,
|
||||
.dirSetPermissions = dirSetPermissions,
|
||||
|
||||
.fileStat = fileStat,
|
||||
.fileClose = fileClose,
|
||||
.fileWriteStreaming = fileWriteStreaming,
|
||||
.fileWritePositional = fileWritePositional,
|
||||
@@ -753,12 +763,22 @@ pub fn ioBasic(t: *Threaded) Io {
|
||||
.dirMakeOpenPath = dirMakeOpenPath,
|
||||
.dirStat = dirStat,
|
||||
.dirStatPath = dirStatPath,
|
||||
.fileStat = fileStat,
|
||||
.dirAccess = dirAccess,
|
||||
.dirCreateFile = dirCreateFile,
|
||||
.dirOpenFile = dirOpenFile,
|
||||
.dirOpenDir = dirOpenDir,
|
||||
.dirClose = dirClose,
|
||||
.dirRealPath = dirRealPath,
|
||||
.dirDeleteFile = dirDeleteFile,
|
||||
.dirDeleteDir = dirDeleteDir,
|
||||
.dirRename = dirRename,
|
||||
.dirSymLink = dirSymLink,
|
||||
.dirReadLink = dirReadLink,
|
||||
.dirSetMode = dirSetMode,
|
||||
.dirSetOwner = dirSetOwner,
|
||||
.dirSetPermissions = dirSetPermissions,
|
||||
|
||||
.fileStat = fileStat,
|
||||
.fileClose = fileClose,
|
||||
.fileWriteStreaming = fileWriteStreaming,
|
||||
.fileWritePositional = fileWritePositional,
|
||||
@@ -3093,6 +3113,1335 @@ fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
|
||||
posix.close(dir.handle);
|
||||
}
|
||||
|
||||
const dirRealPath = switch (native_os) {
|
||||
.windows => dirRealPathWindows,
|
||||
else => dirRealPathPosix,
|
||||
};
|
||||
|
||||
fn dirRealPathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const w = windows;
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
try current_thread.checkCancel();
|
||||
|
||||
var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path);
|
||||
|
||||
const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
|
||||
const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE;
|
||||
const creation = w.FILE_OPEN;
|
||||
const h_file = blk: {
|
||||
const res = w.OpenFile(path_name_w.span(), .{
|
||||
.dir = dir.handle,
|
||||
.access_mask = access_mask,
|
||||
.share_access = share_access,
|
||||
.creation = creation,
|
||||
.filter = .any,
|
||||
}) catch |err| switch (err) {
|
||||
error.WouldBlock => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
break :blk res;
|
||||
};
|
||||
defer w.CloseHandle(h_file);
|
||||
|
||||
const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(wide_slice);
|
||||
if (len > out_buffer.len)
|
||||
return error.NameTooLong;
|
||||
|
||||
return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
|
||||
}
|
||||
|
||||
fn dirRealPathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize {
|
||||
if (native_os == .wasi) @compileError("unsupported operating system");
|
||||
const max_path_bytes = std.fs.max_path_bytes;
|
||||
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
|
||||
|
||||
var flags: posix.O = .{};
|
||||
if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
|
||||
if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
|
||||
if (@hasField(posix.O, "PATH")) flags.PATH = true;
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
const fd: posix.fd_t = while (true) {
|
||||
const rc = openat_sym(dir.handle, sub_path_posix, flags, 0);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
break @intCast(rc);
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.BadPathName,
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FBIG => return error.FileTooBig,
|
||||
.OVERFLOW => return error.FileTooBig,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MFILE => return error.ProcessFdQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NFILE => return error.SystemFdQuotaExceeded,
|
||||
.NODEV => return error.NoDevice,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.SRCH => return error.ProcessNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.BUSY => return error.DeviceBusy,
|
||||
.NXIO => return error.NoDevice,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
errdefer posix.close(fd);
|
||||
|
||||
switch (native_os) {
|
||||
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
|
||||
// On macOS, we can use F.GETPATH fcntl command to query the OS for
|
||||
// the path to the file descriptor.
|
||||
@memset(out_buffer, 0);
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
break;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => return error.FileNotFound,
|
||||
.NOSPC => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
// TODO man pages for fcntl on macOS don't really tell you what
|
||||
// errno values to expect when command is F.GETPATH...
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len;
|
||||
},
|
||||
.linux, .serenity, .illumos => {
|
||||
var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
|
||||
const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
|
||||
const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
const len: usize = @bitCast(rc);
|
||||
return len;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.NotLink,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.ILSEQ => |err| return errnoBug(err),
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
.freebsd => {
|
||||
var kfile: std.c.kinfo_file = undefined;
|
||||
kfile.structsize = std.c.KINFO_FILE_SIZE;
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
break;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => return error.FileNotFound,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
const len = std.mem.indexOfScalar(u8, &kfile.path, 0) orelse kfile.path.len;
|
||||
if (len == 0) return error.NameTooLong;
|
||||
return len;
|
||||
},
|
||||
.netbsd, .dragonfly => {
|
||||
@memset(out_buffer[0..max_path_bytes], 0);
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
break;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.BADF => return error.FileNotFound,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.RANGE => return error.NameTooLong,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len;
|
||||
},
|
||||
else => @compileError("unsupported OS"),
|
||||
}
|
||||
comptime unreachable;
|
||||
}
|
||||
|
||||
const dirDeleteFile = switch (native_os) {
|
||||
.windows => dirDeleteFileWindows,
|
||||
.wasi => dirDeleteFileWasi,
|
||||
else => dirDeleteFilePosix,
|
||||
};
|
||||
|
||||
fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
|
||||
return dirDeleteWindows(userdata, dir, sub_path, false);
|
||||
}
|
||||
|
||||
fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
|
||||
if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path);
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len);
|
||||
switch (res) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.NOTEMPTY => return error.DirNotEmpty,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
.INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
// Some systems return permission errors when trying to delete a
|
||||
// directory, so we need to handle that case specifically and
|
||||
// translate the error.
|
||||
.PERM => switch (native_os) {
|
||||
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
|
||||
|
||||
// Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them).
|
||||
var st = std.mem.zeroes(posix.Stat);
|
||||
while (true) {
|
||||
try current_thread.checkCancel();
|
||||
switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
break;
|
||||
},
|
||||
.INTR => continue,
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => {
|
||||
current_thread.endSyscall();
|
||||
return error.PermissionDenied;
|
||||
},
|
||||
}
|
||||
}
|
||||
const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR;
|
||||
if (is_dir)
|
||||
return error.IsDir
|
||||
else
|
||||
return error.PermissionDenied;
|
||||
},
|
||||
else => {
|
||||
current_thread.endSyscall();
|
||||
return error.PermissionDenied;
|
||||
},
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.EXIST => |err| return errnoBug(err),
|
||||
.NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR
|
||||
.ILSEQ => return error.BadPathName,
|
||||
.INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirDeleteDir = switch (native_os) {
|
||||
.windows => dirDeleteDirWindows,
|
||||
.wasi => dirDeleteDirWasi,
|
||||
else => dirDeleteDirPosix,
|
||||
};
|
||||
|
||||
fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
|
||||
return dirDeleteWindows(userdata, dir, sub_path, true);
|
||||
}
|
||||
|
||||
fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, remove_dir: bool) Io.Dir.DeleteFileError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
const w = windows;
|
||||
|
||||
try current_thread.checkCancel();
|
||||
|
||||
const sub_path_w = try w.sliceToPrefixedFileW(dir.handle, sub_path);
|
||||
|
||||
const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
|
||||
var nt_name: w.UNICODE_STRING = .{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
// The Windows API makes this mutable, but it will not mutate here.
|
||||
.Buffer = @constCast(sub_path_w.ptr),
|
||||
};
|
||||
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||||
// Can't remove the parent directory with an open handle.
|
||||
return error.FileBusy;
|
||||
}
|
||||
|
||||
const create_options_flags: w.ULONG = if (remove_dir)
|
||||
w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT
|
||||
else
|
||||
w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT;
|
||||
|
||||
var attr: w.OBJECT_ATTRIBUTES = .{
|
||||
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
|
||||
.Attributes = w.OBJ_CASE_INSENSITIVE,
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io_status_block: w.IO_STATUS_BLOCK = undefined;
|
||||
var tmp_handle: w.HANDLE = undefined;
|
||||
var rc = w.ntdll.NtCreateFile(
|
||||
&tmp_handle,
|
||||
w.SYNCHRONIZE | w.DELETE,
|
||||
&attr,
|
||||
&io_status_block,
|
||||
null,
|
||||
0,
|
||||
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
|
||||
w.FILE_OPEN,
|
||||
create_options_flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
|
||||
.BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
.SHARING_VIOLATION => return error.FileBusy,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.DELETE_PENDING => return,
|
||||
else => return w.unexpectedStatus(rc),
|
||||
}
|
||||
defer w.CloseHandle(tmp_handle);
|
||||
|
||||
// FileDispositionInformationEx has varying levels of support:
|
||||
// - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
|
||||
// (INVALID_INFO_CLASS is returned if not supported)
|
||||
// - Requires the NTFS filesystem
|
||||
// (on filesystems like FAT32, INVALID_PARAMETER is returned)
|
||||
// - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
|
||||
// - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
|
||||
// (NOT_SUPPORTED is returned if a flag is unsupported)
|
||||
//
|
||||
// The strategy here is just to try using FileDispositionInformationEx and fall back to
|
||||
// FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
|
||||
const need_fallback = need_fallback: {
|
||||
try current_thread.checkCancel();
|
||||
|
||||
// Deletion with posix semantics if the filesystem supports it.
|
||||
var info: w.FILE_DISPOSITION_INFORMATION_EX = .{
|
||||
.Flags = w.FILE_DISPOSITION_DELETE |
|
||||
w.FILE_DISPOSITION_POSIX_SEMANTICS |
|
||||
w.FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
|
||||
};
|
||||
|
||||
rc = w.ntdll.NtSetInformationFile(
|
||||
tmp_handle,
|
||||
&io_status_block,
|
||||
&info,
|
||||
@sizeOf(w.FILE_DISPOSITION_INFORMATION_EX),
|
||||
.FileDispositionInformationEx,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
// The filesystem does not support FileDispositionInformationEx
|
||||
.INVALID_PARAMETER,
|
||||
// The operating system does not support FileDispositionInformationEx
|
||||
.INVALID_INFO_CLASS,
|
||||
// The operating system does not support one of the flags
|
||||
.NOT_SUPPORTED,
|
||||
=> break :need_fallback true,
|
||||
// For all other statuses, fall down to the switch below to handle them.
|
||||
else => break :need_fallback false,
|
||||
}
|
||||
};
|
||||
|
||||
if (need_fallback) {
|
||||
try current_thread.checkCancel();
|
||||
|
||||
// Deletion with file pending semantics, which requires waiting or moving
|
||||
// files to get them removed (from here).
|
||||
var file_dispo: w.FILE_DISPOSITION_INFORMATION = .{
|
||||
.DeleteFile = w.TRUE,
|
||||
};
|
||||
|
||||
rc = w.ntdll.NtSetInformationFile(
|
||||
tmp_handle,
|
||||
&io_status_block,
|
||||
&file_dispo,
|
||||
@sizeOf(w.FILE_DISPOSITION_INFORMATION),
|
||||
.FileDispositionInformation,
|
||||
);
|
||||
}
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.CANNOT_DELETE => return error.AccessDenied,
|
||||
.MEDIA_WRITE_PROTECTED => return error.AccessDenied,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
else => return w.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
|
||||
if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path);
|
||||
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len);
|
||||
switch (res) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.NOTEMPTY => return error.DirNotEmpty,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
.INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => |err| return errnoBug(err),
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.EXIST => |err| return errnoBug(err),
|
||||
.NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR
|
||||
.ILSEQ => return error.BadPathName,
|
||||
.INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirRename = switch (native_os) {
|
||||
.windows => dirRenameWindows,
|
||||
.wasi => dirRenameWasi,
|
||||
else => dirRenamePosix,
|
||||
};
|
||||
|
||||
fn dirRenameWindows(
|
||||
userdata: ?*anyopaque,
|
||||
old_dir: Io.Dir,
|
||||
old_sub_path: []const u8,
|
||||
new_dir: Io.Dir,
|
||||
new_sub_path: []const u8,
|
||||
) Io.Dir.RenameError!void {
|
||||
const w = windows;
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
const old_path_w = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
|
||||
const new_path_w = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
|
||||
const replace_if_exists = true;
|
||||
|
||||
try current_thread.checkCancel();
|
||||
|
||||
const src_fd = w.OpenFile(old_path_w, .{
|
||||
.dir = old_dir.handle,
|
||||
.access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | w.DELETE,
|
||||
.creation = w.FILE_OPEN,
|
||||
.filter = .any, // This function is supposed to rename both files and directories.
|
||||
.follow_symlinks = false,
|
||||
}) catch |err| switch (err) {
|
||||
error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
|
||||
else => |e| return e,
|
||||
};
|
||||
defer w.CloseHandle(src_fd);
|
||||
|
||||
var rc: w.NTSTATUS = undefined;
|
||||
// FileRenameInformationEx has varying levels of support:
|
||||
// - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
|
||||
// (INVALID_INFO_CLASS is returned if not supported)
|
||||
// - Requires the NTFS filesystem
|
||||
// (on filesystems like FAT32, INVALID_PARAMETER is returned)
|
||||
// - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
|
||||
// - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
|
||||
// (NOT_SUPPORTED is returned if a flag is unsupported)
|
||||
//
|
||||
// The strategy here is just to try using FileRenameInformationEx and fall back to
|
||||
// FileRenameInformation if the return value lets us know that some aspect of it is not supported.
|
||||
const need_fallback = need_fallback: {
|
||||
const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + (w.PATH_MAX_WIDE * 2);
|
||||
var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION_EX)) = undefined;
|
||||
const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2;
|
||||
if (struct_len > struct_buf_len) return error.NameTooLong;
|
||||
|
||||
const rename_info: *w.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf);
|
||||
var io_status_block: w.IO_STATUS_BLOCK = undefined;
|
||||
|
||||
var flags: w.ULONG = w.FILE_RENAME_POSIX_SEMANTICS | w.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE;
|
||||
if (replace_if_exists) flags |= w.FILE_RENAME_REPLACE_IF_EXISTS;
|
||||
rename_info.* = .{
|
||||
.Flags = flags,
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
|
||||
.FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
|
||||
.FileName = undefined,
|
||||
};
|
||||
@memcpy((&rename_info.FileName).ptr, new_path_w);
|
||||
rc = w.ntdll.NtSetInformationFile(
|
||||
src_fd,
|
||||
&io_status_block,
|
||||
rename_info,
|
||||
@intCast(struct_len), // already checked for error.NameTooLong
|
||||
.FileRenameInformationEx,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
// The filesystem does not support FileDispositionInformationEx
|
||||
.INVALID_PARAMETER,
|
||||
// The operating system does not support FileDispositionInformationEx
|
||||
.INVALID_INFO_CLASS,
|
||||
// The operating system does not support one of the flags
|
||||
.NOT_SUPPORTED,
|
||||
=> break :need_fallback true,
|
||||
// For all other statuses, fall down to the switch below to handle them.
|
||||
else => break :need_fallback false,
|
||||
}
|
||||
};
|
||||
|
||||
if (need_fallback) {
|
||||
const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION) + (w.PATH_MAX_WIDE * 2);
|
||||
var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION)) = undefined;
|
||||
const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION) + new_path_w.len * 2;
|
||||
if (struct_len > struct_buf_len) return error.NameTooLong;
|
||||
|
||||
const rename_info: *w.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf);
|
||||
var io_status_block: w.IO_STATUS_BLOCK = undefined;
|
||||
|
||||
rename_info.* = .{
|
||||
.Flags = @intFromBool(replace_if_exists),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
|
||||
.FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
|
||||
.FileName = undefined,
|
||||
};
|
||||
@memcpy((&rename_info.FileName).ptr, new_path_w);
|
||||
|
||||
rc = w.ntdll.NtSetInformationFile(
|
||||
src_fd,
|
||||
&io_status_block,
|
||||
rename_info,
|
||||
@intCast(struct_len), // already checked for error.NameTooLong
|
||||
.FileRenameInformation,
|
||||
);
|
||||
}
|
||||
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.INVALID_HANDLE => unreachable,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
|
||||
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
|
||||
.DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
else => return w.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
fn dirRenameWasi(
|
||||
userdata: ?*anyopaque,
|
||||
old_dir: Io.Dir,
|
||||
old_sub_path: []const u8,
|
||||
new_dir: Io.Dir,
|
||||
new_sub_path: []const u8,
|
||||
) Io.Dir.RenameError!void {
|
||||
if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path);
|
||||
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (std.os.wasi.path_rename(old_dir.handle, old_sub_path.ptr, old_sub_path.len, new_dir.handle, new_sub_path.ptr, new_sub_path.len)) {
|
||||
.SUCCESS => return current_thread.endSyscall(),
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MLINK => return error.LinkQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.NOTEMPTY => return error.PathAlreadyExists,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.XDEV => return error.RenameAcrossMountPoints,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dirRenamePosix(
|
||||
userdata: ?*anyopaque,
|
||||
old_dir: Io.Dir,
|
||||
old_sub_path: []const u8,
|
||||
new_dir: Io.Dir,
|
||||
new_sub_path: []const u8,
|
||||
) Io.Dir.RenameError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
|
||||
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
|
||||
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
|
||||
.SUCCESS => return current_thread.endSyscall(),
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MLINK => return error.LinkQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.NOTEMPTY => return error.PathAlreadyExists,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.XDEV => return error.RenameAcrossMountPoints,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirSymLink = switch (native_os) {
|
||||
.windows => dirSymLinkWindows,
|
||||
.wasi => dirSymLinkWasi,
|
||||
else => dirSymLinkPosix,
|
||||
};
|
||||
|
||||
fn dirSymLinkWindows(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Io.Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: Io.Dir.SymLinkFlags,
|
||||
) Io.Dir.SymLinkError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
const w = windows;
|
||||
|
||||
try current_thread.checkCancel();
|
||||
|
||||
// Target path does not use sliceToPrefixedFileW because certain paths
|
||||
// are handled differently when creating a symlink than they would be
|
||||
// when converting to an NT namespaced path. CreateSymbolicLink in
|
||||
// symLinkW will handle the necessary conversion.
|
||||
var target_path_w: w.PathSpace = undefined;
|
||||
target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path);
|
||||
target_path_w.data[target_path_w.len] = 0;
|
||||
// However, we need to canonicalize any path separators to `\`, since if
|
||||
// the target path is relative, then it must use `\` as the path separator.
|
||||
std.mem.replaceScalar(
|
||||
u16,
|
||||
target_path_w.data[0..target_path_w.len],
|
||||
std.mem.nativeToLittle(u16, '/'),
|
||||
std.mem.nativeToLittle(u16, '\\'),
|
||||
);
|
||||
|
||||
const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path);
|
||||
|
||||
const SYMLINK_DATA = extern struct {
|
||||
ReparseTag: w.ULONG,
|
||||
ReparseDataLength: w.USHORT,
|
||||
Reserved: w.USHORT,
|
||||
SubstituteNameOffset: w.USHORT,
|
||||
SubstituteNameLength: w.USHORT,
|
||||
PrintNameOffset: w.USHORT,
|
||||
PrintNameLength: w.USHORT,
|
||||
Flags: w.ULONG,
|
||||
};
|
||||
|
||||
const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{
|
||||
.access_mask = w.SYNCHRONIZE | w.GENERIC_READ | w.GENERIC_WRITE,
|
||||
.dir = dir,
|
||||
.creation = w.FILE_CREATE,
|
||||
.filter = if (flags.is_directory) .dir_only else .file_only,
|
||||
}) catch |err| switch (err) {
|
||||
error.IsDir => return error.PathAlreadyExists,
|
||||
error.NotDir => return error.Unexpected,
|
||||
error.WouldBlock => return error.Unexpected,
|
||||
error.PipeBusy => return error.Unexpected,
|
||||
error.NoDevice => return error.Unexpected,
|
||||
error.AntivirusInterference => return error.Unexpected,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer w.CloseHandle(symlink_handle);
|
||||
|
||||
// Relevant portions of the documentation:
|
||||
// > Relative links are specified using the following conventions:
|
||||
// > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32".
|
||||
// > - Current working directory–relative—for example, if the current working directory is
|
||||
// > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt".
|
||||
// > Note: If you specify a current working directory–relative link, it is created as an absolute
|
||||
// > link, due to the way the current working directory is processed based on the user and the thread.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
|
||||
var is_target_absolute = false;
|
||||
const final_target_path = target_path: {
|
||||
if (w.hasCommonNtPrefix(u16, target_path)) {
|
||||
// Already an NT path, no need to do anything to it
|
||||
break :target_path target_path;
|
||||
} else {
|
||||
switch (w.getWin32PathType(u16, target_path)) {
|
||||
// Rooted paths need to avoid getting put through wToPrefixedFileW
|
||||
// (and they are treated as relative in this context)
|
||||
// Note: It seems that rooted paths in symbolic links are relative to
|
||||
// the drive that the symbolic exists on, not to the CWD's drive.
|
||||
// So, if the symlink is on C:\ and the CWD is on D:\,
|
||||
// it will still resolve the path relative to the root of
|
||||
// the C:\ drive.
|
||||
.rooted => break :target_path target_path,
|
||||
// Keep relative paths relative, but anything else needs to get NT-prefixed.
|
||||
else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
|
||||
break :target_path target_path,
|
||||
}
|
||||
}
|
||||
var prefixed_target_path = try w.wToPrefixedFileW(dir, target_path);
|
||||
// We do this after prefixing to ensure that drive-relative paths are treated as absolute
|
||||
is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
|
||||
break :target_path prefixed_target_path.span();
|
||||
};
|
||||
|
||||
// prepare reparse data buffer
|
||||
var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
|
||||
const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
|
||||
const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;
|
||||
const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);
|
||||
const symlink_data = SYMLINK_DATA{
|
||||
.ReparseTag = w.IO_REPARSE_TAG_SYMLINK,
|
||||
.ReparseDataLength = @intCast(buf_len - header_len),
|
||||
.Reserved = 0,
|
||||
.SubstituteNameOffset = @intCast(final_target_path.len * 2),
|
||||
.SubstituteNameLength = @intCast(final_target_path.len * 2),
|
||||
.PrintNameOffset = 0,
|
||||
.PrintNameLength = @intCast(final_target_path.len * 2),
|
||||
.Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0,
|
||||
};
|
||||
|
||||
@memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data));
|
||||
@memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
|
||||
const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
|
||||
@memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
|
||||
_ = try w.DeviceIoControl(symlink_handle, w.FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
|
||||
}
|
||||
|
||||
fn dirSymLinkWasi(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Io.Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: Io.Dir.SymLinkFlags,
|
||||
) Io.Dir.SymLinkError!void {
|
||||
if (builtin.link_libc) return dirSymLinkPosix(dir, target_path, sym_link_path, flags);
|
||||
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) {
|
||||
.SUCCESS => return current_thread.endSyscall(),
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.BADF => |err| return errnoBug(err),
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dirSymLinkPosix(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Io.Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: Io.Dir.SymLinkFlags,
|
||||
) Io.Dir.SymLinkError!void {
|
||||
_ = flags;
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var target_path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
|
||||
const target_path_posix = try pathToPosix(target_path, &target_path_buffer);
|
||||
const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) {
|
||||
.SUCCESS => return current_thread.endSyscall(),
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirReadLink = switch (native_os) {
|
||||
.windows => dirReadLinkWindows,
|
||||
.wasi => dirReadLinkWasi,
|
||||
else => dirReadLinkPosix,
|
||||
};
|
||||
|
||||
fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
const w = windows;
|
||||
|
||||
try current_thread.checkCancel();
|
||||
|
||||
var sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
|
||||
|
||||
const result_handle = w.OpenFile(sub_path_w.span(), .{
|
||||
.access_mask = w.FILE_READ_ATTRIBUTES | w.SYNCHRONIZE,
|
||||
.dir = dir,
|
||||
.creation = w.FILE_OPEN,
|
||||
.follow_symlinks = false,
|
||||
.filter = .any,
|
||||
}) catch |err| switch (err) {
|
||||
error.IsDir, error.NotDir => return error.Unexpected, // filter = .any
|
||||
error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN
|
||||
error.WouldBlock => return error.Unexpected,
|
||||
error.NoDevice => return error.FileNotFound,
|
||||
error.PipeBusy => return error.AccessDenied,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer w.CloseHandle(result_handle);
|
||||
|
||||
var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(w.REPARSE_DATA_BUFFER)) = undefined;
|
||||
_ = w.DeviceIoControl(result_handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) {
|
||||
error.AccessDenied => return error.Unexpected,
|
||||
error.UnrecognizedVolume => return error.Unexpected,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
const reparse_struct: *const w.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
|
||||
const wide_result = switch (reparse_struct.ReparseTag) {
|
||||
w.IO_REPARSE_TAG_SYMLINK => r: {
|
||||
const buf: *const w.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset >> 1;
|
||||
const len = buf.SubstituteNameLength >> 1;
|
||||
const path_buf: [*]const u16 = &buf.PathBuffer;
|
||||
const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0;
|
||||
break :r try w.parseReadLinkPath(path_buf[offset..][0..len], is_relative, buffer);
|
||||
},
|
||||
w.IO_REPARSE_TAG_MOUNT_POINT => r: {
|
||||
const buf: *const w.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset >> 1;
|
||||
const len = buf.SubstituteNameLength >> 1;
|
||||
const path_buf: [*]const u16 = &buf.PathBuffer;
|
||||
break :r try w.parseReadLinkPath(path_buf[offset..][0..len], false, buffer);
|
||||
},
|
||||
else => return error.UnsupportedReparsePointType,
|
||||
};
|
||||
|
||||
const len = std.unicode.calcWtf8Len(wide_result);
|
||||
if (len > buffer.len) return error.NameTooLong;
|
||||
|
||||
return std.unicode.wtf16LeToWtf8(buffer, wide_result);
|
||||
}
|
||||
|
||||
fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
|
||||
if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer);
|
||||
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var n: usize = undefined;
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return buffer[0..n];
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.NotLink,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
var sub_path_buffer: [posix.PATH_MAX]u8 = undefined;
|
||||
const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
const len: usize = @bitCast(rc);
|
||||
return len;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => return error.NotLink,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirSetMode = switch (native_os) {
|
||||
.windows => dirSetModeUnsupported,
|
||||
else => dirSetModePosix,
|
||||
};
|
||||
|
||||
fn dirSetModeUnsupported(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void {
|
||||
_ = userdata;
|
||||
_ = dir;
|
||||
_ = new_mode;
|
||||
return error.Unexpected;
|
||||
}
|
||||
|
||||
fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.fchmod(dir.handle, new_mode))) {
|
||||
.SUCCESS => return current_thread.endSyscall(),
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => |err| return errnoBug(err),
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirSetOwner = switch (native_os) {
|
||||
.windows => dirSetOwnerUnsupported,
|
||||
else => dirSetOwnerPosix,
|
||||
};
|
||||
|
||||
fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
|
||||
_ = userdata;
|
||||
_ = dir;
|
||||
_ = owner;
|
||||
_ = group;
|
||||
return error.Unexpected;
|
||||
}
|
||||
|
||||
fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
const uid = owner orelse ~@as(posix.uid_t, 0);
|
||||
const gid = group orelse ~@as(posix.gid_t, 0);
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.fchown(dir.handle, uid, gid))) {
|
||||
.SUCCESS => return current_thread.endSyscall(),
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Io.Dir.OpenOptions.iterate`
|
||||
.FAULT => |err| return errnoBug(err),
|
||||
.INVAL => |err| return errnoBug(err),
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dirSetPermissions = switch (native_os) {
|
||||
.windows => dirSetPermissionsWindows,
|
||||
else => dirSetPermissionsPosix,
|
||||
};
|
||||
|
||||
fn dirSetPermissionsWindows(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Io.Dir,
|
||||
permissions: Io.Dir.Permissions,
|
||||
) Io.Dir.SetPermissionsError!void {
|
||||
_ = userdata;
|
||||
_ = dir;
|
||||
_ = permissions;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
fn dirSetPermissionsPosix(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Io.Dir,
|
||||
permissions: Io.Dir.Permissions,
|
||||
) Io.Dir.SetPermissionsError!void {
|
||||
_ = userdata;
|
||||
_ = dir;
|
||||
_ = permissions;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
fn dirOpenDirWasi(
|
||||
userdata: ?*anyopaque,
|
||||
dir: Io.Dir,
|
||||
@@ -3445,10 +4794,100 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8,
|
||||
|
||||
fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
_ = t;
|
||||
_ = file;
|
||||
_ = offset;
|
||||
@panic("TODO implement fileSeekBy");
|
||||
const current_thread = Thread.getCurrent(t);
|
||||
const fd = file.handle;
|
||||
|
||||
if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
|
||||
var result: u64 = undefined;
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.CUR))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.INVAL => return error.Unseekable,
|
||||
.OVERFLOW => return error.Unseekable,
|
||||
.SPIPE => return error.Unseekable,
|
||||
.NXIO => return error.Unseekable,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (native_os == .windows) {
|
||||
try current_thread.checkCancel();
|
||||
return windows.SetFilePointerEx_CURRENT(fd, offset);
|
||||
}
|
||||
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
var new_offset: std.os.wasi.filesize_t = undefined;
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.INVAL => return error.Unseekable,
|
||||
.OVERFLOW => return error.Unseekable,
|
||||
.SPIPE => return error.Unseekable,
|
||||
.NXIO => return error.Unseekable,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (posix.SEEK == void) return error.Unseekable;
|
||||
|
||||
try current_thread.beginSyscall();
|
||||
while (true) {
|
||||
switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) {
|
||||
.SUCCESS => {
|
||||
current_thread.endSyscall();
|
||||
return;
|
||||
},
|
||||
.INTR => {
|
||||
try current_thread.checkCancel();
|
||||
continue;
|
||||
},
|
||||
.CANCELED => return current_thread.endSyscallCanceled(),
|
||||
else => |e| {
|
||||
current_thread.endSyscall();
|
||||
switch (e) {
|
||||
.BADF => |err| return errnoBug(err), // File descriptor used after closed.
|
||||
.INVAL => return error.Unseekable,
|
||||
.OVERFLOW => return error.Unseekable,
|
||||
.SPIPE => return error.Unseekable,
|
||||
.NXIO => return error.Unseekable,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
|
||||
|
||||
+46
-34
@@ -1104,7 +1104,14 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
|
||||
fn printSourceAtAddress(
|
||||
gpa: Allocator,
|
||||
io: Io,
|
||||
debug_info: *SelfInfo,
|
||||
writer: *Writer,
|
||||
address: usize,
|
||||
tty_config: tty.Config,
|
||||
) Writer.Error!void {
|
||||
const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
|
||||
error.MissingDebugInfo,
|
||||
error.UnsupportedDebugInfo,
|
||||
@@ -1125,6 +1132,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *
|
||||
};
|
||||
defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
|
||||
return printLineInfo(
|
||||
io,
|
||||
writer,
|
||||
symbol.source_location,
|
||||
address,
|
||||
@@ -1134,6 +1142,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *
|
||||
);
|
||||
}
|
||||
fn printLineInfo(
|
||||
io: Io,
|
||||
writer: *Writer,
|
||||
source_location: ?SourceLocation,
|
||||
address: usize,
|
||||
@@ -1159,7 +1168,7 @@ fn printLineInfo(
|
||||
|
||||
// Show the matching source code line if possible
|
||||
if (source_location) |sl| {
|
||||
if (printLineFromFile(writer, sl)) {
|
||||
if (printLineFromFile(io, writer, sl)) {
|
||||
if (sl.column > 0) {
|
||||
// The caret already takes one char
|
||||
const space_needed = @as(usize, @intCast(sl.column - 1));
|
||||
@@ -1177,16 +1186,17 @@ fn printLineInfo(
|
||||
}
|
||||
}
|
||||
}
|
||||
fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
|
||||
fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void {
|
||||
// Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.
|
||||
if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
|
||||
return root.debug.printLineFromFile(writer, source_location);
|
||||
return root.debug.printLineFromFile(io, writer, source_location);
|
||||
}
|
||||
|
||||
// Need this to always block even in async I/O mode, because this could potentially
|
||||
// be called from e.g. the event loop code crashing.
|
||||
var f = try fs.cwd().openFile(source_location.file_name, .{});
|
||||
defer f.close();
|
||||
const cwd: Io.Dir = .cwd();
|
||||
var f = try cwd.openFile(io, source_location.file_name, .{});
|
||||
defer f.close(io);
|
||||
// TODO fstat and make sure that the file has the correct size
|
||||
|
||||
var buf: [4096]u8 = undefined;
|
||||
@@ -1237,11 +1247,13 @@ fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
|
||||
}
|
||||
|
||||
test printLineFromFile {
|
||||
var aw: Writer.Allocating = .init(std.testing.allocator);
|
||||
const io = std.testing.io;
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var aw: Writer.Allocating = .init(gpa);
|
||||
defer aw.deinit();
|
||||
const output_stream = &aw.writer;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
const join = std.fs.path.join;
|
||||
const expectError = std.testing.expectError;
|
||||
const expectEqualStrings = std.testing.expectEqualStrings;
|
||||
@@ -1249,24 +1261,24 @@ test printLineFromFile {
|
||||
var test_dir = std.testing.tmpDir(.{});
|
||||
defer test_dir.cleanup();
|
||||
// Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
|
||||
const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
|
||||
defer allocator.free(test_dir_path);
|
||||
const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
|
||||
defer gpa.free(test_dir_path);
|
||||
|
||||
// Cases
|
||||
{
|
||||
const path = try join(allocator, &.{ test_dir_path, "one_line.zig" });
|
||||
defer allocator.free(path);
|
||||
const path = try join(gpa, &.{ test_dir_path, "one_line.zig" });
|
||||
defer gpa.free(path);
|
||||
try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
|
||||
|
||||
try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
|
||||
try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });
|
||||
defer allocator.free(path);
|
||||
const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" });
|
||||
defer gpa.free(path);
|
||||
try test_dir.dir.writeFile(.{
|
||||
.sub_path = "three_lines.zig",
|
||||
.data =
|
||||
@@ -1276,19 +1288,19 @@ test printLineFromFile {
|
||||
,
|
||||
});
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings("1\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 });
|
||||
try expectEqualStrings("3\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});
|
||||
defer file.close();
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
|
||||
defer allocator.free(path);
|
||||
const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
|
||||
defer gpa.free(path);
|
||||
|
||||
const overlap = 10;
|
||||
var buf: [16]u8 = undefined;
|
||||
@@ -1299,55 +1311,55 @@ test printLineFromFile {
|
||||
try writer.splatByteAll('a', overlap);
|
||||
try writer.flush();
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
|
||||
try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});
|
||||
defer file.close();
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
|
||||
defer allocator.free(path);
|
||||
const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
|
||||
defer gpa.free(path);
|
||||
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
try writer.splatByteAll('a', std.heap.page_size_max);
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});
|
||||
defer file.close();
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
|
||||
defer allocator.free(path);
|
||||
const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
|
||||
defer gpa.free(path);
|
||||
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
try writer.splatByteAll('a', 3 * std.heap.page_size_max);
|
||||
|
||||
try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
|
||||
try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try writer.writeAll("a\na");
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
|
||||
try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
|
||||
try expectEqualStrings("a\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
{
|
||||
const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});
|
||||
defer file.close();
|
||||
const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });
|
||||
defer allocator.free(path);
|
||||
const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" });
|
||||
defer gpa.free(path);
|
||||
|
||||
var file_writer = file.writer(&.{});
|
||||
const writer = &file_writer.interface;
|
||||
@@ -1355,11 +1367,11 @@ test printLineFromFile {
|
||||
try writer.splatByteAll('\n', real_file_start);
|
||||
try writer.writeAll("abc\ndef");
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
|
||||
try expectEqualStrings("abc\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
|
||||
try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
|
||||
try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
|
||||
try expectEqualStrings("def\n", aw.written());
|
||||
aw.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
+14
-55
@@ -15,9 +15,13 @@ 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");
|
||||
/// Deprecated.
|
||||
pub const AtomicFile = std.Io.File.Atomic;
|
||||
/// Deprecated.
|
||||
pub const Dir = std.Io.Dir;
|
||||
/// Deprecated.
|
||||
pub const File = std.Io.File;
|
||||
|
||||
pub const path = @import("fs/path.zig");
|
||||
|
||||
pub const has_executable_bit = switch (native_os) {
|
||||
@@ -153,42 +157,9 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
|
||||
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 cwd() Io.Dir {
|
||||
return .cwd();
|
||||
}
|
||||
|
||||
pub fn defaultWasiCwd() std.os.wasi.fd_t {
|
||||
@@ -209,23 +180,11 @@ pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.O
|
||||
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);
|
||||
/// Deprecated in favor of `Io.File.openAbsolute`.
|
||||
pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Io.File.OpenError!Io.File {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.File.openAbsolute(io, absolute_path, flags);
|
||||
}
|
||||
|
||||
/// Test accessing `path`.
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
const AtomicFile = @This();
|
||||
const std = @import("../std.zig");
|
||||
const File = std.fs.File;
|
||||
const Dir = std.fs.Dir;
|
||||
const fs = std.fs;
|
||||
const assert = std.debug.assert;
|
||||
const posix = std.posix;
|
||||
|
||||
file_writer: File.Writer,
|
||||
random_integer: u64,
|
||||
dest_basename: []const u8,
|
||||
file_open: bool,
|
||||
file_exists: bool,
|
||||
close_dir_on_deinit: bool,
|
||||
dir: Dir,
|
||||
|
||||
pub const InitError = File.OpenError;
|
||||
|
||||
/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
|
||||
pub fn init(
|
||||
dest_basename: []const u8,
|
||||
mode: File.Mode,
|
||||
dir: Dir,
|
||||
close_dir_on_deinit: bool,
|
||||
write_buffer: []u8,
|
||||
) InitError!AtomicFile {
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
const tmp_sub_path = std.fmt.hex(random_integer);
|
||||
const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.file_writer = file.writer(write_buffer),
|
||||
.random_integer = random_integer,
|
||||
.dest_basename = dest_basename,
|
||||
.file_open = true,
|
||||
.file_exists = true,
|
||||
.close_dir_on_deinit = close_dir_on_deinit,
|
||||
.dir = dir,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Always call deinit, even after a successful finish().
|
||||
pub fn deinit(af: *AtomicFile) void {
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close();
|
||||
af.file_open = false;
|
||||
}
|
||||
if (af.file_exists) {
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
af.dir.deleteFile(&tmp_sub_path) catch {};
|
||||
af.file_exists = false;
|
||||
}
|
||||
if (af.close_dir_on_deinit) {
|
||||
af.dir.close();
|
||||
}
|
||||
af.* = undefined;
|
||||
}
|
||||
|
||||
pub const FlushError = File.WriteError;
|
||||
|
||||
pub fn flush(af: *AtomicFile) FlushError!void {
|
||||
af.file_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => return af.file_writer.err.?,
|
||||
};
|
||||
}
|
||||
|
||||
pub const RenameIntoPlaceError = posix.RenameError;
|
||||
|
||||
/// On Windows, this function introduces a period of time where some file
|
||||
/// system operations on the destination file will result in
|
||||
/// `error.AccessDenied`, including rename operations (such as the one used in
|
||||
/// this function).
|
||||
pub fn renameIntoPlace(af: *AtomicFile) RenameIntoPlaceError!void {
|
||||
assert(af.file_exists);
|
||||
if (af.file_open) {
|
||||
af.file_writer.file.close();
|
||||
af.file_open = false;
|
||||
}
|
||||
const tmp_sub_path = std.fmt.hex(af.random_integer);
|
||||
try posix.renameat(af.dir.fd, &tmp_sub_path, af.dir.fd, af.dest_basename);
|
||||
af.file_exists = false;
|
||||
}
|
||||
|
||||
pub const FinishError = FlushError || RenameIntoPlaceError;
|
||||
|
||||
/// Combination of `flush` followed by `renameIntoPlace`.
|
||||
pub fn finish(af: *AtomicFile) FinishError!void {
|
||||
try af.flush();
|
||||
try af.renameIntoPlace();
|
||||
}
|
||||
-2066
@@ -1,2066 +0,0 @@
|
||||
//! Deprecated in favor of `Io.Dir`.
|
||||
const Dir = @This();
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_os = builtin.os.tag;
|
||||
|
||||
const std = @import("../std.zig");
|
||||
const Io = std.Io;
|
||||
const File = std.fs.File;
|
||||
const AtomicFile = std.fs.AtomicFile;
|
||||
const base64_encoder = fs.base64_encoder;
|
||||
const posix = std.posix;
|
||||
const mem = std.mem;
|
||||
const path = fs.path;
|
||||
const fs = std.fs;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const linux = std.os.linux;
|
||||
const windows = std.os.windows;
|
||||
const have_flock = @TypeOf(posix.system.flock) != void;
|
||||
|
||||
fd: Handle,
|
||||
|
||||
pub const Handle = posix.fd_t;
|
||||
|
||||
pub const default_mode = 0o755;
|
||||
|
||||
pub const Entry = struct {
|
||||
name: []const u8,
|
||||
kind: Kind,
|
||||
|
||||
pub const Kind = File.Kind;
|
||||
};
|
||||
|
||||
const IteratorError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
SystemResources,
|
||||
} || posix.UnexpectedError;
|
||||
|
||||
pub const Iterator = switch (native_os) {
|
||||
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => struct {
|
||||
dir: Dir,
|
||||
seek: i64,
|
||||
buf: [1024]u8 align(@alignOf(posix.system.dirent)),
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
first_iter: bool,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = IteratorError;
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
switch (native_os) {
|
||||
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => return self.nextDarwin(),
|
||||
.freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
|
||||
.illumos => return self.nextIllumos(),
|
||||
else => @compileError("unimplemented"),
|
||||
}
|
||||
}
|
||||
|
||||
fn nextDarwin(self: *Self) !?Entry {
|
||||
start_over: while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.first_iter) {
|
||||
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
|
||||
self.first_iter = false;
|
||||
}
|
||||
const rc = posix.system.getdirentries(
|
||||
self.dir.fd,
|
||||
&self.buf,
|
||||
self.buf.len,
|
||||
&self.seek,
|
||||
);
|
||||
if (rc == 0) return null;
|
||||
if (rc < 0) {
|
||||
switch (posix.errno(rc)) {
|
||||
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
self.index = 0;
|
||||
self.end_index = @as(usize, @intCast(rc));
|
||||
}
|
||||
const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
|
||||
const next_index = self.index + darwin_entry.reclen;
|
||||
self.index = next_index;
|
||||
|
||||
const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
|
||||
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) {
|
||||
continue :start_over;
|
||||
}
|
||||
|
||||
const entry_kind: Entry.Kind = switch (darwin_entry.type) {
|
||||
posix.DT.BLK => .block_device,
|
||||
posix.DT.CHR => .character_device,
|
||||
posix.DT.DIR => .directory,
|
||||
posix.DT.FIFO => .named_pipe,
|
||||
posix.DT.LNK => .sym_link,
|
||||
posix.DT.REG => .file,
|
||||
posix.DT.SOCK => .unix_domain_socket,
|
||||
posix.DT.WHT => .whiteout,
|
||||
else => .unknown,
|
||||
};
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn nextIllumos(self: *Self) !?Entry {
|
||||
start_over: while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.first_iter) {
|
||||
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
|
||||
self.first_iter = false;
|
||||
}
|
||||
const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
if (rc == 0) return null;
|
||||
self.index = 0;
|
||||
self.end_index = @as(usize, @intCast(rc));
|
||||
}
|
||||
const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
|
||||
const next_index = self.index + entry.reclen;
|
||||
self.index = next_index;
|
||||
|
||||
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
|
||||
continue :start_over;
|
||||
|
||||
// illumos dirent doesn't expose type, so we have to call stat to get it.
|
||||
const stat_info = posix.fstatat(
|
||||
self.dir.fd,
|
||||
name,
|
||||
posix.AT.SYMLINK_NOFOLLOW,
|
||||
) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.SymLinkLoop => unreachable,
|
||||
error.FileNotFound => unreachable, // lost the race
|
||||
else => |e| return e,
|
||||
};
|
||||
const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
|
||||
posix.S.IFIFO => .named_pipe,
|
||||
posix.S.IFCHR => .character_device,
|
||||
posix.S.IFDIR => .directory,
|
||||
posix.S.IFBLK => .block_device,
|
||||
posix.S.IFREG => .file,
|
||||
posix.S.IFLNK => .sym_link,
|
||||
posix.S.IFSOCK => .unix_domain_socket,
|
||||
posix.S.IFDOOR => .door,
|
||||
posix.S.IFPORT => .event_port,
|
||||
else => .unknown,
|
||||
};
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn nextBsd(self: *Self) !?Entry {
|
||||
start_over: while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.first_iter) {
|
||||
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
|
||||
self.first_iter = false;
|
||||
}
|
||||
const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
|
||||
switch (posix.errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
// Introduced in freebsd 13.2: directory unlinked but still open.
|
||||
// To be consistent, iteration ends if the directory being iterated is deleted during iteration.
|
||||
.NOENT => return null,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
if (rc == 0) return null;
|
||||
self.index = 0;
|
||||
self.end_index = @as(usize, @intCast(rc));
|
||||
}
|
||||
const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
|
||||
const next_index = self.index +
|
||||
if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
|
||||
self.index = next_index;
|
||||
|
||||
const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
|
||||
|
||||
const skip_zero_fileno = switch (native_os) {
|
||||
// fileno=0 is used to mark invalid entries or deleted files.
|
||||
.openbsd, .netbsd => true,
|
||||
else => false,
|
||||
};
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
|
||||
(skip_zero_fileno and bsd_entry.fileno == 0))
|
||||
{
|
||||
continue :start_over;
|
||||
}
|
||||
|
||||
const entry_kind: Entry.Kind = switch (bsd_entry.type) {
|
||||
posix.DT.BLK => .block_device,
|
||||
posix.DT.CHR => .character_device,
|
||||
posix.DT.DIR => .directory,
|
||||
posix.DT.FIFO => .named_pipe,
|
||||
posix.DT.LNK => .sym_link,
|
||||
posix.DT.REG => .file,
|
||||
posix.DT.SOCK => .unix_domain_socket,
|
||||
posix.DT.WHT => .whiteout,
|
||||
else => .unknown,
|
||||
};
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.index = 0;
|
||||
self.end_index = 0;
|
||||
self.first_iter = true;
|
||||
}
|
||||
},
|
||||
.haiku => struct {
|
||||
dir: Dir,
|
||||
buf: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)),
|
||||
offset: usize,
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
first_iter: bool,
|
||||
|
||||
const Self = @This();
|
||||
const DirEnt = posix.system.DirEnt;
|
||||
|
||||
pub const Error = IteratorError;
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.first_iter) {
|
||||
switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) {
|
||||
.SUCCESS => {},
|
||||
.BADF => unreachable, // Dir is invalid
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
self.first_iter = false;
|
||||
}
|
||||
const rc = posix.system._kern_read_dir(
|
||||
self.dir.fd,
|
||||
&self.buf,
|
||||
self.buf.len,
|
||||
self.buf.len / @sizeOf(DirEnt),
|
||||
);
|
||||
if (rc == 0) return null;
|
||||
if (rc < 0) {
|
||||
switch (@as(posix.E, @enumFromInt(rc))) {
|
||||
.BADF => unreachable, // Dir is invalid
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.OVERFLOW => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
self.offset = 0;
|
||||
self.index = 0;
|
||||
self.end_index = @intCast(rc);
|
||||
}
|
||||
const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset]));
|
||||
self.offset += dirent.reclen;
|
||||
self.index += 1;
|
||||
const name = mem.span(dirent.getName());
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue;
|
||||
|
||||
var stat_info: posix.Stat = undefined;
|
||||
switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat(
|
||||
self.dir.fd,
|
||||
name,
|
||||
false,
|
||||
&stat_info,
|
||||
@sizeOf(posix.Stat),
|
||||
)))) {
|
||||
.SUCCESS => {},
|
||||
.INVAL => unreachable,
|
||||
.BADF => unreachable, // Dir is invalid
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.FAULT => unreachable,
|
||||
.NAMETOOLONG => unreachable,
|
||||
.LOOP => unreachable,
|
||||
.NOENT => continue,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
const statmode = stat_info.mode & posix.S.IFMT;
|
||||
|
||||
const entry_kind: Entry.Kind = switch (statmode) {
|
||||
posix.S.IFDIR => .directory,
|
||||
posix.S.IFBLK => .block_device,
|
||||
posix.S.IFCHR => .character_device,
|
||||
posix.S.IFLNK => .sym_link,
|
||||
posix.S.IFREG => .file,
|
||||
posix.S.IFIFO => .named_pipe,
|
||||
else => .unknown,
|
||||
};
|
||||
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.index = 0;
|
||||
self.end_index = 0;
|
||||
self.first_iter = true;
|
||||
}
|
||||
},
|
||||
.linux => struct {
|
||||
dir: Dir,
|
||||
buf: [1024]u8 align(@alignOf(linux.dirent64)),
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
first_iter: bool,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = IteratorError;
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
return self.nextLinux() catch |err| switch (err) {
|
||||
// To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
|
||||
// This matches the behavior of non-Linux UNIX platforms.
|
||||
error.DirNotFound => null,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ErrorLinux = error{DirNotFound} || IteratorError;
|
||||
|
||||
/// Implementation of `next` that can return `error.DirNotFound` if the directory being
|
||||
/// iterated was deleted during iteration (this error is Linux specific).
|
||||
pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
|
||||
start_over: while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
if (self.first_iter) {
|
||||
posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
|
||||
self.first_iter = false;
|
||||
}
|
||||
const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
|
||||
switch (linux.errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
|
||||
.INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
|
||||
.ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
if (rc == 0) return null;
|
||||
self.index = 0;
|
||||
self.end_index = rc;
|
||||
}
|
||||
const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
|
||||
const next_index = self.index + linux_entry.reclen;
|
||||
self.index = next_index;
|
||||
|
||||
const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
|
||||
|
||||
// skip . and .. entries
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
||||
continue :start_over;
|
||||
}
|
||||
|
||||
const entry_kind: Entry.Kind = switch (linux_entry.type) {
|
||||
linux.DT.BLK => .block_device,
|
||||
linux.DT.CHR => .character_device,
|
||||
linux.DT.DIR => .directory,
|
||||
linux.DT.FIFO => .named_pipe,
|
||||
linux.DT.LNK => .sym_link,
|
||||
linux.DT.REG => .file,
|
||||
linux.DT.SOCK => .unix_domain_socket,
|
||||
else => .unknown,
|
||||
};
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.index = 0;
|
||||
self.end_index = 0;
|
||||
self.first_iter = true;
|
||||
}
|
||||
},
|
||||
.windows => struct {
|
||||
dir: Dir,
|
||||
buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)),
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
first_iter: bool,
|
||||
name_data: [fs.max_name_bytes]u8,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = IteratorError;
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
const w = windows;
|
||||
while (true) {
|
||||
if (self.index >= self.end_index) {
|
||||
var io: w.IO_STATUS_BLOCK = undefined;
|
||||
const rc = w.ntdll.NtQueryDirectoryFile(
|
||||
self.dir.fd,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
&io,
|
||||
&self.buf,
|
||||
self.buf.len,
|
||||
.BothDirectory,
|
||||
w.FALSE,
|
||||
null,
|
||||
@intFromBool(self.first_iter),
|
||||
);
|
||||
self.first_iter = false;
|
||||
if (io.Information == 0) return null;
|
||||
self.index = 0;
|
||||
self.end_index = io.Information;
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
|
||||
|
||||
else => return w.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
// While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
|
||||
// this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
|
||||
const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
|
||||
if (dir_info.NextEntryOffset != 0) {
|
||||
self.index += dir_info.NextEntryOffset;
|
||||
} else {
|
||||
self.index = self.buf.len;
|
||||
}
|
||||
|
||||
const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
|
||||
|
||||
if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
|
||||
continue;
|
||||
const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
|
||||
const name_wtf8 = self.name_data[0..name_wtf8_len];
|
||||
const kind: Entry.Kind = blk: {
|
||||
const attrs = dir_info.FileAttributes;
|
||||
if (attrs.DIRECTORY) break :blk .directory;
|
||||
if (attrs.REPARSE_POINT) break :blk .sym_link;
|
||||
break :blk .file;
|
||||
};
|
||||
return Entry{
|
||||
.name = name_wtf8,
|
||||
.kind = kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.index = 0;
|
||||
self.end_index = 0;
|
||||
self.first_iter = true;
|
||||
}
|
||||
},
|
||||
.wasi => struct {
|
||||
dir: Dir,
|
||||
buf: [1024]u8 align(@alignOf(std.os.wasi.dirent_t)),
|
||||
cookie: u64,
|
||||
index: usize,
|
||||
end_index: usize,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = IteratorError;
|
||||
|
||||
/// Memory such as file names referenced in this returned entry becomes invalid
|
||||
/// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
|
||||
pub fn next(self: *Self) Error!?Entry {
|
||||
return self.nextWasi() catch |err| switch (err) {
|
||||
// To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
|
||||
// This matches the behavior of non-Linux UNIX platforms.
|
||||
error.DirNotFound => null,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ErrorWasi = error{DirNotFound} || IteratorError;
|
||||
|
||||
/// Implementation of `next` that can return platform-dependent errors depending on the host platform.
|
||||
/// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
|
||||
/// iterated was deleted during iteration.
|
||||
pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
|
||||
// We intentinally use fd_readdir even when linked with libc,
|
||||
// since its implementation is exactly the same as below,
|
||||
// and we avoid the code complexity here.
|
||||
const w = std.os.wasi;
|
||||
start_over: while (true) {
|
||||
// According to the WASI spec, the last entry might be truncated,
|
||||
// so we need to check if the left buffer contains the whole dirent.
|
||||
if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
|
||||
var bufused: usize = undefined;
|
||||
switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
|
||||
.SUCCESS => {},
|
||||
.BADF => unreachable, // Dir is invalid or was opened without iteration ability
|
||||
.FAULT => unreachable,
|
||||
.NOTDIR => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
if (bufused == 0) return null;
|
||||
self.index = 0;
|
||||
self.end_index = bufused;
|
||||
}
|
||||
const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
|
||||
const entry_size = @sizeOf(w.dirent_t);
|
||||
const name_index = self.index + entry_size;
|
||||
if (name_index + entry.namlen > self.end_index) {
|
||||
// This case, the name is truncated, so we need to call readdir to store the entire name.
|
||||
self.end_index = self.index; // Force fd_readdir in the next loop.
|
||||
continue :start_over;
|
||||
}
|
||||
const name = self.buf[name_index .. name_index + entry.namlen];
|
||||
|
||||
const next_index = name_index + entry.namlen;
|
||||
self.index = next_index;
|
||||
self.cookie = entry.next;
|
||||
|
||||
// skip . and .. entries
|
||||
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
|
||||
continue :start_over;
|
||||
}
|
||||
|
||||
const entry_kind: Entry.Kind = switch (entry.type) {
|
||||
.BLOCK_DEVICE => .block_device,
|
||||
.CHARACTER_DEVICE => .character_device,
|
||||
.DIRECTORY => .directory,
|
||||
.SYMBOLIC_LINK => .sym_link,
|
||||
.REGULAR_FILE => .file,
|
||||
.SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
|
||||
else => .unknown,
|
||||
};
|
||||
return Entry{
|
||||
.name = name,
|
||||
.kind = entry_kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.index = 0;
|
||||
self.end_index = 0;
|
||||
self.cookie = std.os.wasi.DIRCOOKIE_START;
|
||||
}
|
||||
},
|
||||
else => @compileError("unimplemented"),
|
||||
};
|
||||
|
||||
pub fn iterate(self: Dir) Iterator {
|
||||
return self.iterateImpl(true);
|
||||
}
|
||||
|
||||
/// Like `iterate`, but will not reset the directory cursor before the first
|
||||
/// iteration. This should only be used in cases where it is known that the
|
||||
/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
|
||||
pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
|
||||
return self.iterateImpl(false);
|
||||
}
|
||||
|
||||
fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
|
||||
switch (native_os) {
|
||||
.driverkit,
|
||||
.ios,
|
||||
.maccatalyst,
|
||||
.macos,
|
||||
.tvos,
|
||||
.visionos,
|
||||
.watchos,
|
||||
.freebsd,
|
||||
.netbsd,
|
||||
.dragonfly,
|
||||
.openbsd,
|
||||
.illumos,
|
||||
=> return Iterator{
|
||||
.dir = self,
|
||||
.seek = 0,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.buf = undefined,
|
||||
.first_iter = first_iter_start_value,
|
||||
},
|
||||
.linux => return Iterator{
|
||||
.dir = self,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.buf = undefined,
|
||||
.first_iter = first_iter_start_value,
|
||||
},
|
||||
.haiku => return Iterator{
|
||||
.dir = self,
|
||||
.offset = 0,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.buf = undefined,
|
||||
.first_iter = first_iter_start_value,
|
||||
},
|
||||
.windows => return Iterator{
|
||||
.dir = self,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.first_iter = first_iter_start_value,
|
||||
.buf = undefined,
|
||||
.name_data = undefined,
|
||||
},
|
||||
.wasi => return Iterator{
|
||||
.dir = self,
|
||||
.cookie = std.os.wasi.DIRCOOKIE_START,
|
||||
.index = 0,
|
||||
.end_index = 0,
|
||||
.buf = undefined,
|
||||
},
|
||||
else => @compileError("unimplemented"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const SelectiveWalker = struct {
|
||||
stack: std.ArrayList(Walker.StackItem),
|
||||
name_buffer: std.ArrayList(u8),
|
||||
allocator: Allocator,
|
||||
|
||||
pub const Error = IteratorError || Allocator.Error;
|
||||
|
||||
/// After each call to this function, and on deinit(), the memory returned
|
||||
/// from this function becomes invalid. A copy must be made in order to keep
|
||||
/// a reference to the path.
|
||||
pub fn next(self: *SelectiveWalker) Error!?Walker.Entry {
|
||||
while (self.stack.items.len > 0) {
|
||||
const top = &self.stack.items[self.stack.items.len - 1];
|
||||
var dirname_len = top.dirname_len;
|
||||
if (top.iter.next() catch |err| {
|
||||
// If we get an error, then we want the user to be able to continue
|
||||
// walking if they want, which means that we need to pop the directory
|
||||
// that errored from the stack. Otherwise, all future `next` calls would
|
||||
// likely just fail with the same error.
|
||||
var item = self.stack.pop().?;
|
||||
if (self.stack.items.len != 0) {
|
||||
item.iter.dir.close();
|
||||
}
|
||||
return err;
|
||||
}) |entry| {
|
||||
self.name_buffer.shrinkRetainingCapacity(dirname_len);
|
||||
if (self.name_buffer.items.len != 0) {
|
||||
try self.name_buffer.append(self.allocator, fs.path.sep);
|
||||
dirname_len += 1;
|
||||
}
|
||||
try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
|
||||
self.name_buffer.appendSliceAssumeCapacity(entry.name);
|
||||
self.name_buffer.appendAssumeCapacity(0);
|
||||
const walker_entry: Walker.Entry = .{
|
||||
.dir = top.iter.dir,
|
||||
.basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
|
||||
.path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
|
||||
.kind = entry.kind,
|
||||
};
|
||||
return walker_entry;
|
||||
} else {
|
||||
var item = self.stack.pop().?;
|
||||
if (self.stack.items.len != 0) {
|
||||
item.iter.dir.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Traverses into the directory, continuing walking one level down.
|
||||
pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void {
|
||||
if (entry.kind != .directory) {
|
||||
@branchHint(.cold);
|
||||
return;
|
||||
}
|
||||
|
||||
var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| {
|
||||
switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
else => |e| return e,
|
||||
}
|
||||
};
|
||||
errdefer new_dir.close();
|
||||
|
||||
try self.stack.append(self.allocator, .{
|
||||
.iter = new_dir.iterateAssumeFirstIteration(),
|
||||
.dirname_len = self.name_buffer.items.len - 1,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SelectiveWalker) void {
|
||||
self.name_buffer.deinit(self.allocator);
|
||||
self.stack.deinit(self.allocator);
|
||||
}
|
||||
|
||||
/// Leaves the current directory, continuing walking one level up.
|
||||
/// If the current entry is a directory entry, then the "current directory"
|
||||
/// will pertain to that entry if `enter` is called before `leave`.
|
||||
pub fn leave(self: *SelectiveWalker) void {
|
||||
var item = self.stack.pop().?;
|
||||
if (self.stack.items.len != 0) {
|
||||
@branchHint(.likely);
|
||||
item.iter.dir.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Recursively iterates over a directory, but requires the user to
|
||||
/// opt-in to recursing into each directory entry.
|
||||
///
|
||||
/// `self` must have been opened with `OpenOptions{.iterate = true}`.
|
||||
///
|
||||
/// `Walker.deinit` releases allocated memory and directory handles.
|
||||
///
|
||||
/// The order of returned file system entries is undefined.
|
||||
///
|
||||
/// `self` will not be closed after walking it.
|
||||
///
|
||||
/// See also `walk`.
|
||||
pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker {
|
||||
var stack: std.ArrayList(Walker.StackItem) = .empty;
|
||||
|
||||
try stack.append(allocator, .{
|
||||
.iter = self.iterate(),
|
||||
.dirname_len = 0,
|
||||
});
|
||||
|
||||
return .{
|
||||
.stack = stack,
|
||||
.name_buffer = .{},
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Walker = struct {
|
||||
inner: SelectiveWalker,
|
||||
|
||||
pub const Entry = struct {
|
||||
/// The containing directory. This can be used to operate directly on `basename`
|
||||
/// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
|
||||
/// The directory remains open until `next` or `deinit` is called.
|
||||
dir: Dir,
|
||||
basename: [:0]const u8,
|
||||
path: [:0]const u8,
|
||||
kind: Dir.Entry.Kind,
|
||||
|
||||
/// Returns the depth of the entry relative to the initial directory.
|
||||
/// Returns 1 for a direct child of the initial directory, 2 for an entry
|
||||
/// within a direct child of the initial directory, etc.
|
||||
pub fn depth(self: Walker.Entry) usize {
|
||||
return mem.countScalar(u8, self.path, fs.path.sep) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
const StackItem = struct {
|
||||
iter: Dir.Iterator,
|
||||
dirname_len: usize,
|
||||
};
|
||||
|
||||
/// After each call to this function, and on deinit(), the memory returned
|
||||
/// from this function becomes invalid. A copy must be made in order to keep
|
||||
/// a reference to the path.
|
||||
pub fn next(self: *Walker) !?Walker.Entry {
|
||||
const entry = try self.inner.next();
|
||||
if (entry != null and entry.?.kind == .directory) {
|
||||
try self.inner.enter(entry.?);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Walker) void {
|
||||
self.inner.deinit();
|
||||
}
|
||||
|
||||
/// Leaves the current directory, continuing walking one level up.
|
||||
/// If the current entry is a directory entry, then the "current directory"
|
||||
/// is the directory pertaining to the current entry.
|
||||
pub fn leave(self: *Walker) void {
|
||||
self.inner.leave();
|
||||
}
|
||||
};
|
||||
|
||||
/// Recursively iterates over a directory.
|
||||
///
|
||||
/// `self` must have been opened with `OpenOptions{.iterate = true}`.
|
||||
///
|
||||
/// `Walker.deinit` releases allocated memory and directory handles.
|
||||
///
|
||||
/// The order of returned file system entries is undefined.
|
||||
///
|
||||
/// `self` will not be closed after walking it.
|
||||
///
|
||||
/// See also `walkSelectively`.
|
||||
pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
|
||||
return .{
|
||||
.inner = try walkSelectively(self, allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub const OpenError = Io.Dir.OpenError;
|
||||
|
||||
pub fn close(self: *Dir) void {
|
||||
posix.close(self.fd);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.openFile`.
|
||||
pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.createFile`.
|
||||
pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
|
||||
return .adaptFromNewApi(new_file);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.MakeError`.
|
||||
pub const MakeError = Io.Dir.MakeError;
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.makeDir`.
|
||||
pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.makeDir`.
|
||||
pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
|
||||
try posix.mkdiratZ(self.fd, sub_path, default_mode);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.makeDir`.
|
||||
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
|
||||
try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.makePath`.
|
||||
pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
|
||||
_ = try self.makePathStatus(sub_path);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.MakePathStatus`.
|
||||
pub const MakePathStatus = Io.Dir.MakePathStatus;
|
||||
/// Deprecated in favor of `Io.Dir.MakePathError`.
|
||||
pub const MakePathError = Io.Dir.MakePathError;
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.makePathStatus`.
|
||||
pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.makeOpenPath`.
|
||||
pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
|
||||
}
|
||||
|
||||
pub const RealPathError = posix.RealPathError || error{Canceled};
|
||||
|
||||
/// This function returns the canonicalized absolute pathname of
|
||||
/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
|
||||
/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
|
||||
/// argument.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// 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.
|
||||
/// This function is not universally supported by all platforms.
|
||||
/// Currently supported hosts are: Linux, macOS, and Windows.
|
||||
/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
|
||||
pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 {
|
||||
if (native_os == .wasi) {
|
||||
@compileError("realpath is not available on WASI");
|
||||
}
|
||||
if (native_os == .windows) {
|
||||
var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname);
|
||||
|
||||
const wide_slice = try self.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];
|
||||
}
|
||||
const pathname_c = try posix.toPosixPath(pathname);
|
||||
return self.realpathZ(&pathname_c, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `Dir.realpath` except `pathname` is null-terminated.
|
||||
/// See also `Dir.realpath`, `realpathZ`.
|
||||
pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 {
|
||||
if (native_os == .windows) {
|
||||
var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname);
|
||||
|
||||
const wide_slice = try self.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];
|
||||
}
|
||||
|
||||
var flags: posix.O = .{};
|
||||
if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
|
||||
if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
|
||||
if (@hasField(posix.O, "PATH")) flags.PATH = true;
|
||||
|
||||
const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
|
||||
error.FileLocksNotSupported => return error.Unexpected,
|
||||
error.FileBusy => return error.Unexpected,
|
||||
error.WouldBlock => return error.Unexpected,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer posix.close(fd);
|
||||
|
||||
var buffer: [fs.max_path_bytes]u8 = undefined;
|
||||
const out_path = try std.os.getFdPath(fd, &buffer);
|
||||
|
||||
if (out_path.len > out_buffer.len) {
|
||||
return error.NameTooLong;
|
||||
}
|
||||
|
||||
const result = out_buffer[0..out_path.len];
|
||||
@memcpy(result, out_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Deprecated: use `realpathW2`.
|
||||
///
|
||||
/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
|
||||
/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// See also `Dir.realpath`, `realpathW`.
|
||||
pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
|
||||
var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
|
||||
const wide_slice = try self.realpathW2(pathname, &wide_buf);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `Dir.realpath` except
|
||||
/// * `pathname` and the result are WTF-16 LE encoded
|
||||
/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details.
|
||||
///
|
||||
/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it
|
||||
/// is safe to reuse a single buffer for both.
|
||||
///
|
||||
/// See also `Dir.realpath`, `realpathW`.
|
||||
pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 {
|
||||
const w = windows;
|
||||
|
||||
const h_file = blk: {
|
||||
const res = w.OpenFile(pathname, .{
|
||||
.dir = self.fd,
|
||||
.access_mask = .{
|
||||
.STANDARD = .{ .SYNCHRONIZE = true },
|
||||
.GENERIC = .{ .READ = true },
|
||||
},
|
||||
.creation = .OPEN,
|
||||
.filter = .any,
|
||||
}) catch |err| switch (err) {
|
||||
error.WouldBlock => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
break :blk res;
|
||||
};
|
||||
defer w.CloseHandle(h_file);
|
||||
|
||||
return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
|
||||
}
|
||||
|
||||
pub const RealPathAllocError = RealPathError || Allocator.Error;
|
||||
|
||||
/// Same as `Dir.realpath` except caller must free the returned memory.
|
||||
/// See also `Dir.realpath`.
|
||||
pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]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: [fs.max_path_bytes]u8 = undefined;
|
||||
return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
|
||||
}
|
||||
|
||||
/// Changes the current working directory to the open directory handle.
|
||||
/// This modifies global state and can have surprising effects in multi-
|
||||
/// threaded applications. Most applications and especially libraries should
|
||||
/// not call this function as a general rule, however it can have use cases
|
||||
/// in, for example, implementing a shell, or child process execution.
|
||||
/// Not all targets support this. For example, WASI does not have the concept
|
||||
/// of a current working directory.
|
||||
pub fn setAsCwd(self: Dir) !void {
|
||||
if (native_os == .wasi) {
|
||||
@compileError("changing cwd is not currently possible in WASI");
|
||||
}
|
||||
if (native_os == .windows) {
|
||||
var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
|
||||
const dir_path = try windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
|
||||
if (builtin.link_libc) {
|
||||
return posix.chdirW(dir_path);
|
||||
}
|
||||
return windows.SetCurrentDirectory(dir_path);
|
||||
}
|
||||
try posix.fchdir(self.fd);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.OpenOptions`.
|
||||
pub const OpenOptions = Io.Dir.OpenOptions;
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.openDir`.
|
||||
pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
|
||||
}
|
||||
|
||||
pub const DeleteFileError = posix.UnlinkError;
|
||||
|
||||
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
|
||||
if (native_os == .windows) {
|
||||
const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
|
||||
return self.deleteFileW(sub_path_w.span());
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
|
||||
else => |e| return e,
|
||||
};
|
||||
} else {
|
||||
const sub_path_c = try posix.toPosixPath(sub_path);
|
||||
return self.deleteFileZ(&sub_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `deleteFile` except the parameter is null-terminated.
|
||||
pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
|
||||
posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
|
||||
error.AccessDenied, error.PermissionDenied => |e| switch (native_os) {
|
||||
// non-Linux POSIX systems return permission errors when trying to delete a
|
||||
// directory, so we need to handle that case specifically and translate the error
|
||||
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
|
||||
// Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
|
||||
const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
|
||||
const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
|
||||
return if (is_dir) error.IsDir else e;
|
||||
},
|
||||
else => return e,
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
|
||||
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
|
||||
posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const DeleteDirError = error{
|
||||
DirNotEmpty,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
FileSystem,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
NotDir,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
ProcessNotFound,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
/// Returns `error.DirNotEmpty` if the directory is not empty.
|
||||
/// To delete a directory recursively, see `deleteTree`.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
|
||||
if (native_os == .windows) {
|
||||
const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
|
||||
return self.deleteDirW(sub_path_w.span());
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
|
||||
else => |e| return e,
|
||||
};
|
||||
} else {
|
||||
const sub_path_c = try posix.toPosixPath(sub_path);
|
||||
return self.deleteDirZ(&sub_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `deleteDir` except the parameter is null-terminated.
|
||||
pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
|
||||
posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
|
||||
/// This function is Windows-only.
|
||||
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
|
||||
posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const RenameError = posix.RenameError;
|
||||
|
||||
/// Change the name or location of a file or directory.
|
||||
/// If new_sub_path already exists, it will be replaced.
|
||||
/// Renaming a file over an existing directory or a directory
|
||||
/// over an existing file will fail with `error.IsDir` or `error.NotDir`
|
||||
/// 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 rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
|
||||
return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are null-terminated.
|
||||
pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
|
||||
return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
|
||||
/// This function is Windows-only.
|
||||
pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
|
||||
return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w, windows.TRUE);
|
||||
}
|
||||
|
||||
/// Use with `Dir.symLink`, `Dir.atomicSymLink`, and `symLinkAbsolute` to
|
||||
/// specify whether the symlink will point to a file or a directory. This value
|
||||
/// is ignored on all hosts except Windows where creating symlinks to different
|
||||
/// resource types, requires different flags. By default, `symLinkAbsolute` is
|
||||
/// assumed to point to a file.
|
||||
pub const SymLinkFlags = struct {
|
||||
is_directory: bool = false,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
/// 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 symLink(
|
||||
self: Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return self.symLinkWasi(target_path, sym_link_path, flags);
|
||||
}
|
||||
if (native_os == .windows) {
|
||||
// Target path does not use sliceToPrefixedFileW because certain paths
|
||||
// are handled differently when creating a symlink than they would be
|
||||
// when converting to an NT namespaced path. CreateSymbolicLink in
|
||||
// symLinkW will handle the necessary conversion.
|
||||
var target_path_w: windows.PathSpace = undefined;
|
||||
target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
|
||||
target_path_w.data[target_path_w.len] = 0;
|
||||
// However, we need to canonicalize any path separators to `\`, since if
|
||||
// the target path is relative, then it must use `\` as the path separator.
|
||||
mem.replaceScalar(
|
||||
u16,
|
||||
target_path_w.data[0..target_path_w.len],
|
||||
mem.nativeToLittle(u16, '/'),
|
||||
mem.nativeToLittle(u16, '\\'),
|
||||
);
|
||||
|
||||
const sym_link_path_w = try windows.sliceToPrefixedFileW(self.fd, sym_link_path);
|
||||
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
|
||||
}
|
||||
const target_path_c = try posix.toPosixPath(target_path);
|
||||
const sym_link_path_c = try posix.toPosixPath(sym_link_path);
|
||||
return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
|
||||
}
|
||||
|
||||
/// WASI-only. Same as `symLink` except targeting WASI.
|
||||
pub fn symLinkWasi(
|
||||
self: Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
_: SymLinkFlags,
|
||||
) !void {
|
||||
return posix.symlinkat(target_path, self.fd, sym_link_path);
|
||||
}
|
||||
|
||||
/// Same as `symLink`, except the pathname parameters are null-terminated.
|
||||
pub fn symLinkZ(
|
||||
self: Dir,
|
||||
target_path_c: [*:0]const u8,
|
||||
sym_link_path_c: [*:0]const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
if (native_os == .windows) {
|
||||
const target_path_w = try windows.cStrToPrefixedFileW(self.fd, target_path_c);
|
||||
const sym_link_path_w = try windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
|
||||
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
|
||||
}
|
||||
return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `symLink` except the pathname parameters
|
||||
/// are WTF16 LE encoded.
|
||||
pub fn symLinkW(
|
||||
self: Dir,
|
||||
/// WTF-16, does not need to be NT-prefixed. The NT-prefixing
|
||||
/// of this path is handled by CreateSymbolicLink.
|
||||
/// Any path separators must be `\`, not `/`.
|
||||
target_path_w: [:0]const u16,
|
||||
/// WTF-16, must be NT-prefixed or relative
|
||||
sym_link_path_w: []const u16,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
|
||||
}
|
||||
|
||||
/// Same as `symLink`, except tries to create the symbolic link until it
|
||||
/// succeeds or encounters an error other than `error.PathAlreadyExists`.
|
||||
///
|
||||
/// * 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 atomicSymLink(
|
||||
dir: Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
if (dir.symLink(target_path, sym_link_path, flags)) {
|
||||
return;
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => {},
|
||||
else => |e| return e,
|
||||
}
|
||||
|
||||
const dirname = path.dirname(sym_link_path) orelse ".";
|
||||
|
||||
const rand_len = @sizeOf(u64) * 2;
|
||||
const temp_path_len = dirname.len + 1 + rand_len;
|
||||
var temp_path_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
|
||||
if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
|
||||
@memcpy(temp_path_buf[0..dirname.len], dirname);
|
||||
temp_path_buf[dirname.len] = path.sep;
|
||||
|
||||
const temp_path = temp_path_buf[0..temp_path_len];
|
||||
|
||||
while (true) {
|
||||
const random_integer = std.crypto.random.int(u64);
|
||||
temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
|
||||
|
||||
if (dir.symLink(target_path, temp_path, flags)) {
|
||||
return dir.rename(temp_path, sym_link_path);
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const ReadLinkError = posix.ReadLinkError;
|
||||
|
||||
/// Read value of a symbolic link.
|
||||
/// The return value is a slice of `buffer`, from index `0`.
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return self.readLinkWasi(sub_path, buffer);
|
||||
}
|
||||
if (native_os == .windows) {
|
||||
var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
|
||||
const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(result_w);
|
||||
if (len > buffer.len) return error.NameTooLong;
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
|
||||
return buffer[0..end_index];
|
||||
}
|
||||
const sub_path_c = try posix.toPosixPath(sub_path);
|
||||
return self.readLinkZ(&sub_path_c, buffer);
|
||||
}
|
||||
|
||||
/// WASI-only. Same as `readLink` except targeting WASI.
|
||||
pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
||||
return posix.readlinkat(self.fd, sub_path, buffer);
|
||||
}
|
||||
|
||||
/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
|
||||
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
|
||||
if (native_os == .windows) {
|
||||
var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
|
||||
const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(result_w);
|
||||
if (len > buffer.len) return error.NameTooLong;
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
|
||||
return buffer[0..end_index];
|
||||
}
|
||||
return posix.readlinkatZ(self.fd, sub_path_c, buffer);
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `readLink` except the path parameter
|
||||
/// is WTF-16 LE encoded, NT-prefixed.
|
||||
///
|
||||
/// `sub_path_w` will never be accessed after `buffer` has been written to, so it
|
||||
/// is safe to reuse a single buffer for both.
|
||||
pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 {
|
||||
return windows.ReadLink(self.fd, sub_path_w, buffer);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.readFile`.
|
||||
pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
|
||||
}
|
||||
|
||||
pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
|
||||
/// File size reached or exceeded the provided limit.
|
||||
StreamTooLong,
|
||||
};
|
||||
|
||||
/// Reads all the bytes from the named file. On success, caller owns returned
|
||||
/// buffer.
|
||||
///
|
||||
/// If the file size is already known, a better alternative is to initialize a
|
||||
/// `File.Reader`.
|
||||
///
|
||||
/// If the file size cannot be obtained, an error is returned. If
|
||||
/// this is a realistic possibility, a better alternative is to initialize a
|
||||
/// `File.Reader` which handles this seamlessly.
|
||||
pub fn readFileAlloc(
|
||||
dir: Dir,
|
||||
/// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, should be encoded as valid UTF-8.
|
||||
/// On other platforms, an opaque sequence of bytes with no particular encoding.
|
||||
sub_path: []const u8,
|
||||
/// Used to allocate the result.
|
||||
gpa: Allocator,
|
||||
/// If reached or exceeded, `error.StreamTooLong` is returned instead.
|
||||
limit: Io.Limit,
|
||||
) ReadFileAllocError![]u8 {
|
||||
return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
|
||||
}
|
||||
|
||||
/// Reads all the bytes from the named file. On success, caller owns returned
|
||||
/// buffer.
|
||||
///
|
||||
/// If the file size is already known, a better alternative is to initialize a
|
||||
/// `File.Reader`.
|
||||
///
|
||||
/// TODO move this function to Io.Dir
|
||||
pub fn readFileAllocOptions(
|
||||
dir: Dir,
|
||||
/// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, should be encoded as valid UTF-8.
|
||||
/// On other platforms, an opaque sequence of bytes with no particular encoding.
|
||||
sub_path: []const u8,
|
||||
/// Used to allocate the result.
|
||||
gpa: Allocator,
|
||||
/// If reached or exceeded, `error.StreamTooLong` is returned instead.
|
||||
limit: Io.Limit,
|
||||
comptime alignment: std.mem.Alignment,
|
||||
comptime sentinel: ?u8,
|
||||
) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
|
||||
var file = try dir.openFile(sub_path, .{});
|
||||
defer file.close();
|
||||
var file_reader = file.reader(io, &.{});
|
||||
return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
|
||||
error.ReadFailed => return file_reader.err.?,
|
||||
error.OutOfMemory, error.StreamTooLong => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const DeleteTreeError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileTooBig,
|
||||
SymLinkLoop,
|
||||
ProcessFdQuotaExceeded,
|
||||
NameTooLong,
|
||||
SystemFdQuotaExceeded,
|
||||
NoDevice,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
FileSystem,
|
||||
FileBusy,
|
||||
DeviceBusy,
|
||||
ProcessNotFound,
|
||||
/// One of the path components was not a directory.
|
||||
/// This error is unreachable if `sub_path` does not contain a path separator.
|
||||
NotDir,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
|
||||
Canceled,
|
||||
} || posix.UnexpectedError;
|
||||
|
||||
/// Whether `sub_path` describes a symlink, file, or directory, this function
|
||||
/// removes it. If it cannot be removed because it is a non-empty directory,
|
||||
/// this function recursively removes its entries and then tries again.
|
||||
/// This operation is not atomic on most file systems.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
|
||||
var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
|
||||
|
||||
const StackItem = struct {
|
||||
name: []const u8,
|
||||
parent_dir: Dir,
|
||||
iter: Dir.Iterator,
|
||||
|
||||
fn closeAll(items: []@This()) void {
|
||||
for (items) |*item| item.iter.dir.close();
|
||||
}
|
||||
};
|
||||
|
||||
var stack_buffer: [16]StackItem = undefined;
|
||||
var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
|
||||
defer StackItem.closeAll(stack.items);
|
||||
|
||||
stack.appendAssumeCapacity(.{
|
||||
.name = sub_path,
|
||||
.parent_dir = self,
|
||||
.iter = initial_iterable_dir.iterateAssumeFirstIteration(),
|
||||
});
|
||||
|
||||
process_stack: while (stack.items.len != 0) {
|
||||
var top = &stack.items[stack.items.len - 1];
|
||||
while (try top.iter.next()) |entry| {
|
||||
var treat_as_dir = entry.kind == .directory;
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
if (stack.unusedCapacitySlice().len >= 1) {
|
||||
var iterable_dir = top.iter.dir.openDir(entry.name, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
break :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
stack.appendAssumeCapacity(.{
|
||||
.name = entry.name,
|
||||
.parent_dir = top.iter.dir,
|
||||
.iter = iterable_dir.iterateAssumeFirstIteration(),
|
||||
});
|
||||
continue :process_stack;
|
||||
} else {
|
||||
try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
|
||||
break :handle_entry;
|
||||
}
|
||||
} else {
|
||||
if (top.iter.dir.deleteFile(entry.name)) {
|
||||
break :handle_entry;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => break :handle_entry,
|
||||
|
||||
// Impossible because we do not pass any path separators.
|
||||
error.NotDir => unreachable,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On Windows, we can't delete until the dir's handle has been closed, so
|
||||
// close it before we try to delete.
|
||||
top.iter.dir.close();
|
||||
|
||||
// In order to avoid double-closing the directory when cleaning up
|
||||
// the stack in the case of an error, we save the relevant portions and
|
||||
// pop the value from the stack.
|
||||
const parent_dir = top.parent_dir;
|
||||
const name = top.name;
|
||||
stack.items.len -= 1;
|
||||
|
||||
var need_to_retry: bool = false;
|
||||
parent_dir.deleteDir(name) catch |err| switch (err) {
|
||||
error.FileNotFound => {},
|
||||
error.DirNotEmpty => need_to_retry = true,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
if (need_to_retry) {
|
||||
// Since we closed the handle that the previous iterator used, we
|
||||
// need to re-open the dir and re-create the iterator.
|
||||
var iterable_dir = iterable_dir: {
|
||||
var treat_as_dir = true;
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
break :iterable_dir parent_dir.openDir(name, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
continue :process_stack;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
} else {
|
||||
if (parent_dir.deleteFile(name)) {
|
||||
continue :process_stack;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => continue :process_stack,
|
||||
|
||||
// Impossible because we do not pass any path separators.
|
||||
error.NotDir => unreachable,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// We know there is room on the stack since we are just re-adding
|
||||
// the StackItem that we previously popped.
|
||||
stack.appendAssumeCapacity(.{
|
||||
.name = name,
|
||||
.parent_dir = parent_dir,
|
||||
.iter = iterable_dir.iterateAssumeFirstIteration(),
|
||||
});
|
||||
continue :process_stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
|
||||
/// This is slower than `deleteTree` but uses less stack space.
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
|
||||
return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
|
||||
}
|
||||
|
||||
fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
|
||||
start_over: while (true) {
|
||||
var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
|
||||
var cleanup_dir_parent: ?Dir = null;
|
||||
defer if (cleanup_dir_parent) |*d| d.close();
|
||||
|
||||
var cleanup_dir = true;
|
||||
defer if (cleanup_dir) dir.close();
|
||||
|
||||
// Valid use of max_path_bytes because dir_name_buf will only
|
||||
// ever store a single path component that was returned from the
|
||||
// filesystem.
|
||||
var dir_name_buf: [fs.max_path_bytes]u8 = undefined;
|
||||
var dir_name: []const u8 = sub_path;
|
||||
|
||||
// Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
|
||||
// Go through each entry and if it is not a directory, delete it. If it is a directory,
|
||||
// open it, and close the original directory. Repeat. Then start the entire operation over.
|
||||
|
||||
scan_dir: while (true) {
|
||||
var dir_it = dir.iterateAssumeFirstIteration();
|
||||
dir_it: while (try dir_it.next()) |entry| {
|
||||
var treat_as_dir = entry.kind == .directory;
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
const new_dir = dir.openDir(entry.name, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
continue :dir_it;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.DeviceBusy,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
if (cleanup_dir_parent) |*d| d.close();
|
||||
cleanup_dir_parent = dir;
|
||||
dir = new_dir;
|
||||
const result = dir_name_buf[0..entry.name.len];
|
||||
@memcpy(result, entry.name);
|
||||
dir_name = result;
|
||||
continue :scan_dir;
|
||||
} else {
|
||||
if (dir.deleteFile(entry.name)) {
|
||||
continue :dir_it;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => continue :dir_it,
|
||||
|
||||
// Impossible because we do not pass any path separators.
|
||||
error.NotDir => unreachable,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reached the end of the directory entries, which means we successfully deleted all of them.
|
||||
// Now to remove the directory itself.
|
||||
dir.close();
|
||||
cleanup_dir = false;
|
||||
|
||||
if (cleanup_dir_parent) |d| {
|
||||
d.deleteDir(dir_name) catch |err| switch (err) {
|
||||
// These two things can happen due to file system race conditions.
|
||||
error.FileNotFound, error.DirNotEmpty => continue :start_over,
|
||||
else => |e| return e,
|
||||
};
|
||||
continue :start_over;
|
||||
} else {
|
||||
self.deleteDir(sub_path) catch |err| switch (err) {
|
||||
error.FileNotFound => return,
|
||||
error.DirNotEmpty => continue :start_over,
|
||||
else => |e| return e,
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On successful delete, returns null.
|
||||
fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
|
||||
return iterable_dir: {
|
||||
// Treat as a file by default
|
||||
var treat_as_dir = kind_hint == .directory;
|
||||
|
||||
handle_entry: while (true) {
|
||||
if (treat_as_dir) {
|
||||
break :iterable_dir self.openDir(sub_path, .{
|
||||
.follow_symlinks = false,
|
||||
.iterate = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.NotDir => {
|
||||
treat_as_dir = false;
|
||||
continue :handle_entry;
|
||||
},
|
||||
error.FileNotFound => {
|
||||
// That's fine, we were trying to remove this directory anyway.
|
||||
return null;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.NameTooLong,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.NoDevice,
|
||||
error.SystemResources,
|
||||
error.Unexpected,
|
||||
error.BadPathName,
|
||||
error.DeviceBusy,
|
||||
error.NetworkNotFound,
|
||||
error.Canceled,
|
||||
=> |e| return e,
|
||||
};
|
||||
} else {
|
||||
if (self.deleteFile(sub_path)) {
|
||||
return null;
|
||||
} else |err| switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
|
||||
error.IsDir => {
|
||||
treat_as_dir = true;
|
||||
continue :handle_entry;
|
||||
},
|
||||
|
||||
error.AccessDenied,
|
||||
error.PermissionDenied,
|
||||
error.SymLinkLoop,
|
||||
error.NameTooLong,
|
||||
error.SystemResources,
|
||||
error.ReadOnlyFileSystem,
|
||||
error.NotDir,
|
||||
error.FileSystem,
|
||||
error.FileBusy,
|
||||
error.BadPathName,
|
||||
error.NetworkNotFound,
|
||||
error.Unexpected,
|
||||
=> |e| return e,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const WriteFileError = File.WriteError || File.OpenError;
|
||||
|
||||
pub const WriteFileOptions = struct {
|
||||
/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
|
||||
sub_path: []const u8,
|
||||
data: []const u8,
|
||||
flags: File.CreateFlags = .{},
|
||||
};
|
||||
|
||||
/// Writes content to the file system, using the file creation flags provided.
|
||||
pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
|
||||
var file = try self.createFile(options.sub_path, options.flags);
|
||||
defer file.close();
|
||||
try file.writeAll(options.data);
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.AccessError`.
|
||||
pub const AccessError = Io.Dir.AccessError;
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.access`.
|
||||
pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
|
||||
}
|
||||
|
||||
pub const CopyFileOptions = struct {
|
||||
/// When this is `null` the mode is copied from the source file.
|
||||
override_mode: ?File.Mode = null,
|
||||
};
|
||||
|
||||
pub const CopyFileError = File.OpenError || File.StatError ||
|
||||
AtomicFile.InitError || AtomicFile.FinishError ||
|
||||
File.ReadError || File.WriteError || error{InvalidFileName};
|
||||
|
||||
/// Atomically creates a new file at `dest_path` within `dest_dir` with the
|
||||
/// same contents as `source_path` within `source_dir`, overwriting any already
|
||||
/// existing file.
|
||||
///
|
||||
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
|
||||
/// readily available, there is a possibility of power loss or application
|
||||
/// termination leaving temporary files present in the same directory as
|
||||
/// dest_path.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// TODO move this function to Io.Dir
|
||||
pub fn copyFile(
|
||||
source_dir: Dir,
|
||||
source_path: []const u8,
|
||||
dest_dir: Dir,
|
||||
dest_path: []const u8,
|
||||
options: CopyFileOptions,
|
||||
) CopyFileError!void {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
|
||||
const file = try source_dir.openFile(source_path, .{});
|
||||
var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
|
||||
defer file_reader.file.close(io);
|
||||
|
||||
const mode = options.override_mode orelse blk: {
|
||||
const st = try file_reader.file.stat(io);
|
||||
file_reader.size = st.size;
|
||||
break :blk st.mode;
|
||||
};
|
||||
|
||||
var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
|
||||
var atomic_file = try dest_dir.atomicFile(dest_path, .{
|
||||
.mode = mode,
|
||||
.write_buffer = &buffer,
|
||||
});
|
||||
defer atomic_file.deinit();
|
||||
|
||||
_ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
|
||||
error.ReadFailed => return file_reader.err.?,
|
||||
error.WriteFailed => return atomic_file.file_writer.err.?,
|
||||
};
|
||||
|
||||
try atomic_file.finish();
|
||||
}
|
||||
|
||||
pub const AtomicFileOptions = struct {
|
||||
mode: File.Mode = File.default_mode,
|
||||
make_path: bool = false,
|
||||
write_buffer: []u8,
|
||||
};
|
||||
|
||||
/// Directly access the `.file` field, and then call `AtomicFile.finish` to
|
||||
/// atomically replace `dest_path` with contents.
|
||||
/// Always call `AtomicFile.deinit` to clean up, regardless of whether
|
||||
/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
|
||||
/// `AtomicFile.deinit` is called.
|
||||
/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `dest_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
|
||||
if (fs.path.dirname(dest_path)) |dirname| {
|
||||
const dir = if (options.make_path)
|
||||
try self.makeOpenPath(dirname, .{})
|
||||
else
|
||||
try self.openDir(dirname, .{});
|
||||
|
||||
return .init(fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
|
||||
} else {
|
||||
return .init(dest_path, options.mode, self, false, options.write_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pub const Stat = File.Stat;
|
||||
pub const StatError = File.StatError;
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.stat`.
|
||||
pub fn stat(self: Dir) StatError!Stat {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.Dir.stat(.{ .handle = self.fd }, io);
|
||||
}
|
||||
|
||||
pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
|
||||
|
||||
/// Deprecated in favor of `Io.Dir.statPath`.
|
||||
pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
|
||||
}
|
||||
|
||||
pub const ChmodError = File.ChmodError;
|
||||
|
||||
/// Changes the mode of the directory.
|
||||
/// The process must have the correct privileges in order to do this
|
||||
/// successfully, or must have the effective user ID matching the owner
|
||||
/// of the directory. Additionally, the directory must have been opened
|
||||
/// with `OpenOptions{ .iterate = true }`.
|
||||
pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
|
||||
const file: File = .{ .handle = self.fd };
|
||||
try file.chmod(new_mode);
|
||||
}
|
||||
|
||||
/// Changes the owner and group of the directory.
|
||||
/// The process must have the correct privileges in order to do this
|
||||
/// successfully. The group may be changed by the owner of the directory to
|
||||
/// any group of which the owner is a member. Additionally, the directory
|
||||
/// must have been opened with `OpenOptions{ .iterate = true }`. If the
|
||||
/// owner or group is specified as `null`, the ID is not changed.
|
||||
pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
|
||||
const file: File = .{ .handle = self.fd };
|
||||
try file.chown(owner, group);
|
||||
}
|
||||
|
||||
pub const ChownError = File.ChownError;
|
||||
|
||||
const Permissions = File.Permissions;
|
||||
pub const SetPermissionsError = File.SetPermissionsError;
|
||||
|
||||
/// Sets permissions according to the provided `Permissions` struct.
|
||||
/// This method is *NOT* available on WASI
|
||||
pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
|
||||
const file: File = .{ .handle = self.fd };
|
||||
try file.setPermissions(permissions);
|
||||
}
|
||||
|
||||
pub fn adaptToNewApi(dir: Dir) Io.Dir {
|
||||
return .{ .handle = dir.fd };
|
||||
}
|
||||
|
||||
pub fn adaptFromNewApi(dir: Io.Dir) Dir {
|
||||
return .{ .fd = dir.handle };
|
||||
}
|
||||
+9
-90
@@ -22,18 +22,10 @@ handle: Handle,
|
||||
pub const Handle = Io.File.Handle;
|
||||
pub const Mode = Io.File.Mode;
|
||||
pub const INode = Io.File.INode;
|
||||
pub const Uid = posix.uid_t;
|
||||
pub const Gid = posix.gid_t;
|
||||
pub const Uid = Io.File.Uid;
|
||||
pub const Gid = Io.File.Gid;
|
||||
pub const Kind = Io.File.Kind;
|
||||
|
||||
/// This is the default mode given to POSIX operating systems for creating
|
||||
/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
|
||||
/// since most people would expect "-rw-r--r--", for example, when using
|
||||
/// the `touch` command, which would correspond to `0o644`. However, POSIX
|
||||
/// libc implementations use `0o666` inside `fopen` and then rely on the
|
||||
/// process-scoped "umask" setting to adjust this number for file creation.
|
||||
pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
|
||||
|
||||
/// Deprecated in favor of `Io.File.OpenError`.
|
||||
pub const OpenError = Io.File.OpenError || error{WouldBlock};
|
||||
/// Deprecated in favor of `Io.File.OpenMode`.
|
||||
@@ -43,53 +35,7 @@ pub const Lock = Io.File.Lock;
|
||||
/// Deprecated in favor of `Io.File.OpenFlags`.
|
||||
pub const OpenFlags = Io.File.OpenFlags;
|
||||
|
||||
pub const CreateFlags = struct {
|
||||
/// Whether the file will be created with read access.
|
||||
read: bool = false,
|
||||
|
||||
/// If the file already exists, and is a regular file, and the access
|
||||
/// mode allows writing, it will be truncated to length 0.
|
||||
truncate: bool = true,
|
||||
|
||||
/// Ensures that this open call creates the file, otherwise causes
|
||||
/// `error.PathAlreadyExists` to be returned.
|
||||
exclusive: bool = false,
|
||||
|
||||
/// Open the file with an advisory lock to coordinate with other processes
|
||||
/// accessing it at the same time. An exclusive lock will prevent other
|
||||
/// processes from acquiring a lock. A shared lock will prevent other
|
||||
/// processes from acquiring a exclusive lock, but does not prevent
|
||||
/// other process from getting their own shared locks.
|
||||
///
|
||||
/// The lock is advisory, except on Linux in very specific circumstances[1].
|
||||
/// This means that a process that does not respect the locking API can still get access
|
||||
/// to the file, despite the lock.
|
||||
///
|
||||
/// On these operating systems, the lock is acquired atomically with
|
||||
/// opening the file:
|
||||
/// * Darwin
|
||||
/// * DragonFlyBSD
|
||||
/// * FreeBSD
|
||||
/// * Haiku
|
||||
/// * NetBSD
|
||||
/// * OpenBSD
|
||||
/// On these operating systems, the lock is acquired via a separate syscall
|
||||
/// after opening the file:
|
||||
/// * Linux
|
||||
/// * Windows
|
||||
///
|
||||
/// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
|
||||
lock: Lock = .none,
|
||||
|
||||
/// Sets whether or not to wait until the file is locked to return. If set to true,
|
||||
/// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
|
||||
/// is available to proceed.
|
||||
lock_nonblocking: bool = false,
|
||||
|
||||
/// For POSIX systems this is the file system mode the file will
|
||||
/// be created with. On other systems this is always 0.
|
||||
mode: Mode = default_mode,
|
||||
};
|
||||
pub const CreateFlags = std.Io.File.CreateFlags;
|
||||
|
||||
pub fn stdout() File {
|
||||
return .{ .handle = if (is_windows) windows.peb().ProcessParameters.hStdOutput else posix.STDOUT_FILENO };
|
||||
@@ -259,33 +205,6 @@ pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
|
||||
try posix.ftruncate(self.handle, length);
|
||||
}
|
||||
|
||||
pub const SeekError = posix.SeekError;
|
||||
|
||||
/// Repositions read/write file offset relative to the current offset.
|
||||
/// TODO: integrate with async I/O
|
||||
pub fn seekBy(self: File, offset: i64) SeekError!void {
|
||||
return posix.lseek_CUR(self.handle, offset);
|
||||
}
|
||||
|
||||
/// Repositions read/write file offset relative to the end.
|
||||
/// TODO: integrate with async I/O
|
||||
pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
|
||||
return posix.lseek_END(self.handle, offset);
|
||||
}
|
||||
|
||||
/// Repositions read/write file offset relative to the beginning.
|
||||
/// TODO: integrate with async I/O
|
||||
pub fn seekTo(self: File, offset: u64) SeekError!void {
|
||||
return posix.lseek_SET(self.handle, offset);
|
||||
}
|
||||
|
||||
pub const GetSeekPosError = posix.SeekError || StatError;
|
||||
|
||||
/// TODO: integrate with async I/O
|
||||
pub fn getPos(self: File) GetSeekPosError!u64 {
|
||||
return posix.lseek_CUR_get(self.handle);
|
||||
}
|
||||
|
||||
pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError;
|
||||
|
||||
/// TODO: integrate with async I/O
|
||||
@@ -306,11 +225,13 @@ pub fn mode(self: File) ModeError!Mode {
|
||||
return (try self.stat()).mode;
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `Io.File.Stat`.
|
||||
pub const Stat = Io.File.Stat;
|
||||
|
||||
/// Deprecated in favor of `Io.File.StatError`.
|
||||
pub const StatError = posix.FStatError;
|
||||
|
||||
/// Returns `Stat` containing basic information about the `File`.
|
||||
/// Deprecated in favor of `Io.File.stat`.
|
||||
pub fn stat(self: File) StatError!Stat {
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
@@ -710,7 +631,7 @@ pub const Writer = struct {
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub const SeekError = File.SeekError;
|
||||
pub const SeekError = Io.File.SeekError;
|
||||
|
||||
/// Number of slices to store on the stack, when trying to send as many byte
|
||||
/// vectors through the underlying write calls as possible.
|
||||
@@ -1268,10 +1189,8 @@ pub fn writerStreaming(file: File, buffer: []u8) Writer {
|
||||
const range_off: windows.LARGE_INTEGER = 0;
|
||||
const range_len: windows.LARGE_INTEGER = 1;
|
||||
|
||||
pub const LockError = error{
|
||||
SystemResources,
|
||||
FileLocksNotSupported,
|
||||
} || posix.UnexpectedError;
|
||||
/// Deprecated
|
||||
pub const LockError = Io.File.LockError;
|
||||
|
||||
/// Blocks when an incompatible lock is held by another process.
|
||||
/// A process may hold only one type of lock (shared or exclusive) on
|
||||
|
||||
@@ -2065,21 +2065,6 @@ test "chown" {
|
||||
try dir.chown(null, null);
|
||||
}
|
||||
|
||||
test "delete a setAsCwd directory on Windows" {
|
||||
if (native_os != .windows) return error.SkipZigTest;
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
// Set tmp dir as current working directory.
|
||||
try tmp.dir.setAsCwd();
|
||||
tmp.dir.close();
|
||||
try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path));
|
||||
// Now set the parent dir as the current working dir for clean up.
|
||||
try tmp.parent_dir.setAsCwd();
|
||||
try tmp.parent_dir.deleteTree(&tmp.sub_path);
|
||||
// Close the parent "tmp" so we don't leak the HANDLE.
|
||||
tmp.parent_dir.close();
|
||||
}
|
||||
|
||||
test "invalid UTF-8/WTF-8 paths" {
|
||||
const expected_err = switch (native_os) {
|
||||
.wasi => error.BadPathName,
|
||||
|
||||
-130
@@ -21,7 +21,6 @@ const mem = std.mem;
|
||||
const elf = std.elf;
|
||||
const fs = std.fs;
|
||||
const dl = @import("dynamic_library.zig");
|
||||
const max_path_bytes = std.fs.max_path_bytes;
|
||||
const posix = std.posix;
|
||||
const native_os = builtin.os.tag;
|
||||
|
||||
@@ -56,135 +55,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o
|
||||
else => undefined,
|
||||
};
|
||||
|
||||
pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
|
||||
return switch (os.tag) {
|
||||
.windows,
|
||||
.driverkit,
|
||||
.ios,
|
||||
.maccatalyst,
|
||||
.macos,
|
||||
.tvos,
|
||||
.visionos,
|
||||
.watchos,
|
||||
.linux,
|
||||
.illumos,
|
||||
.freebsd,
|
||||
.serenity,
|
||||
=> true,
|
||||
|
||||
.dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
|
||||
.netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Return canonical path of handle `fd`.
|
||||
///
|
||||
/// This function is very host-specific and is not universally supported by all hosts.
|
||||
/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
|
||||
/// unsupported on WASI.
|
||||
///
|
||||
/// * 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.
|
||||
///
|
||||
/// Calling this function is usually a bug.
|
||||
pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
|
||||
if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
|
||||
@compileError("querying for canonical path of a handle is unsupported on this host");
|
||||
}
|
||||
switch (native_os) {
|
||||
.windows => {
|
||||
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
|
||||
const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
|
||||
return out_buffer[0..end_index];
|
||||
},
|
||||
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
|
||||
// On macOS, we can use F.GETPATH fcntl command to query the OS for
|
||||
// the path to the file descriptor.
|
||||
@memset(out_buffer[0..max_path_bytes], 0);
|
||||
switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
|
||||
.SUCCESS => {},
|
||||
.BADF => return error.FileNotFound,
|
||||
.NOSPC => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
// TODO man pages for fcntl on macOS don't really tell you what
|
||||
// errno values to expect when command is F.GETPATH...
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
|
||||
return out_buffer[0..len];
|
||||
},
|
||||
.linux, .serenity => {
|
||||
var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
|
||||
const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}, 0) catch unreachable;
|
||||
|
||||
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| {
|
||||
switch (err) {
|
||||
error.NotLink => unreachable,
|
||||
error.BadPathName => unreachable,
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows-only
|
||||
error.NetworkNotFound => unreachable, // Windows-only
|
||||
else => |e| return e,
|
||||
}
|
||||
};
|
||||
return target;
|
||||
},
|
||||
.illumos => {
|
||||
var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
|
||||
const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/path/{d}", .{fd}, 0) catch unreachable;
|
||||
|
||||
const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
|
||||
error.UnsupportedReparsePointType => unreachable,
|
||||
error.NotLink => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
return target;
|
||||
},
|
||||
.freebsd => {
|
||||
var kfile: std.c.kinfo_file = undefined;
|
||||
kfile.structsize = std.c.KINFO_FILE_SIZE;
|
||||
switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
|
||||
.SUCCESS => {},
|
||||
.BADF => return error.FileNotFound,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
const len = mem.findScalar(u8, &kfile.path, 0) orelse max_path_bytes;
|
||||
if (len == 0) return error.NameTooLong;
|
||||
const result = out_buffer[0..len];
|
||||
@memcpy(result, kfile.path[0..len]);
|
||||
return result;
|
||||
},
|
||||
.dragonfly => {
|
||||
@memset(out_buffer[0..max_path_bytes], 0);
|
||||
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
|
||||
.SUCCESS => {},
|
||||
.BADF => return error.FileNotFound,
|
||||
.RANGE => return error.NameTooLong,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
|
||||
return out_buffer[0..len];
|
||||
},
|
||||
.netbsd => {
|
||||
@memset(out_buffer[0..max_path_bytes], 0);
|
||||
switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
|
||||
.SUCCESS => {},
|
||||
.ACCES => return error.AccessDenied,
|
||||
.BADF => return error.FileNotFound,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.RANGE => return error.NameTooLong,
|
||||
else => |err| return posix.unexpectedErrno(err),
|
||||
}
|
||||
const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
|
||||
return out_buffer[0..len];
|
||||
},
|
||||
else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
|
||||
}
|
||||
}
|
||||
|
||||
pub const FstatError = error{
|
||||
SystemResources,
|
||||
AccessDenied,
|
||||
|
||||
-1014
@@ -319,35 +319,6 @@ pub const FChmodError = error{
|
||||
ReadOnlyFileSystem,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Changes the mode of the file referred to by the file descriptor.
|
||||
///
|
||||
/// The process must have the correct privileges in order to do this
|
||||
/// successfully, or must have the effective user ID matching the owner
|
||||
/// of the file.
|
||||
pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
|
||||
if (!fs.has_executable_bit) @compileError("fchmod unsupported by target OS");
|
||||
|
||||
while (true) {
|
||||
const res = system.fchmod(fd, mode);
|
||||
switch (errno(res)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => unreachable,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const FChmodAtError = FChmodError || error{
|
||||
/// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
|
||||
/// `PATH_MAX`.
|
||||
@@ -533,50 +504,6 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
|
||||
}
|
||||
}
|
||||
|
||||
pub const FChownError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
InputOutput,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Changes the owner and group of the file referred to by the file descriptor.
|
||||
/// The process must have the correct privileges in order to do this
|
||||
/// successfully. The group may be changed by the owner of the directory to
|
||||
/// any group of which the owner is a member. If the owner or group is
|
||||
/// specified as `null`, the ID is not changed.
|
||||
pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
|
||||
switch (native_os) {
|
||||
.windows, .wasi => @compileError("Unsupported OS"),
|
||||
else => {},
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0));
|
||||
|
||||
switch (errno(res)) {
|
||||
.SUCCESS => return,
|
||||
.INTR => continue,
|
||||
.BADF => unreachable, // Can be reached if the fd refers to a directory opened without `Dir.OpenOptions{ .iterate = true }`
|
||||
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.IO => return error.InputOutput,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.FileNotFound,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const RebootError = error{
|
||||
PermissionDenied,
|
||||
} || UnexpectedError;
|
||||
@@ -1926,150 +1853,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
|
||||
}
|
||||
}
|
||||
|
||||
pub const SymLinkError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to create a new symbolic link relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
DiskQuota,
|
||||
PathAlreadyExists,
|
||||
FileSystem,
|
||||
SymLinkLoop,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
ReadOnlyFileSystem,
|
||||
NotDir,
|
||||
NameTooLong,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// If `sym_link_path` exists, it will not be overwritten.
|
||||
/// See also `symlinkZ.
|
||||
pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
|
||||
if (native_os == .windows) {
|
||||
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return symlinkat(target_path, AT.FDCWD, sym_link_path);
|
||||
}
|
||||
const target_path_c = try toPosixPath(target_path);
|
||||
const sym_link_path_c = try toPosixPath(sym_link_path);
|
||||
return symlinkZ(&target_path_c, &sym_link_path_c);
|
||||
}
|
||||
|
||||
/// This is the same as `symlink` except the parameters are null-terminated pointers.
|
||||
/// See also `symlink`.
|
||||
pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
|
||||
if (native_os == .windows) {
|
||||
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return symlinkatZ(target_path, fs.cwd().fd, sym_link_path);
|
||||
}
|
||||
switch (errno(system.symlink(target_path, sym_link_path))) {
|
||||
.SUCCESS => return,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string
|
||||
/// `target_path` **relative** to `newdirfd` directory handle.
|
||||
/// 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.
|
||||
/// 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.
|
||||
/// If `sym_link_path` exists, it will not be overwritten.
|
||||
/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
|
||||
pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
|
||||
if (native_os == .windows) {
|
||||
@compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return symlinkatWasi(target_path, newdirfd, sym_link_path);
|
||||
}
|
||||
const target_path_c = try toPosixPath(target_path);
|
||||
const sym_link_path_c = try toPosixPath(sym_link_path);
|
||||
return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c);
|
||||
}
|
||||
|
||||
/// WASI-only. The same as `symlinkat` but targeting WASI.
|
||||
/// See also `symlinkat`.
|
||||
pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
|
||||
switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) {
|
||||
.SUCCESS => {},
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.BADF => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as `symlinkat` except the parameters are null-terminated pointers.
|
||||
/// See also `symlinkat`.
|
||||
pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
|
||||
if (native_os == .windows) {
|
||||
@compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
|
||||
}
|
||||
switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
|
||||
.SUCCESS => return,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub const LinkError = UnexpectedError || error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
@@ -2216,334 +1999,6 @@ pub fn linkat(
|
||||
return try linkatZ(olddir, &old, newdir, &new, flags);
|
||||
}
|
||||
|
||||
pub const UnlinkError = error{
|
||||
FileNotFound,
|
||||
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to unlink a resource by path relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
FileSystem,
|
||||
IsDir,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
NotDir,
|
||||
SystemResources,
|
||||
ReadOnlyFileSystem,
|
||||
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
/// Windows: file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Delete a name and possibly the file it refers to.
|
||||
/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `file_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// See also `unlinkZ`.
|
||||
pub fn unlink(file_path: []const u8) UnlinkError!void {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // only occurs when targeting directories
|
||||
else => |e| return e,
|
||||
};
|
||||
} else if (native_os == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
|
||||
return unlinkW(file_path_w.span());
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return unlinkZ(&file_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `unlink` except the parameter is null terminated.
|
||||
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
|
||||
if (native_os == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
|
||||
return unlinkW(file_path_w.span());
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return unlink(mem.sliceTo(file_path, 0));
|
||||
}
|
||||
switch (errno(system.unlink(file_path))) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
|
||||
pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
|
||||
windows.DeleteFile(file_path_w, .{ .dir = fs.cwd().fd }) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const UnlinkatError = UnlinkError || error{
|
||||
/// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty.
|
||||
DirNotEmpty,
|
||||
};
|
||||
|
||||
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
|
||||
/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `file_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
|
||||
if (native_os == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
|
||||
return unlinkatW(dirfd, file_path_w.span(), flags);
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return unlinkatWasi(dirfd, file_path, flags);
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return unlinkatZ(dirfd, &file_path_c, flags);
|
||||
}
|
||||
}
|
||||
|
||||
/// WASI-only. Same as `unlinkat` but targeting WASI.
|
||||
/// See also `unlinkat`.
|
||||
pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
|
||||
const remove_dir = (flags & AT.REMOVEDIR) != 0;
|
||||
const res = if (remove_dir)
|
||||
wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)
|
||||
else
|
||||
wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len);
|
||||
switch (res) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => unreachable,
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.NOTEMPTY => return error.DirNotEmpty,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
|
||||
.INVAL => unreachable, // invalid flags, or pathname has . as last component
|
||||
.BADF => unreachable, // always a race condition
|
||||
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `unlinkat` but `file_path` is a null-terminated string.
|
||||
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
|
||||
if (native_os == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c);
|
||||
return unlinkatW(dirfd, file_path_w.span(), flags);
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
|
||||
}
|
||||
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => unreachable,
|
||||
.IO => return error.FileSystem,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.EXIST => return error.DirNotEmpty,
|
||||
.NOTEMPTY => return error.DirNotEmpty,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
|
||||
.INVAL => unreachable, // invalid flags, or pathname has . as last component
|
||||
.BADF => unreachable, // always a race condition
|
||||
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
|
||||
pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
|
||||
const remove_dir = (flags & AT.REMOVEDIR) != 0;
|
||||
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
|
||||
}
|
||||
|
||||
pub const RenameError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to rename a resource by path relative to it.
|
||||
///
|
||||
/// On Windows, this error may be returned instead of PathAlreadyExists when
|
||||
/// renaming a directory over an existing directory.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
DiskQuota,
|
||||
IsDir,
|
||||
SymLinkLoop,
|
||||
LinkQuotaExceeded,
|
||||
NameTooLong,
|
||||
FileNotFound,
|
||||
NotDir,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
PathAlreadyExists,
|
||||
ReadOnlyFileSystem,
|
||||
RenameAcrossMountPoints,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
NoDevice,
|
||||
SharingViolation,
|
||||
PipeBusy,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
/// 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,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Change the name or location of a file.
|
||||
/// 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 rename(old_path: []const u8, new_path: []const u8) RenameError!void {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path);
|
||||
} else if (native_os == .windows) {
|
||||
const old_path_w = try windows.sliceToPrefixedFileW(null, old_path);
|
||||
const new_path_w = try windows.sliceToPrefixedFileW(null, new_path);
|
||||
return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
|
||||
} else {
|
||||
const old_path_c = try toPosixPath(old_path);
|
||||
const new_path_c = try toPosixPath(new_path);
|
||||
return renameZ(&old_path_c, &new_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are null-terminated.
|
||||
pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
|
||||
if (native_os == .windows) {
|
||||
const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
|
||||
const new_path_w = try windows.cStrToPrefixedFileW(null, new_path);
|
||||
return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
|
||||
}
|
||||
switch (errno(system.rename(old_path, new_path))) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MLINK => return error.LinkQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.NOTEMPTY => return error.PathAlreadyExists,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.XDEV => return error.RenameAcrossMountPoints,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
|
||||
/// Assumes target is Windows.
|
||||
pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
|
||||
const cwd_handle = std.fs.cwd().fd;
|
||||
return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true);
|
||||
}
|
||||
|
||||
/// Change the name or location of a file based on an open directory handle.
|
||||
/// 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 renameat(
|
||||
old_dir_fd: fd_t,
|
||||
old_path: []const u8,
|
||||
new_dir_fd: fd_t,
|
||||
new_path: []const u8,
|
||||
) RenameError!void {
|
||||
if (native_os == .windows) {
|
||||
const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path);
|
||||
const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path);
|
||||
return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
|
||||
const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
|
||||
return renameatWasi(old, new);
|
||||
} else {
|
||||
const old_path_c = try toPosixPath(old_path);
|
||||
const new_path_c = try toPosixPath(new_path);
|
||||
return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// WASI-only. Same as `renameat` expect targeting WASI.
|
||||
/// See also `renameat`.
|
||||
fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
|
||||
switch (wasi.path_rename(old.dir_fd, old.relative_path.ptr, old.relative_path.len, new.dir_fd, new.relative_path.ptr, new.relative_path.len)) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MLINK => return error.LinkQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.NOTEMPTY => return error.PathAlreadyExists,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.XDEV => return error.RenameAcrossMountPoints,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// An fd-relative file path
|
||||
///
|
||||
/// This is currently only used for WASI-specific functionality, but the concept
|
||||
@@ -2555,58 +2010,6 @@ const RelativePathWasi = struct {
|
||||
relative_path: []const u8,
|
||||
};
|
||||
|
||||
/// Same as `renameat` except the parameters are null-terminated.
|
||||
pub fn renameatZ(
|
||||
old_dir_fd: fd_t,
|
||||
old_path: [*:0]const u8,
|
||||
new_dir_fd: fd_t,
|
||||
new_path: [*:0]const u8,
|
||||
) RenameError!void {
|
||||
if (native_os == .windows) {
|
||||
const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path);
|
||||
const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path);
|
||||
return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
|
||||
}
|
||||
|
||||
switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.DQUOT => return error.DiskQuota,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.ISDIR => return error.IsDir,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.MLINK => return error.LinkQuotaExceeded,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOSPC => return error.NoSpaceLeft,
|
||||
.EXIST => return error.PathAlreadyExists,
|
||||
.NOTEMPTY => return error.PathAlreadyExists,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.XDEV => return error.RenameAcrossMountPoints,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `renameat` but Windows-only and the path parameters are
|
||||
/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.
|
||||
pub fn renameatW(
|
||||
old_dir_fd: fd_t,
|
||||
old_path_w: []const u16,
|
||||
new_dir_fd: fd_t,
|
||||
new_path_w: []const u16,
|
||||
ReplaceIfExists: windows.BOOLEAN,
|
||||
) RenameError!void {
|
||||
return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0);
|
||||
}
|
||||
|
||||
/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
|
||||
@@ -2723,84 +2126,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
|
||||
windows.CloseHandle(sub_dir_handle);
|
||||
}
|
||||
|
||||
pub const DeleteDirError = error{
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileBusy,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NotDir,
|
||||
DirNotEmpty,
|
||||
ReadOnlyFileSystem,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Deletes an empty directory.
|
||||
/// 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 rmdir(dir_path: []const u8) DeleteDirError!void {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return unlinkat(AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
|
||||
error.FileSystem => unreachable, // only occurs when targeting files
|
||||
error.IsDir => unreachable, // only occurs when targeting files
|
||||
else => |e| return e,
|
||||
};
|
||||
} else if (native_os == .windows) {
|
||||
const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
|
||||
return rmdirW(dir_path_w.span());
|
||||
} else {
|
||||
const dir_path_c = try toPosixPath(dir_path);
|
||||
return rmdirZ(&dir_path_c);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `rmdir` except the parameter is null-terminated.
|
||||
/// 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 rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
|
||||
if (native_os == .windows) {
|
||||
const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
|
||||
return rmdirW(dir_path_w.span());
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return rmdir(mem.sliceTo(dir_path, 0));
|
||||
}
|
||||
switch (errno(system.rmdir(dir_path))) {
|
||||
.SUCCESS => return,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.PERM => return error.PermissionDenied,
|
||||
.BUSY => return error.FileBusy,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => return error.BadPathName,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.EXIST => return error.DirNotEmpty,
|
||||
.NOTEMPTY => return error.DirNotEmpty,
|
||||
.ROFS => return error.ReadOnlyFileSystem,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
|
||||
pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
|
||||
return windows.DeleteFile(dir_path_w, .{ .dir = fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
|
||||
error.IsDir => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ChangeCurDirError = error{
|
||||
AccessDenied,
|
||||
FileSystem,
|
||||
@@ -2889,194 +2214,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void {
|
||||
}
|
||||
}
|
||||
|
||||
pub const ReadLinkError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to read value of a symbolic link relative to it.
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
FileSystem,
|
||||
SymLinkLoop,
|
||||
NameTooLong,
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NotLink,
|
||||
NotDir,
|
||||
/// WASI: file paths must be valid UTF-8.
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
/// Windows-only. This error may occur if the opened reparse point is
|
||||
/// of unsupported type.
|
||||
UnsupportedReparsePointType,
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
/// 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,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Read value of a symbolic link.
|
||||
/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `file_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// The return value is a slice of `out_buffer` from index 0.
|
||||
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, the result is encoded as UTF-8.
|
||||
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
||||
pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return readlinkat(AT.FDCWD, file_path, out_buffer);
|
||||
} else if (native_os == .windows) {
|
||||
var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
|
||||
const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(result_w);
|
||||
if (len > out_buffer.len) return error.NameTooLong;
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
|
||||
return out_buffer[0..end_index];
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return readlinkZ(&file_path_c, out_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed.
|
||||
/// The result is encoded as WTF-16 LE.
|
||||
///
|
||||
/// `file_path` will never be accessed after `out_buffer` has been written to, so it
|
||||
/// is safe to reuse a single buffer for both.
|
||||
///
|
||||
/// See also `readlinkZ`.
|
||||
pub fn readlinkW(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
|
||||
return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `readlink` except `file_path` is null-terminated.
|
||||
pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (native_os == .windows) {
|
||||
var file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
|
||||
const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(result_w);
|
||||
if (len > out_buffer.len) return error.NameTooLong;
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
|
||||
return out_buffer[0..end_index];
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return readlink(mem.sliceTo(file_path, 0), out_buffer);
|
||||
}
|
||||
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return out_buffer[0..@bitCast(rc)],
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => return error.NotLink,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
|
||||
/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, `file_path` should be encoded as valid UTF-8.
|
||||
/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
|
||||
/// The return value is a slice of `out_buffer` from index 0.
|
||||
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On WASI, the result is encoded as UTF-8.
|
||||
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
||||
/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
|
||||
pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
return readlinkatWasi(dirfd, file_path, out_buffer);
|
||||
}
|
||||
if (native_os == .windows) {
|
||||
var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
|
||||
const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(result_w);
|
||||
if (len > out_buffer.len) return error.NameTooLong;
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
|
||||
return out_buffer[0..end_index];
|
||||
}
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return readlinkatZ(dirfd, &file_path_c, out_buffer);
|
||||
}
|
||||
|
||||
/// WASI-only. Same as `readlinkat` but targets WASI.
|
||||
/// See also `readlinkat`.
|
||||
pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
var bufused: usize = undefined;
|
||||
switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) {
|
||||
.SUCCESS => return out_buffer[0..bufused],
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => return error.NotLink,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NOTCAPABLE => return error.AccessDenied,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed.
|
||||
/// The result is encoded as WTF-16 LE.
|
||||
///
|
||||
/// `file_path` will never be accessed after `out_buffer` has been written to, so it
|
||||
/// is safe to reuse a single buffer for both.
|
||||
///
|
||||
/// See also `readlinkat`.
|
||||
pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
|
||||
return windows.ReadLink(dirfd, file_path, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `readlinkat` except `file_path` is null-terminated.
|
||||
/// See also `readlinkat`.
|
||||
pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (native_os == .windows) {
|
||||
var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
|
||||
const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
|
||||
|
||||
const len = std.unicode.calcWtf8Len(result_w);
|
||||
if (len > out_buffer.len) return error.NameTooLong;
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
|
||||
return out_buffer[0..end_index];
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
|
||||
}
|
||||
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return out_buffer[0..@bitCast(rc)],
|
||||
.ACCES => return error.AccessDenied,
|
||||
.FAULT => unreachable,
|
||||
.INVAL => return error.NotLink,
|
||||
.IO => return error.FileSystem,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.NOMEM => return error.SystemResources,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.ILSEQ => return error.BadPathName,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub const SetEidError = error{
|
||||
InvalidUserId,
|
||||
PermissionDenied,
|
||||
@@ -4814,157 +3951,6 @@ pub fn flock(fd: fd_t, operation: i32) FlockError!void {
|
||||
}
|
||||
}
|
||||
|
||||
pub const RealPathError = error{
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PermissionDenied,
|
||||
NameTooLong,
|
||||
NotSupported,
|
||||
NotDir,
|
||||
SymLinkLoop,
|
||||
InputOutput,
|
||||
FileTooBig,
|
||||
IsDir,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
NoDevice,
|
||||
SystemResources,
|
||||
NoSpaceLeft,
|
||||
FileSystem,
|
||||
DeviceBusy,
|
||||
ProcessNotFound,
|
||||
|
||||
SharingViolation,
|
||||
PipeBusy,
|
||||
|
||||
/// Windows: file paths provided by the user must be valid WTF-8.
|
||||
/// https://wtf-8.codeberg.page/
|
||||
BadPathName,
|
||||
|
||||
/// On Windows, `\\server` or `\\server\share` was not found.
|
||||
NetworkNotFound,
|
||||
|
||||
PathAlreadyExists,
|
||||
|
||||
/// 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,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Return the canonicalized absolute pathname.
|
||||
///
|
||||
/// Expands all symbolic links and resolves references to `.`, `..`, and
|
||||
/// extra `/` characters in `pathname`.
|
||||
///
|
||||
/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
///
|
||||
/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
|
||||
///
|
||||
/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
|
||||
///
|
||||
/// See also `realpathZ` and `realpathW`.
|
||||
///
|
||||
/// * 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.
|
||||
///
|
||||
/// Calling this function is usually a bug.
|
||||
pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
|
||||
if (native_os == .windows) {
|
||||
var pathname_w = try windows.sliceToPrefixedFileW(null, pathname);
|
||||
|
||||
const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data);
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
|
||||
return out_buffer[0..end_index];
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
@compileError("WASI does not support os.realpath");
|
||||
}
|
||||
const pathname_c = try toPosixPath(pathname);
|
||||
return realpathZ(&pathname_c, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `realpath` except `pathname` is null-terminated.
|
||||
///
|
||||
/// Calling this function is usually a bug.
|
||||
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
|
||||
if (native_os == .windows) {
|
||||
var pathname_w = try windows.cStrToPrefixedFileW(null, pathname);
|
||||
|
||||
const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data);
|
||||
|
||||
const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
|
||||
return out_buffer[0..end_index];
|
||||
} else if (native_os == .wasi and !builtin.link_libc) {
|
||||
return realpath(mem.sliceTo(pathname, 0), out_buffer);
|
||||
}
|
||||
if (!builtin.link_libc) {
|
||||
const flags: O = switch (native_os) {
|
||||
.linux => .{
|
||||
.NONBLOCK = true,
|
||||
.CLOEXEC = true,
|
||||
.PATH = true,
|
||||
},
|
||||
else => .{
|
||||
.NONBLOCK = true,
|
||||
.CLOEXEC = true,
|
||||
},
|
||||
};
|
||||
const fd = openZ(pathname, flags, 0) catch |err| switch (err) {
|
||||
error.FileLocksNotSupported => unreachable,
|
||||
error.WouldBlock => unreachable,
|
||||
error.FileBusy => unreachable, // not asking for write permissions
|
||||
else => |e| return e,
|
||||
};
|
||||
defer close(fd);
|
||||
|
||||
return std.os.getFdPath(fd, out_buffer);
|
||||
}
|
||||
const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) {
|
||||
.SUCCESS => unreachable,
|
||||
.INVAL => unreachable,
|
||||
.BADF => unreachable,
|
||||
.FAULT => unreachable,
|
||||
.ACCES => return error.AccessDenied,
|
||||
.NOENT => return error.FileNotFound,
|
||||
.OPNOTSUPP => return error.NotSupported,
|
||||
.NOTDIR => return error.NotDir,
|
||||
.NAMETOOLONG => return error.NameTooLong,
|
||||
.LOOP => return error.SymLinkLoop,
|
||||
.IO => return error.InputOutput,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
};
|
||||
return mem.sliceTo(result_path, 0);
|
||||
}
|
||||
|
||||
/// Deprecated: use `realpathW2`.
|
||||
///
|
||||
/// Same as `realpath` except `pathname` is WTF16LE-encoded.
|
||||
///
|
||||
/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
///
|
||||
/// Calling this function is usually a bug.
|
||||
pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
|
||||
return fs.cwd().realpathW(pathname, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `realpath` except `pathname` is WTF16LE-encoded.
|
||||
///
|
||||
/// The result is encoded as WTF16LE.
|
||||
///
|
||||
/// Calling this function is usually a bug.
|
||||
pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 {
|
||||
return fs.cwd().realpathW2(pathname, out_buffer);
|
||||
}
|
||||
|
||||
/// Spurious wakeups are possible and no precision of timing is guaranteed.
|
||||
pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
|
||||
var req = timespec{
|
||||
|
||||
@@ -786,7 +786,9 @@ test glibcVerFromLinkName {
|
||||
}
|
||||
|
||||
fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
|
||||
var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
|
||||
const cwd: Io.Dir = .cwd();
|
||||
|
||||
var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
|
||||
error.NameTooLong => return error.Unexpected,
|
||||
error.BadPathName => return error.Unexpected,
|
||||
error.DeviceBusy => return error.Unexpected,
|
||||
@@ -805,7 +807,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
|
||||
error.Unexpected => |e| return e,
|
||||
error.Canceled => |e| return e,
|
||||
};
|
||||
defer dir.close();
|
||||
defer dir.close(io);
|
||||
|
||||
// Now we have a candidate for the path to libc shared object. In
|
||||
// the past, we used readlink() here because the link name would
|
||||
|
||||
Reference in New Issue
Block a user