std: all Dir functions moved to std.Io

This commit is contained in:
Andrew Kelley 2025-12-03 20:37:43 -08:00
parent d113257d42
commit 48d70cfc38
24 changed files with 3064 additions and 3651 deletions

View file

@ -437,8 +437,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

View file

@ -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\"", .{});
@ -1146,10 +1150,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});
@ -1159,13 +1166,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" },
@ -1176,9 +1183,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 |_| {}
@ -1574,6 +1581,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(.{
@ -1583,7 +1592,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),
});

View file

@ -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);

View file

@ -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.

View file

@ -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();

View file

@ -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;
}

View file

@ -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));

View file

@ -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 {

View file

@ -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;
@ -1694,19 +1696,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" }),
});
@ -1717,7 +1722,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" }),
});
@ -1793,7 +1798,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);
@ -1826,18 +1831,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.
},
@ -1949,18 +1954,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),
});

View file

@ -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);

View file

@ -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,
}),
}
}

View file

@ -662,16 +662,27 @@ pub const VTable = struct {
conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) 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,

File diff suppressed because it is too large Load diff

View file

@ -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;

File diff suppressed because it is too large Load diff

View file

@ -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();
}

View file

@ -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`.

View file

@ -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();
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -2022,21 +2022,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,

View file

@ -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.indexOfScalar(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.indexOfScalar(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.indexOfScalar(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.indexOfScalar(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,

File diff suppressed because it is too large Load diff

View file

@ -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