authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-03 20:37:43-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:07-08:00
logd1d2c37af26902f953b2b72335b326c4b01e3bb2
treeff74335c6b9eda252c6c4970d49f36bd6f904537
parent81214278ca14b832e53876feb70fa3c072c14dd6

std: all Dir functions moved to std.Io


24 files changed, 3226 insertions(+), 3812 deletions(-)

CMakeLists.txt-2
......@@ -436,8 +436,6 @@ set(ZIG_STAGE2_SOURCES
436436 lib/std/fmt.zig
437437 lib/std/fmt/parse_float.zig
438438 lib/std/fs.zig
439 lib/std/fs/AtomicFile.zig
440 lib/std/fs/Dir.zig
441439 lib/std/fs/File.zig
442440 lib/std/fs/get_app_data_dir.zig
443441 lib/std/fs/path.zig
build.zig+22-13
......@@ -1,18 +1,20 @@
11const std = @import("std");
22const builtin = std.builtin;
3const tests = @import("test/tests.zig");
43const BufMap = std.BufMap;
54const mem = std.mem;
6const io = std.io;
75const fs = std.fs;
86const InstallDirectoryOptions = std.Build.InstallDirectoryOptions;
97const assert = std.debug.assert;
8const Io = std.Io;
9
10const tests = @import("test/tests.zig");
1011const DevEnv = @import("src/dev.zig").Env;
11const ValueInterpretMode = enum { direct, by_name };
1212
1313const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 };
1414const stack_size = 46 * 1024 * 1024;
1515
16const ValueInterpretMode = enum { direct, by_name };
17
1618pub fn build(b: *std.Build) !void {
1719 const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;
1820 const target = b.standardTargetOptions(.{
......@@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void {
306308
307309 if (enable_llvm) {
308310 const cmake_cfg = if (static_llvm) null else blk: {
311 const io = b.graph.io;
312 const cwd: Io.Dir = .cwd();
309313 if (findConfigH(b, config_h_path_option)) |config_h_path| {
310 const file_contents = fs.cwd().readFileAlloc(config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
314 const file_contents = cwd.readFileAlloc(io, config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable;
311315 break :blk parseConfigH(b, file_contents);
312316 } else {
313317 std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{});
......@@ -1153,10 +1157,13 @@ const CMakeConfig = struct {
11531157const max_config_h_bytes = 1 * 1024 * 1024;
11541158
11551159fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
1160 const io = b.graph.io;
1161 const cwd: Io.Dir = .cwd();
1162
11561163 if (config_h_path_option) |path| {
1157 var config_h_or_err = fs.cwd().openFile(path, .{});
1164 var config_h_or_err = cwd.openFile(io, path, .{});
11581165 if (config_h_or_err) |*file| {
1159 file.close();
1166 file.close(io);
11601167 return path;
11611168 } else |_| {
11621169 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 {
11661173
11671174 var check_dir = fs.path.dirname(b.graph.zig_exe).?;
11681175 while (true) {
1169 var dir = fs.cwd().openDir(check_dir, .{}) catch unreachable;
1170 defer dir.close();
1176 var dir = cwd.openDir(io, check_dir, .{}) catch unreachable;
1177 defer dir.close(io);
11711178
11721179 // Check if config.h is present in dir
1173 var config_h_or_err = dir.openFile("config.h", .{});
1180 var config_h_or_err = dir.openFile(io, "config.h", .{});
11741181 if (config_h_or_err) |*file| {
1175 file.close();
1182 file.close(io);
11761183 return fs.path.join(
11771184 b.allocator,
11781185 &[_][]const u8{ check_dir, "config.h" },
......@@ -1183,9 +1190,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 {
11831190 }
11841191
11851192 // Check if we reached the source root by looking for .git, and bail if so
1186 var git_dir_or_err = dir.openDir(".git", .{});
1193 var git_dir_or_err = dir.openDir(io, ".git", .{});
11871194 if (git_dir_or_err) |*git_dir| {
1188 git_dir.close();
1195 git_dir.close(io);
11891196 return null;
11901197 } else |_| {}
11911198
......@@ -1581,6 +1588,8 @@ const llvm_libs_xtensa = [_][]const u8{
15811588};
15821589
15831590fn generateLangRef(b: *std.Build) std.Build.LazyPath {
1591 const io = b.graph.io;
1592
15841593 const doctest_exe = b.addExecutable(.{
15851594 .name = "doctest",
15861595 .root_module = b.createModule(.{
......@@ -1590,7 +1599,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
15901599 }),
15911600 });
15921601
1593 var dir = b.build_root.handle.openDir("doc/langref", .{ .iterate = true }) catch |err| {
1602 var dir = b.build_root.handle.openDir(io, "doc/langref", .{ .iterate = true }) catch |err| {
15941603 std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{
15951604 b.build_root, @errorName(err),
15961605 });
lib/compiler/build_runner.zig+8-6
......@@ -53,24 +53,26 @@ pub fn main() !void {
5353 const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{});
5454 const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{});
5555
56 const cwd: Io.Dir = .cwd();
57
5658 const zig_lib_directory: std.Build.Cache.Directory = .{
5759 .path = zig_lib_dir,
58 .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}),
60 .handle = try cwd.openDir(io, zig_lib_dir, .{}),
5961 };
6062
6163 const build_root_directory: std.Build.Cache.Directory = .{
6264 .path = build_root,
63 .handle = try std.fs.cwd().openDir(build_root, .{}),
65 .handle = try cwd.openDir(io, build_root, .{}),
6466 };
6567
6668 const local_cache_directory: std.Build.Cache.Directory = .{
6769 .path = cache_root,
68 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
70 .handle = try cwd.makeOpenPath(io, cache_root, .{}),
6971 };
7072
7173 const global_cache_directory: std.Build.Cache.Directory = .{
7274 .path = global_cache_root,
73 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
75 .handle = try cwd.makeOpenPath(io, global_cache_root, .{}),
7476 };
7577
7678 var graph: std.Build.Graph = .{
......@@ -79,7 +81,7 @@ pub fn main() !void {
7981 .cache = .{
8082 .io = io,
8183 .gpa = arena,
82 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
84 .manifest_dir = try local_cache_directory.handle.makeOpenPath(io, "h", .{}),
8385 },
8486 .zig_exe = zig_exe,
8587 .env_map = try process.getEnvMap(arena),
......@@ -92,7 +94,7 @@ pub fn main() !void {
9294 .time_report = false,
9395 };
9496
95 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
97 graph.cache.addPrefix(.{ .path = null, .handle = cwd });
9698 graph.cache.addPrefix(build_root_directory);
9799 graph.cache.addPrefix(local_cache_directory);
98100 graph.cache.addPrefix(global_cache_directory);
lib/std/Build.zig+3-4
......@@ -1700,9 +1700,8 @@ pub fn addCheckFile(
17001700}
17011701
17021702pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Dir.StatFileError)!void {
1703 if (b.verbose) {
1704 log.info("truncate {s}", .{dest_path});
1705 }
1703 const io = b.graph.io;
1704 if (b.verbose) log.info("truncate {s}", .{dest_path});
17061705 const cwd = fs.cwd();
17071706 var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) {
17081707 error.FileNotFound => blk: {
......@@ -1713,7 +1712,7 @@ pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Di
17131712 },
17141713 else => |e| return e,
17151714 };
1716 src_file.close();
1715 src_file.close(io);
17171716}
17181717
17191718/// References a file or directory relative to the source root.
lib/std/Build/Cache.zig+43-40
......@@ -8,7 +8,6 @@ const builtin = @import("builtin");
88const std = @import("std");
99const Io = std.Io;
1010const crypto = std.crypto;
11const fs = std.fs;
1211const assert = std.debug.assert;
1312const testing = std.testing;
1413const mem = std.mem;
......@@ -18,7 +17,7 @@ const log = std.log.scoped(.cache);
1817
1918gpa: Allocator,
2019io: Io,
21manifest_dir: fs.Dir,
20manifest_dir: Io.Dir,
2221hash: HashHelper = .{},
2322/// This value is accessed from multiple threads, protected by mutex.
2423recent_problematic_timestamp: Io.Timestamp = .zero,
......@@ -71,7 +70,7 @@ const PrefixedPath = struct {
7170
7271fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath {
7372 const gpa = cache.gpa;
74 const resolved_path = try fs.path.resolve(gpa, &.{file_path});
73 const resolved_path = try std.fs.path.resolve(gpa, &.{file_path});
7574 errdefer gpa.free(resolved_path);
7675 return findPrefixResolved(cache, resolved_path);
7776}
......@@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath {
102101}
103102
104103fn getPrefixSubpath(allocator: Allocator, prefix: []const u8, path: []u8) ![]u8 {
105 const relative = try fs.path.relative(allocator, prefix, path);
104 const relative = try std.fs.path.relative(allocator, prefix, path);
106105 errdefer allocator.free(relative);
107 var component_iterator = fs.path.NativeComponentIterator.init(relative);
106 var component_iterator = std.fs.path.NativeComponentIterator.init(relative);
108107 if (component_iterator.root() != null) {
109108 return error.NotASubPath;
110109 }
......@@ -145,17 +144,17 @@ pub const File = struct {
145144 max_file_size: ?usize,
146145 /// Populated if the user calls `addOpenedFile`.
147146 /// The handle is not owned here.
148 handle: ?fs.File,
147 handle: ?Io.File,
149148 stat: Stat,
150149 bin_digest: BinDigest,
151150 contents: ?[]const u8,
152151
153152 pub const Stat = struct {
154 inode: fs.File.INode,
153 inode: Io.File.INode,
155154 size: u64,
156155 mtime: Io.Timestamp,
157156
158 pub fn fromFs(fs_stat: fs.File.Stat) Stat {
157 pub fn fromFs(fs_stat: Io.File.Stat) Stat {
159158 return .{
160159 .inode = fs_stat.inode,
161160 .size = fs_stat.size,
......@@ -178,7 +177,7 @@ pub const File = struct {
178177 file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new;
179178 }
180179
181 pub fn updateHandle(file: *File, new_handle: ?fs.File) void {
180 pub fn updateHandle(file: *File, new_handle: ?Io.File) void {
182181 const handle = new_handle orelse return;
183182 file.handle = handle;
184183 }
......@@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest {
293292}
294293
295294pub const Lock = struct {
296 manifest_file: fs.File,
295 manifest_file: Io.File,
297296
298 pub fn release(lock: *Lock) void {
297 pub fn release(lock: *Lock, io: Io) void {
299298 if (builtin.os.tag == .windows) {
300299 // Windows does not guarantee that locks are immediately unlocked when
301300 // the file handle is closed. See LockFileEx documentation.
302301 lock.manifest_file.unlock();
303302 }
304303
305 lock.manifest_file.close();
304 lock.manifest_file.close(io);
306305 lock.* = undefined;
307306 }
308307};
......@@ -311,7 +310,7 @@ pub const Manifest = struct {
311310 cache: *Cache,
312311 /// Current state for incremental hashing.
313312 hash: HashHelper,
314 manifest_file: ?fs.File,
313 manifest_file: ?Io.File,
315314 manifest_dirty: bool,
316315 /// Set this flag to true before calling hit() in order to indicate that
317316 /// upon a cache hit, the code using the cache will not modify the files
......@@ -332,9 +331,9 @@ pub const Manifest = struct {
332331
333332 pub const Diagnostic = union(enum) {
334333 none,
335 manifest_create: fs.File.OpenError,
336 manifest_read: fs.File.ReadError,
337 manifest_lock: fs.File.LockError,
334 manifest_create: Io.File.OpenError,
335 manifest_read: Io.File.Reader.Error,
336 manifest_lock: Io.File.LockError,
338337 file_open: FileOp,
339338 file_stat: FileOp,
340339 file_read: FileOp,
......@@ -393,10 +392,10 @@ pub const Manifest = struct {
393392 }
394393
395394 /// Same as `addFilePath` except the file has already been opened.
396 pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?fs.File, max_file_size: ?usize) !usize {
395 pub fn addOpenedFile(m: *Manifest, path: Path, handle: ?Io.File, max_file_size: ?usize) !usize {
397396 const gpa = m.cache.gpa;
398397 try m.files.ensureUnusedCapacity(gpa, 1);
399 const resolved_path = try fs.path.resolve(gpa, &.{
398 const resolved_path = try std.fs.path.resolve(gpa, &.{
400399 path.root_dir.path orelse ".",
401400 path.subPathOrDot(),
402401 });
......@@ -417,7 +416,7 @@ pub const Manifest = struct {
417416 return addFileInner(self, prefixed_path, null, max_file_size);
418417 }
419418
420 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?fs.File, max_file_size: ?usize) usize {
419 fn addFileInner(self: *Manifest, prefixed_path: PrefixedPath, handle: ?Io.File, max_file_size: ?usize) usize {
421420 const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{});
422421 if (gop.found_existing) {
423422 self.cache.gpa.free(prefixed_path.sub_path);
......@@ -460,7 +459,7 @@ pub const Manifest = struct {
460459 }
461460 }
462461
463 pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
462 pub fn addDepFile(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
464463 assert(self.manifest_file == null);
465464 return self.addDepFileMaybePost(dir, dep_file_sub_path);
466465 }
......@@ -702,7 +701,7 @@ pub const Manifest = struct {
702701 const file_path = iter.rest();
703702
704703 const stat_size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
705 const stat_inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
704 const stat_inode = fmt.parseInt(Io.File.INode, inode, 10) catch return error.InvalidFormat;
706705 const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
707706 const file_bin_digest = b: {
708707 if (digest_str.len != hex_digest_len) return error.InvalidFormat;
......@@ -772,7 +771,7 @@ pub const Manifest = struct {
772771 return error.CacheCheckFailed;
773772 },
774773 };
775 defer this_file.close();
774 defer this_file.close(io);
776775
777776 const actual_stat = this_file.stat() catch |err| {
778777 self.diagnostic = .{ .file_stat = .{
......@@ -879,7 +878,7 @@ pub const Manifest = struct {
879878 error.Canceled => return error.Canceled,
880879 else => return true,
881880 };
882 defer file.close();
881 defer file.close(io);
883882
884883 // Save locally and also save globally (we still hold the global lock).
885884 const stat = file.stat() catch |err| switch (err) {
......@@ -894,18 +893,20 @@ pub const Manifest = struct {
894893 }
895894
896895 fn populateFileHash(self: *Manifest, ch_file: *File) !void {
896 const io = self.cache.io;
897
897898 if (ch_file.handle) |handle| {
898899 return populateFileHashHandle(self, ch_file, handle);
899900 } else {
900901 const pp = ch_file.prefixed_path;
901902 const dir = self.cache.prefixes()[pp.prefix].handle;
902903 const handle = try dir.openFile(pp.sub_path, .{});
903 defer handle.close();
904 defer handle.close(io);
904905 return populateFileHashHandle(self, ch_file, handle);
905906 }
906907 }
907908
908 fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void {
909 fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: Io.File) !void {
909910 const actual_stat = try handle.stat();
910911 ch_file.stat = .{
911912 .size = actual_stat.size,
......@@ -1064,12 +1065,12 @@ pub const Manifest = struct {
10641065 self.hash.hasher.update(&new_file.bin_digest);
10651066 }
10661067
1067 pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
1068 pub fn addDepFilePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
10681069 assert(self.manifest_file != null);
10691070 return self.addDepFileMaybePost(dir, dep_file_sub_path);
10701071 }
10711072
1072 fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
1073 fn addDepFileMaybePost(self: *Manifest, dir: Io.Dir, dep_file_sub_path: []const u8) !void {
10731074 const gpa = self.cache.gpa;
10741075 const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max));
10751076 defer gpa.free(dep_file_contents);
......@@ -1148,7 +1149,7 @@ pub const Manifest = struct {
11481149 }
11491150 }
11501151
1151 fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void {
1152 fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void {
11521153 try fw.interface.writeAll(manifest_header ++ "\n");
11531154 for (self.files.keys()) |file| {
11541155 try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
......@@ -1214,13 +1215,15 @@ pub const Manifest = struct {
12141215 /// `Manifest.hit` must be called first.
12151216 /// Don't forget to call `writeManifest` before this!
12161217 pub fn deinit(self: *Manifest) void {
1218 const io = self.cache.io;
1219
12171220 if (self.manifest_file) |file| {
12181221 if (builtin.os.tag == .windows) {
12191222 // See Lock.release for why this is required on Windows
12201223 file.unlock();
12211224 }
12221225
1223 file.close();
1226 file.close(io);
12241227 }
12251228 for (self.files.keys()) |*file| {
12261229 file.deinit(self.cache.gpa);
......@@ -1281,7 +1284,7 @@ pub const Manifest = struct {
12811284/// On operating systems that support symlinks, does a readlink. On other operating systems,
12821285/// uses the file contents. Windows supports symlinks but only with elevated privileges, so
12831286/// it is treated as not supporting symlinks.
1284pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1287pub fn readSmallFile(dir: Io.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
12851288 if (builtin.os.tag == .windows) {
12861289 return dir.readFile(sub_path, buffer);
12871290 } else {
......@@ -1293,7 +1296,7 @@ pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
12931296/// uses the file contents. Windows supports symlinks but only with elevated privileges, so
12941297/// it is treated as not supporting symlinks.
12951298/// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer.
1296pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void {
1299pub fn writeSmallFile(dir: Io.Dir, sub_path: []const u8, data: []const u8) !void {
12971300 assert(data.len <= 255);
12981301 if (builtin.os.tag == .windows) {
12991302 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
13021305 }
13031306}
13041307
1305fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void {
1308fn hashFile(file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.PReadError!void {
13061309 var buf: [1024]u8 = undefined;
13071310 var hasher = hasher_init;
13081311 var off: u64 = 0;
......@@ -1316,7 +1319,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
13161319}
13171320
13181321// Create/Write a file, close it, then grab its stat.mtime timestamp.
1319fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
1322fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp {
13201323 const test_out_file = "test-filetimestamp.tmp";
13211324
13221325 var file = try dir.createFile(test_out_file, .{
......@@ -1324,7 +1327,7 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
13241327 .truncate = true,
13251328 });
13261329 defer {
1327 file.close();
1330 file.close(io);
13281331 dir.deleteFile(test_out_file) catch {};
13291332 }
13301333
......@@ -1343,8 +1346,8 @@ test "cache file and then recall it" {
13431346 try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = "Hello, world!\n" });
13441347
13451348 // Wait for file timestamps to tick
1346 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1347 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1349 const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir);
1350 while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) {
13481351 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
13491352 }
13501353
......@@ -1358,7 +1361,7 @@ test "cache file and then recall it" {
13581361 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
13591362 };
13601363 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1361 defer cache.manifest_dir.close();
1364 defer cache.manifest_dir.close(io);
13621365
13631366 {
13641367 var ch = cache.obtain();
......@@ -1424,7 +1427,7 @@ test "check that changing a file makes cache fail" {
14241427 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
14251428 };
14261429 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1427 defer cache.manifest_dir.close();
1430 defer cache.manifest_dir.close(io);
14281431
14291432 {
14301433 var ch = cache.obtain();
......@@ -1484,7 +1487,7 @@ test "no file inputs" {
14841487 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
14851488 };
14861489 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1487 defer cache.manifest_dir.close();
1490 defer cache.manifest_dir.close(io);
14881491
14891492 {
14901493 var man = cache.obtain();
......@@ -1543,7 +1546,7 @@ test "Manifest with files added after initial hash work" {
15431546 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
15441547 };
15451548 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1546 defer cache.manifest_dir.close();
1549 defer cache.manifest_dir.close(io);
15471550
15481551 {
15491552 var ch = cache.obtain();
lib/std/Build/Cache/Directory.zig+6-4
......@@ -1,7 +1,9 @@
11const Directory = @This();
2
23const std = @import("../../std.zig");
3const assert = std.debug.assert;
4const Io = std.Io;
45const fs = std.fs;
6const assert = std.debug.assert;
57const fmt = std.fmt;
68const Allocator = std.mem.Allocator;
79
......@@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator;
911/// directly, but it is needed when passing the directory to a child process.
1012/// `null` means cwd.
1113path: ?[]const u8,
12handle: fs.Dir,
14handle: Io.Dir,
1315
1416pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
1517 return .{
......@@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory {
2123pub fn cwd() Directory {
2224 return .{
2325 .path = null,
24 .handle = fs.cwd(),
26 .handle = .cwd(),
2527 };
2628}
2729
......@@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void
6466}
6567
6668pub fn eql(self: Directory, other: Directory) bool {
67 return self.handle.fd == other.handle.fd;
69 return self.handle.handle == other.handle.handle;
6870}
lib/std/Build/Cache/Path.zig+12-12
......@@ -2,8 +2,8 @@ const Path = @This();
22
33const std = @import("../../std.zig");
44const Io = std.Io;
5const assert = std.debug.assert;
65const fs = std.fs;
6const assert = std.debug.assert;
77const Allocator = std.mem.Allocator;
88const Cache = std.Build.Cache;
99
......@@ -62,8 +62,8 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro
6262pub fn openFile(
6363 p: Path,
6464 sub_path: []const u8,
65 flags: fs.File.OpenFlags,
66) !fs.File {
65 flags: Io.File.OpenFlags,
66) !Io.File {
6767 var buf: [fs.max_path_bytes]u8 = undefined;
6868 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
6969 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
......@@ -76,8 +76,8 @@ pub fn openFile(
7676pub fn openDir(
7777 p: Path,
7878 sub_path: []const u8,
79 args: fs.Dir.OpenOptions,
80) fs.Dir.OpenError!fs.Dir {
79 args: Io.Dir.OpenOptions,
80) Io.Dir.OpenError!Io.Dir {
8181 var buf: [fs.max_path_bytes]u8 = undefined;
8282 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
8383 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
......@@ -87,7 +87,7 @@ pub fn openDir(
8787 return p.root_dir.handle.openDir(joined_path, args);
8888}
8989
90pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir {
90pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir {
9191 var buf: [fs.max_path_bytes]u8 = undefined;
9292 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
9393 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
9797 return p.root_dir.handle.makeOpenPath(joined_path, opts);
9898}
9999
100pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat {
100pub fn statFile(p: Path, sub_path: []const u8) !Io.Dir.Stat {
101101 var buf: [fs.max_path_bytes]u8 = undefined;
102102 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
103103 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 {
110110pub fn atomicFile(
111111 p: Path,
112112 sub_path: []const u8,
113 options: fs.Dir.AtomicFileOptions,
113 options: Io.Dir.AtomicFileOptions,
114114 buf: *[fs.max_path_bytes]u8,
115115) !fs.AtomicFile {
116116 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 {
180180}
181181
182182pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void {
183 if (std.fs.path.isAbsolute(self.sub_path)) {
183 if (fs.path.isAbsolute(self.sub_path)) {
184184 try writer.writeAll(self.sub_path);
185185 return;
186186 }
......@@ -225,9 +225,9 @@ pub const TableAdapter = struct {
225225
226226 pub fn hash(self: TableAdapter, a: Cache.Path) u32 {
227227 _ = self;
228 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.fd))) {
229 .pointer => @intFromPtr(a.root_dir.handle.fd),
230 .int => @as(u32, @bitCast(a.root_dir.handle.fd)),
228 const seed = switch (@typeInfo(@TypeOf(a.root_dir.handle.handle))) {
229 .pointer => @intFromPtr(a.root_dir.handle.handle),
230 .int => @as(u32, @bitCast(a.root_dir.handle.handle)),
231231 else => @compileError("unimplemented hash function"),
232232 };
233233 return @truncate(Hash.hash(seed, a.sub_path));
lib/std/Build/Step.zig+3-7
......@@ -510,20 +510,16 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u
510510 const io = b.graph.io;
511511 const src_path = src_lazy_path.getPath3(b, s);
512512 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
513 return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| {
514 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{
515 src_path, dest_path, err,
516 });
517 };
513 return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err|
514 return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err });
518515}
519516
520517/// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output.
521518pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus {
522519 const b = s.owner;
523520 try handleVerbose(b, null, &.{ "install", "-d", dest_path });
524 return std.fs.cwd().makePathStatus(dest_path) catch |err| {
521 return std.fs.cwd().makePathStatus(dest_path) catch |err|
525522 return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err });
526 };
527523}
528524
529525fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path {
lib/std/Build/Step/Compile.zig+21-14
......@@ -1,12 +1,15 @@
1const Compile = @This();
12const builtin = @import("builtin");
3
24const std = @import("std");
5const Io = std.Io;
36const mem = std.mem;
47const fs = std.fs;
58const assert = std.debug.assert;
69const panic = std.debug.panic;
710const StringHashMap = std.StringHashMap;
811const Sha256 = std.crypto.hash.sha2.Sha256;
9const Allocator = mem.Allocator;
12const Allocator = std.mem.Allocator;
1013const Step = std.Build.Step;
1114const LazyPath = std.Build.LazyPath;
1215const PkgConfigPkg = std.Build.PkgConfigPkg;
......@@ -15,7 +18,6 @@ const RunError = std.Build.RunError;
1518const Module = std.Build.Module;
1619const InstallDir = std.Build.InstallDir;
1720const GeneratedFile = std.Build.GeneratedFile;
18const Compile = @This();
1921const Path = std.Build.Cache.Path;
2022
2123pub const base_id: Step.Id = .compile;
......@@ -1561,19 +1563,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
15611563 }
15621564
15631565 // -I and -L arguments that appear after the last --mod argument apply to all modules.
1566 const cwd: Io.Dir = .cwd();
1567 const io = b.graph.io;
1568
15641569 for (b.search_prefixes.items) |search_prefix| {
1565 var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| {
1570 var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| {
15661571 return step.fail("unable to open prefix directory '{s}': {s}", .{
15671572 search_prefix, @errorName(err),
15681573 });
15691574 };
1570 defer prefix_dir.close();
1575 defer prefix_dir.close(io);
15711576
15721577 // Avoid passing -L and -I flags for nonexistent directories.
15731578 // This prevents a warning, that should probably be upgraded to an error in Zig's
15741579 // CLI parsing code, when the linker sees an -L directory that does not exist.
15751580
1576 if (prefix_dir.access("lib", .{})) |_| {
1581 if (prefix_dir.access(io, "lib", .{})) |_| {
15771582 try zig_args.appendSlice(&.{
15781583 "-L", b.pathJoin(&.{ search_prefix, "lib" }),
15791584 });
......@@ -1584,7 +1589,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
15841589 }),
15851590 }
15861591
1587 if (prefix_dir.access("include", .{})) |_| {
1592 if (prefix_dir.access(io, "include", .{})) |_| {
15881593 try zig_args.appendSlice(&.{
15891594 "-I", b.pathJoin(&.{ search_prefix, "include" }),
15901595 });
......@@ -1660,7 +1665,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
16601665 args_length += arg.len + 1; // +1 to account for null terminator
16611666 }
16621667 if (args_length >= 30 * 1024) {
1663 try b.cache_root.handle.makePath("args");
1668 try b.cache_root.handle.makePath(io, "args");
16641669
16651670 const args_to_escape = zig_args.items[2..];
16661671 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 {
16931698 _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash});
16941699
16951700 const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
1696 if (b.cache_root.handle.access(args_file, .{})) |_| {
1701 if (b.cache_root.handle.access(io, args_file, .{})) |_| {
16971702 // The args file is already present from a previous run.
16981703 } else |err| switch (err) {
16991704 error.FileNotFound => {
1700 try b.cache_root.handle.makePath("tmp");
1705 try b.cache_root.handle.makePath(io, "tmp");
17011706 const rand_int = std.crypto.random.int(u64);
17021707 const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1703 try b.cache_root.handle.writeFile(.{ .sub_path = tmp_path, .data = args });
1704 defer b.cache_root.handle.deleteFile(tmp_path) catch {
1708 try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
1709 defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
17051710 // It's fine if the temporary file can't be cleaned up.
17061711 };
1707 b.cache_root.handle.rename(tmp_path, args_file) catch |rename_err| switch (rename_err) {
1712 b.cache_root.handle.rename(io, tmp_path, args_file) catch |rename_err| switch (rename_err) {
17081713 error.PathAlreadyExists => {
17091714 // The args file was created by another concurrent build process.
17101715 },
......@@ -1816,18 +1821,20 @@ pub fn doAtomicSymLinks(
18161821 filename_name_only: []const u8,
18171822) !void {
18181823 const b = step.owner;
1824 const io = b.graph.io;
18191825 const out_dir = fs.path.dirname(output_path) orelse ".";
18201826 const out_basename = fs.path.basename(output_path);
18211827 // sym link for libfoo.so.1 to libfoo.so.1.2.3
18221828 const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only });
1823 fs.cwd().atomicSymLink(out_basename, major_only_path, .{}) catch |err| {
1829 const cwd: Io.Dir = .cwd();
1830 cwd.atomicSymLink(io, out_basename, major_only_path, .{}) catch |err| {
18241831 return step.fail("unable to symlink {s} -> {s}: {s}", .{
18251832 major_only_path, out_basename, @errorName(err),
18261833 });
18271834 };
18281835 // sym link for libfoo.so to libfoo.so.1
18291836 const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only });
1830 fs.cwd().atomicSymLink(filename_major_only, name_only_path, .{}) catch |err| {
1837 cwd.atomicSymLink(io, filename_major_only, name_only_path, .{}) catch |err| {
18311838 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
18321839 name_only_path, filename_major_only, @errorName(err),
18331840 });
lib/std/Build/Step/InstallDir.zig+3-4
......@@ -58,16 +58,15 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {
5858fn make(step: *Step, options: Step.MakeOptions) !void {
5959 _ = options;
6060 const b = step.owner;
61 const io = b.graph.io;
6162 const install_dir: *InstallDir = @fieldParentPtr("step", step);
6263 step.clearWatchInputs();
6364 const arena = b.allocator;
6465 const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir);
6566 const src_dir_path = install_dir.options.source_dir.getPath3(b, step);
6667 const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir);
67 var src_dir = src_dir_path.root_dir.handle.openDir(src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
68 return step.fail("unable to open source directory '{f}': {s}", .{
69 src_dir_path, @errorName(err),
70 });
68 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
69 return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err });
7170 };
7271 defer src_dir.close();
7372 var it = try src_dir.walk(arena);
lib/std/Build/Step/Options.zig+20-22
......@@ -441,6 +441,7 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
441441 _ = make_options;
442442
443443 const b = step.owner;
444 const io = b.graph.io;
444445 const options: *Options = @fieldParentPtr("step", step);
445446
446447 for (options.args.items) |item| {
......@@ -468,18 +469,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
468469
469470 // Optimize for the hot path. Stat the file, and if it already exists,
470471 // cache hit.
471 if (b.cache_root.handle.access(sub_path, .{})) |_| {
472 if (b.cache_root.handle.access(io, sub_path, .{})) |_| {
472473 // This is the hot path, success.
473474 step.result_cached = true;
474475 return;
475476 } else |outer_err| switch (outer_err) {
476477 error.FileNotFound => {
477478 const sub_dirname = fs.path.dirname(sub_path).?;
478 b.cache_root.handle.makePath(sub_dirname) catch |e| {
479 return step.fail("unable to make path '{f}{s}': {s}", .{
480 b.cache_root, sub_dirname, @errorName(e),
481 });
482 };
479 b.cache_root.handle.makePath(io, sub_dirname) catch |e|
480 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
483481
484482 const rand_int = std.crypto.random.int(u64);
485483 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
......@@ -487,40 +485,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
487485 basename;
488486 const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
489487
490 b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| {
491 return step.fail("unable to make temporary directory '{f}{s}': {s}", .{
492 b.cache_root, tmp_sub_path_dirname, @errorName(err),
488 b.cache_root.handle.makePath(io, tmp_sub_path_dirname) catch |err| {
489 return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
490 b.cache_root, tmp_sub_path_dirname, err,
493491 });
494492 };
495493
496 b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
497 return step.fail("unable to write options to '{f}{s}': {s}", .{
498 b.cache_root, tmp_sub_path, @errorName(err),
494 b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
495 return step.fail("unable to write options to '{f}{s}': {t}", .{
496 b.cache_root, tmp_sub_path, err,
499497 });
500498 };
501499
502 b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) {
500 b.cache_root.handle.rename(io, tmp_sub_path, sub_path) catch |err| switch (err) {
503501 error.PathAlreadyExists => {
504502 // Other process beat us to it. Clean up the temp file.
505 b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| {
506 try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{
507 b.cache_root, tmp_sub_path, @errorName(e),
503 b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
504 try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
505 b.cache_root, tmp_sub_path, e,
508506 });
509507 };
510508 step.result_cached = true;
511509 return;
512510 },
513511 else => {
514 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{
515 b.cache_root, tmp_sub_path,
516 b.cache_root, sub_path,
517 @errorName(err),
512 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
513 b.cache_root, tmp_sub_path,
514 b.cache_root, sub_path,
515 err,
518516 });
519517 },
520518 };
521519 },
522 else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{
523 b.cache_root, sub_path, @errorName(e),
520 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
521 b.cache_root, sub_path, e,
524522 }),
525523 }
526524}
lib/std/Io.zig+19-8
......@@ -662,16 +662,27 @@ pub const VTable = struct {
662662 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
663663 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
664664
665 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,
667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
665 dirMake: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakeError!void,
666 dirMakePath: *const fn (?*anyopaque, Dir, []const u8, Dir.Mode) Dir.MakePathError!Dir.MakePathStatus,
667 dirMakeOpenPath: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
668668 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
669 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
670 dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
671 dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
672 dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
673 dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
669 dirStatPath: *const fn (?*anyopaque, Dir, []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
670 dirAccess: *const fn (?*anyopaque, Dir, []const u8, Dir.AccessOptions) Dir.AccessError!void,
671 dirCreateFile: *const fn (?*anyopaque, Dir, []const u8, File.CreateFlags) File.OpenError!File,
672 dirOpenFile: *const fn (?*anyopaque, Dir, []const u8, File.OpenFlags) File.OpenError!File,
673 dirOpenDir: *const fn (?*anyopaque, Dir, []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
674674 dirClose: *const fn (?*anyopaque, Dir) void,
675 dirRead: *const fn (?*anyopaque, *Dir.Reader, []Dir.Entry) Dir.Reader.Error!usize,
676 dirRealPath: *const fn (?*anyopaque, Dir, path_name: []const u8, out_buffer: []u8) Dir.RealPathError!usize,
677 dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
678 dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
679 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
680 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.RenameError!void,
681 dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
682 dirSetMode: *const fn (?*anyopaque, Dir, File.Mode) Dir.SetModeError!void,
683 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
684 dirSetPermissions: *const fn (?*anyopaque, Dir, Dir.Permissions) Dir.SetPermissionsError!void,
685
675686 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
676687 fileClose: *const fn (?*anyopaque, File) void,
677688 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
lib/std/Io/Dir.zig+1202-2
......@@ -6,12 +6,20 @@ const native_os = builtin.os.tag;
66const std = @import("../std.zig");
77const Io = std.Io;
88const File = Io.File;
9const assert = std.debug.assert;
10const Allocator = std.mem.Allocator;
911
1012handle: Handle,
1113
1214pub const Mode = Io.File.Mode;
1315pub const default_mode: Mode = 0o755;
1416
17pub const Entry = struct {
18 name: []const u8,
19 kind: File.Kind,
20 inode: File.INode,
21};
22
1523/// Returns a handle to the current working directory.
1624///
1725/// It is not opened with iteration capability. Iterating over the result is
......@@ -20,6 +28,8 @@ pub const default_mode: Mode = 0o755;
2028/// Closing the returned `Dir` is checked illegal behavior.
2129///
2230/// On POSIX targets, this function is comptime-callable.
31///
32/// On WASI, the value this returns is application-configurable.
2333pub fn cwd() Dir {
2434 return switch (native_os) {
2535 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
......@@ -28,6 +38,270 @@ pub fn cwd() Dir {
2838 };
2939}
3040
41pub const Reader = struct {
42 dir: Dir,
43 state: State,
44 /// Stores I/O implementation specific data.
45 buffer: [2048]u8 align(@alignOf(usize)),
46 index: usize,
47
48 pub const State = enum {
49 /// Indicates the next call to `read` should rewind and start over the
50 /// directory listing.
51 reset,
52 reading,
53 finished,
54 };
55
56 pub const Error = error{
57 AccessDenied,
58 PermissionDenied,
59 SystemResources,
60 } || Io.UnexpectedError || Io.Cancelable;
61
62 pub fn init(dir: Dir) Reader {
63 return .{
64 .dir = dir,
65 .state = .reset,
66 .index = 0,
67 .buffer = undefined,
68 };
69 }
70
71 pub fn read(r: *Reader, io: Io, buffer: []Entry) Error!usize {
72 return io.vtable.dirRead(io.userdata, r, buffer);
73 }
74};
75
76pub const Iterator = struct {
77 reader: Reader,
78 buffer: [32]Entry,
79 /// Index of next entry in `buffer`.
80 index: usize,
81 /// Fill position of `buffer`.
82 end: usize,
83
84 pub const Error = Reader.Error;
85
86 pub fn init(dir: Dir, reader_state: Reader.State) Iterator {
87 return .{
88 .reader = .{
89 .dir = dir,
90 .state = reader_state,
91 .index = 0,
92 .buffer = undefined,
93 },
94 .buffer = undefined,
95 .index = 0,
96 .end = 0,
97 };
98 }
99
100 pub fn next(it: *Iterator, io: Io) Error!?Entry {
101 if (it.end - it.index == 0) {
102 if (it.reader.state == .finished) return null;
103 it.end = try it.reader.read(io, &it.buffer);
104 it.index = 0;
105 if (it.end - it.index == 0) {
106 assert(it.reader.state == .finished);
107 return null;
108 }
109 }
110 const index = it.index;
111 it.index = index + 1;
112 return it.buffer[index];
113 }
114};
115
116pub fn iterate(dir: Dir) Iterator {
117 return .init(dir, .reset);
118}
119
120/// Like `iterate`, but will not reset the directory cursor before the first
121/// iteration. This should only be used in cases where it is known that the
122/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
123pub fn iterateAssumeFirstIteration(dir: Dir) Iterator {
124 return .init(dir, .reading);
125}
126
127pub const SelectiveWalker = struct {
128 stack: std.ArrayList(Walker.StackItem),
129 name_buffer: std.ArrayList(u8),
130 allocator: Allocator,
131
132 pub const Error = Io.Dir.Iterator.Error || Allocator.Error;
133
134 /// After each call to this function, and on deinit(), the memory returned
135 /// from this function becomes invalid. A copy must be made in order to keep
136 /// a reference to the path.
137 pub fn next(self: *SelectiveWalker) Error!?Walker.Entry {
138 while (self.stack.items.len > 0) {
139 const top = &self.stack.items[self.stack.items.len - 1];
140 var dirname_len = top.dirname_len;
141 if (top.iter.next() catch |err| {
142 // If we get an error, then we want the user to be able to continue
143 // walking if they want, which means that we need to pop the directory
144 // that errored from the stack. Otherwise, all future `next` calls would
145 // likely just fail with the same error.
146 var item = self.stack.pop().?;
147 if (self.stack.items.len != 0) {
148 item.iter.dir.close();
149 }
150 return err;
151 }) |entry| {
152 self.name_buffer.shrinkRetainingCapacity(dirname_len);
153 if (self.name_buffer.items.len != 0) {
154 try self.name_buffer.append(self.allocator, std.fs.path.sep);
155 dirname_len += 1;
156 }
157 try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
158 self.name_buffer.appendSliceAssumeCapacity(entry.name);
159 self.name_buffer.appendAssumeCapacity(0);
160 const walker_entry: Walker.Entry = .{
161 .dir = top.iter.dir,
162 .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
163 .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
164 .kind = entry.kind,
165 };
166 return walker_entry;
167 } else {
168 var item = self.stack.pop().?;
169 if (self.stack.items.len != 0) {
170 item.iter.dir.close();
171 }
172 }
173 }
174 return null;
175 }
176
177 /// Traverses into the directory, continuing walking one level down.
178 pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void {
179 if (entry.kind != .directory) {
180 @branchHint(.cold);
181 return;
182 }
183
184 var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| {
185 switch (err) {
186 error.NameTooLong => unreachable,
187 else => |e| return e,
188 }
189 };
190 errdefer new_dir.close();
191
192 try self.stack.append(self.allocator, .{
193 .iter = new_dir.iterateAssumeFirstIteration(),
194 .dirname_len = self.name_buffer.items.len - 1,
195 });
196 }
197
198 pub fn deinit(self: *SelectiveWalker) void {
199 self.name_buffer.deinit(self.allocator);
200 self.stack.deinit(self.allocator);
201 }
202
203 /// Leaves the current directory, continuing walking one level up.
204 /// If the current entry is a directory entry, then the "current directory"
205 /// will pertain to that entry if `enter` is called before `leave`.
206 pub fn leave(self: *SelectiveWalker) void {
207 var item = self.stack.pop().?;
208 if (self.stack.items.len != 0) {
209 @branchHint(.likely);
210 item.iter.dir.close();
211 }
212 }
213};
214
215/// Recursively iterates over a directory, but requires the user to
216/// opt-in to recursing into each directory entry.
217///
218/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
219///
220/// `Walker.deinit` releases allocated memory and directory handles.
221///
222/// The order of returned file system entries is undefined.
223///
224/// `dir` will not be closed after walking it.
225///
226/// See also `walk`.
227pub fn walkSelectively(dir: Dir, allocator: Allocator) !SelectiveWalker {
228 var stack: std.ArrayList(Walker.StackItem) = .empty;
229
230 try stack.append(allocator, .{
231 .iter = dir.iterate(),
232 .dirname_len = 0,
233 });
234
235 return .{
236 .stack = stack,
237 .name_buffer = .{},
238 .allocator = allocator,
239 };
240}
241
242pub const Walker = struct {
243 inner: SelectiveWalker,
244
245 pub const Entry = struct {
246 /// The containing directory. This can be used to operate directly on `basename`
247 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
248 /// The directory remains open until `next` or `deinit` is called.
249 dir: Dir,
250 basename: [:0]const u8,
251 path: [:0]const u8,
252 kind: Dir.Entry.Kind,
253
254 /// Returns the depth of the entry relative to the initial directory.
255 /// Returns 1 for a direct child of the initial directory, 2 for an entry
256 /// within a direct child of the initial directory, etc.
257 pub fn depth(self: Walker.Entry) usize {
258 return std.mem.countScalar(u8, self.path, std.fs.path.sep) + 1;
259 }
260 };
261
262 const StackItem = struct {
263 iter: Dir.Iterator,
264 dirname_len: usize,
265 };
266
267 /// After each call to this function, and on deinit(), the memory returned
268 /// from this function becomes invalid. A copy must be made in order to keep
269 /// a reference to the path.
270 pub fn next(self: *Walker) !?Walker.Entry {
271 const entry = try self.inner.next();
272 if (entry != null and entry.?.kind == .directory) {
273 try self.inner.enter(entry.?);
274 }
275 return entry;
276 }
277
278 pub fn deinit(self: *Walker) void {
279 self.inner.deinit();
280 }
281
282 /// Leaves the current directory, continuing walking one level up.
283 /// If the current entry is a directory entry, then the "current directory"
284 /// is the directory pertaining to the current entry.
285 pub fn leave(self: *Walker) void {
286 self.inner.leave();
287 }
288};
289
290/// Recursively iterates over a directory.
291///
292/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
293///
294/// `Walker.deinit` releases allocated memory and directory handles.
295///
296/// The order of returned file system entries is undefined.
297///
298/// `dir` will not be closed after walking it.
299///
300/// See also `walkSelectively`.
301pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
302 return .{ .inner = try walkSelectively(dir, allocator) };
303}
304
31305pub const Handle = std.posix.fd_t;
32306
33307pub const PathNameError = error{
......@@ -145,7 +419,7 @@ pub const WriteFileOptions = struct {
145419 flags: File.CreateFlags = .{},
146420};
147421
148pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
422pub const WriteFileError = File.WriteError || File.OpenError;
149423
150424/// Writes content to the file system, using the file creation flags provided.
151425pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
......@@ -179,7 +453,7 @@ pub fn updateFile(
179453 dest_dir: Dir,
180454 /// If directories in this path do not exist, they are created.
181455 dest_path: []const u8,
182 options: std.fs.Dir.CopyFileOptions,
456 options: CopyFileOptions,
183457) !PrevStatus {
184458 var src_file = try source_dir.openFile(io, source_path, .{});
185459 defer src_file.close(io);
......@@ -367,3 +641,929 @@ pub const StatPathOptions = struct {
367641pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
368642 return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
369643}
644
645pub const RealPathError = error{
646 FileNotFound,
647 AccessDenied,
648 PermissionDenied,
649 NameTooLong,
650 NotSupported,
651 NotDir,
652 SymLinkLoop,
653 InputOutput,
654 FileTooBig,
655 IsDir,
656 ProcessFdQuotaExceeded,
657 SystemFdQuotaExceeded,
658 NoDevice,
659 SystemResources,
660 NoSpaceLeft,
661 FileSystem,
662 DeviceBusy,
663 ProcessNotFound,
664 SharingViolation,
665 PipeBusy,
666 /// Windows: file paths provided by the user must be valid WTF-8.
667 /// https://wtf-8.codeberg.page/
668 BadPathName,
669 /// On Windows, `\\server` or `\\server\share` was not found.
670 NetworkNotFound,
671 PathAlreadyExists,
672 /// On Windows, antivirus software is enabled by default. It can be
673 /// disabled, but Windows Update sometimes ignores the user's preference
674 /// and re-enables it. When enabled, antivirus software on Windows
675 /// intercepts file system operations and makes them significantly slower
676 /// in addition to possibly failing with this error code.
677 AntivirusInterference,
678 /// On Windows, the volume does not contain a recognized file system. File
679 /// system drivers might not be loaded, or the volume may be corrupt.
680 UnrecognizedVolume,
681} || Io.Cancelable || Io.UnexpectedError;
682
683/// This function returns the canonicalized absolute pathname of `pathname`
684/// relative to this `Dir`. If `pathname` is absolute, ignores this `Dir`
685/// handle and returns the canonicalized absolute pathname of `pathname`
686/// argument.
687///
688/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
689/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
690/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
691/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
692///
693/// This function is not universally supported by all platforms. Currently
694/// supported hosts are: Linux, macOS, and Windows.
695///
696/// See also:
697/// * `realpathAlloc`.
698pub fn realPath(dir: Dir, io: Io, sub_path: []const u8, out_buffer: []u8) RealPathError!usize {
699 return io.vtable.dirRealPath(io.userdata, dir, sub_path, out_buffer);
700}
701
702pub const RealPathAllocError = RealPathError || Allocator.Error;
703
704/// Same as `Dir.realpath` except caller must free the returned memory.
705/// See also `Dir.realpath`.
706pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 {
707 // Use of max_path_bytes here is valid as the realpath function does not
708 // have a variant that takes an arbitrary-size buffer.
709 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
710 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
711 // paths. musl supports passing NULL but restricts the output to PATH_MAX
712 // anyway.
713 var buf: [std.fs.max_path_bytes]u8 = undefined;
714 return allocator.dupe(u8, try self.realpath(pathname, &buf));
715}
716
717pub const DeleteFileError = error{
718 FileNotFound,
719 /// In WASI, this error may occur when the file descriptor does
720 /// not hold the required rights to unlink a resource by path relative to it.
721 AccessDenied,
722 PermissionDenied,
723 FileBusy,
724 FileSystem,
725 IsDir,
726 SymLinkLoop,
727 NameTooLong,
728 NotDir,
729 SystemResources,
730 ReadOnlyFileSystem,
731 /// WASI: file paths must be valid UTF-8.
732 /// Windows: file paths provided by the user must be valid WTF-8.
733 /// https://wtf-8.codeberg.page/
734 /// Windows: file paths cannot contain these characters:
735 /// '/', '*', '?', '"', '<', '>', '|'
736 BadPathName,
737 /// On Windows, `\\server` or `\\server\share` was not found.
738 NetworkNotFound,
739} || Io.Cancelable || Io.UnexpectedError;
740
741/// Delete a file name and possibly the file it refers to, based on an open directory handle.
742///
743/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
744/// On WASI, `sub_path` should be encoded as valid UTF-8.
745/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
746///
747/// Asserts that the path parameter has no null bytes.
748pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void {
749 return io.vtable.dirDeleteFile(io.userdata, dir, sub_path);
750}
751
752pub const DeleteDirError = error{
753 DirNotEmpty,
754 FileNotFound,
755 AccessDenied,
756 PermissionDenied,
757 FileBusy,
758 FileSystem,
759 SymLinkLoop,
760 NameTooLong,
761 NotDir,
762 SystemResources,
763 ReadOnlyFileSystem,
764 /// WASI: file paths must be valid UTF-8.
765 /// Windows: file paths provided by the user must be valid WTF-8.
766 /// https://wtf-8.codeberg.page/
767 BadPathName,
768 /// On Windows, `\\server` or `\\server\share` was not found.
769 NetworkNotFound,
770} || Io.Cancelable || Io.UnexpectedError;
771
772/// Returns `error.DirNotEmpty` if the directory is not empty.
773///
774/// To delete a directory recursively, see `deleteTree`.
775///
776/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
777/// On WASI, `sub_path` should be encoded as valid UTF-8.
778/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
779pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void {
780 return io.vtable.dirDeleteDir(io.userdata, dir, sub_path);
781}
782
783pub const RenameError = error{
784 /// In WASI, this error may occur when the file descriptor does
785 /// not hold the required rights to rename a resource by path relative to it.
786 ///
787 /// On Windows, this error may be returned instead of PathAlreadyExists when
788 /// renaming a directory over an existing directory.
789 AccessDenied,
790 PermissionDenied,
791 FileBusy,
792 DiskQuota,
793 IsDir,
794 SymLinkLoop,
795 LinkQuotaExceeded,
796 NameTooLong,
797 FileNotFound,
798 NotDir,
799 SystemResources,
800 NoSpaceLeft,
801 PathAlreadyExists,
802 ReadOnlyFileSystem,
803 RenameAcrossMountPoints,
804 /// WASI: file paths must be valid UTF-8.
805 /// Windows: file paths provided by the user must be valid WTF-8.
806 /// https://wtf-8.codeberg.page/
807 BadPathName,
808 NoDevice,
809 SharingViolation,
810 PipeBusy,
811 /// On Windows, `\\server` or `\\server\share` was not found.
812 NetworkNotFound,
813 /// On Windows, antivirus software is enabled by default. It can be
814 /// disabled, but Windows Update sometimes ignores the user's preference
815 /// and re-enables it. When enabled, antivirus software on Windows
816 /// intercepts file system operations and makes them significantly slower
817 /// in addition to possibly failing with this error code.
818 AntivirusInterference,
819} || Io.Cancelable || Io.UnexpectedError;
820
821/// Change the name or location of a file or directory.
822///
823/// If `new_sub_path` already exists, it will be replaced.
824///
825/// Renaming a file over an existing directory or a directory over an existing
826/// file will fail with `error.IsDir` or `error.NotDir`
827///
828/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
829/// On WASI, both paths should be encoded as valid UTF-8.
830/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
831pub fn rename(
832 old_dir: Dir,
833 old_sub_path: []const u8,
834 new_dir: Dir,
835 new_sub_path: []const u8,
836 io: Io,
837) RenameError!void {
838 return io.vtable.dirRename(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
839}
840
841/// Use with `Dir.symLink`, `Dir.symLinkAtomic`, and `symLinkAbsolute` to
842/// specify whether the symlink will point to a file or a directory. This value
843/// is ignored on all hosts except Windows where creating symlinks to different
844/// resource types, requires different flags. By default, `symLinkAbsolute` is
845/// assumed to point to a file.
846pub const SymLinkFlags = struct {
847 is_directory: bool = false,
848};
849
850pub const SymLinkError = error{
851 /// In WASI, this error may occur when the file descriptor does
852 /// not hold the required rights to create a new symbolic link relative to it.
853 AccessDenied,
854 PermissionDenied,
855 DiskQuota,
856 PathAlreadyExists,
857 FileSystem,
858 SymLinkLoop,
859 FileNotFound,
860 SystemResources,
861 NoSpaceLeft,
862 ReadOnlyFileSystem,
863 NotDir,
864 NameTooLong,
865 /// WASI: file paths must be valid UTF-8.
866 /// Windows: file paths provided by the user must be valid WTF-8.
867 /// https://wtf-8.codeberg.page/
868 BadPathName,
869} || Io.Cancelable || Io.UnexpectedError;
870
871/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
872///
873/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
874/// one; the latter case is known as a dangling link.
875///
876/// If `sym_link_path` exists, it will not be overwritten.
877///
878/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
879/// On WASI, both paths should be encoded as valid UTF-8.
880/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
881pub fn symLink(
882 dir: Dir,
883 io: Io,
884 target_path: []const u8,
885 sym_link_path: []const u8,
886 flags: SymLinkFlags,
887) SymLinkError!void {
888 return io.vtable.dirSymLink(io.userdata, dir, target_path, sym_link_path, flags);
889}
890
891/// Same as `symLink`, except tries to create the symbolic link until it
892/// succeeds or encounters an error other than `error.PathAlreadyExists`.
893///
894/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
895/// * On WASI, both paths should be encoded as valid UTF-8.
896/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
897pub fn symLinkAtomic(
898 dir: Dir,
899 io: Io,
900 target_path: []const u8,
901 sym_link_path: []const u8,
902 flags: SymLinkFlags,
903) !void {
904 if (dir.symLink(io, target_path, sym_link_path, flags)) {
905 return;
906 } else |err| switch (err) {
907 error.PathAlreadyExists => {},
908 else => |e| return e,
909 }
910
911 const dirname = std.fs.path.dirname(sym_link_path) orelse ".";
912
913 const rand_len = @sizeOf(u64) * 2;
914 const temp_path_len = dirname.len + 1 + rand_len;
915 var temp_path_buf: [std.fs.max_path_bytes]u8 = undefined;
916
917 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
918 @memcpy(temp_path_buf[0..dirname.len], dirname);
919 temp_path_buf[dirname.len] = std.fs.path.sep;
920
921 const temp_path = temp_path_buf[0..temp_path_len];
922
923 while (true) {
924 const random_integer = std.crypto.random.int(u64);
925 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
926
927 if (dir.symLink(io, target_path, temp_path, flags)) {
928 return dir.rename(temp_path, dir, io, sym_link_path);
929 } else |err| switch (err) {
930 error.PathAlreadyExists => continue,
931 else => |e| return e,
932 }
933 }
934}
935
936pub const ReadLinkError = error{
937 /// In WASI, this error may occur when the file descriptor does
938 /// not hold the required rights to read value of a symbolic link relative to it.
939 AccessDenied,
940 PermissionDenied,
941 FileSystem,
942 SymLinkLoop,
943 NameTooLong,
944 FileNotFound,
945 SystemResources,
946 NotLink,
947 NotDir,
948 /// WASI: file paths must be valid UTF-8.
949 /// Windows: file paths provided by the user must be valid WTF-8.
950 /// https://wtf-8.codeberg.page/
951 BadPathName,
952 /// Windows-only. This error may occur if the opened reparse point is
953 /// of unsupported type.
954 UnsupportedReparsePointType,
955 /// On Windows, `\\server` or `\\server\share` was not found.
956 NetworkNotFound,
957 /// On Windows, antivirus software is enabled by default. It can be
958 /// disabled, but Windows Update sometimes ignores the user's preference
959 /// and re-enables it. When enabled, antivirus software on Windows
960 /// intercepts file system operations and makes them significantly slower
961 /// in addition to possibly failing with this error code.
962 AntivirusInterference,
963} || Io.Cancelable || Io.UnexpectedError;
964
965/// Obtain target of a symbolic link.
966///
967/// Returns how many bytes of `buffer` are populated.
968///
969/// Asserts that the path parameter has no null bytes.
970///
971/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
972/// On WASI, `sub_path` should be encoded as valid UTF-8.
973/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
974pub fn readLink(dir: Dir, io: Io, sub_path: []const u8, buffer: []u8) ReadLinkError!usize {
975 return io.vtable.dirReadLink(io.userdata, dir, sub_path, buffer);
976}
977
978pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
979 /// File size reached or exceeded the provided limit.
980 StreamTooLong,
981};
982
983/// Reads all the bytes from the named file. On success, caller owns returned
984/// buffer.
985///
986/// If the file size is already known, a better alternative is to initialize a
987/// `File.Reader`.
988///
989/// If the file size cannot be obtained, an error is returned. If
990/// this is a realistic possibility, a better alternative is to initialize a
991/// `File.Reader` which handles this seamlessly.
992pub fn readFileAlloc(
993 dir: Dir,
994 io: Io,
995 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
996 /// On WASI, should be encoded as valid UTF-8.
997 /// On other platforms, an opaque sequence of bytes with no particular encoding.
998 sub_path: []const u8,
999 /// Used to allocate the result.
1000 gpa: Allocator,
1001 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1002 limit: Io.Limit,
1003) ReadFileAllocError![]u8 {
1004 return readFileAllocOptions(dir, io, sub_path, gpa, limit, .of(u8), null);
1005}
1006
1007/// Reads all the bytes from the named file. On success, caller owns returned
1008/// buffer.
1009///
1010/// If the file size is already known, a better alternative is to initialize a
1011/// `File.Reader`.
1012pub fn readFileAllocOptions(
1013 dir: Dir,
1014 io: Io,
1015 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1016 /// On WASI, should be encoded as valid UTF-8.
1017 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1018 sub_path: []const u8,
1019 /// Used to allocate the result.
1020 gpa: Allocator,
1021 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1022 limit: Io.Limit,
1023 comptime alignment: std.mem.Alignment,
1024 comptime sentinel: ?u8,
1025) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1026 var file = try dir.openFile(io, sub_path, .{});
1027 defer file.close(io);
1028 var file_reader = file.reader(io, &.{});
1029 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
1030 error.ReadFailed => return file_reader.err.?,
1031 error.OutOfMemory, error.StreamTooLong => |e| return e,
1032 };
1033}
1034
1035pub const DeleteTreeError = error{
1036 AccessDenied,
1037 PermissionDenied,
1038 FileTooBig,
1039 SymLinkLoop,
1040 ProcessFdQuotaExceeded,
1041 NameTooLong,
1042 SystemFdQuotaExceeded,
1043 NoDevice,
1044 SystemResources,
1045 ReadOnlyFileSystem,
1046 FileSystem,
1047 FileBusy,
1048 DeviceBusy,
1049 ProcessNotFound,
1050 /// One of the path components was not a directory.
1051 /// This error is unreachable if `sub_path` does not contain a path separator.
1052 NotDir,
1053 /// WASI: file paths must be valid UTF-8.
1054 /// Windows: file paths provided by the user must be valid WTF-8.
1055 /// https://wtf-8.codeberg.page/
1056 /// On Windows, file paths cannot contain these characters:
1057 /// '/', '*', '?', '"', '<', '>', '|'
1058 BadPathName,
1059 /// On Windows, `\\server` or `\\server\share` was not found.
1060 NetworkNotFound,
1061} || Io.Cancelable || Io.UnexpectedError;
1062
1063/// Whether `sub_path` describes a symlink, file, or directory, this function
1064/// removes it. If it cannot be removed because it is a non-empty directory,
1065/// this function recursively removes its entries and then tries again.
1066///
1067/// This operation is not atomic on most file systems.
1068///
1069/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1070/// On WASI, `sub_path` should be encoded as valid UTF-8.
1071/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1072pub fn deleteTree(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1073 var initial_iterable_dir = (try dir.deleteTreeOpenInitialSubpath(io, sub_path, .file)) orelse return;
1074
1075 const StackItem = struct {
1076 name: []const u8,
1077 parent_dir: Dir,
1078 iter: Dir.Iterator,
1079
1080 fn closeAll(inner_io: Io, items: []@This()) void {
1081 for (items) |*item| item.iter.dir.close(inner_io);
1082 }
1083 };
1084
1085 var stack_buffer: [16]StackItem = undefined;
1086 var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
1087 defer StackItem.closeAll(io, stack.items);
1088
1089 stack.appendAssumeCapacity(.{
1090 .name = sub_path,
1091 .parent_dir = dir,
1092 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1093 });
1094
1095 process_stack: while (stack.items.len != 0) {
1096 var top = &stack.items[stack.items.len - 1];
1097 while (try top.iter.next()) |entry| {
1098 var treat_as_dir = entry.kind == .directory;
1099 handle_entry: while (true) {
1100 if (treat_as_dir) {
1101 if (stack.unusedCapacitySlice().len >= 1) {
1102 var iterable_dir = top.iter.dir.openDir(io, entry.name, .{
1103 .follow_symlinks = false,
1104 .iterate = true,
1105 }) catch |err| switch (err) {
1106 error.NotDir => {
1107 treat_as_dir = false;
1108 continue :handle_entry;
1109 },
1110 error.FileNotFound => {
1111 // That's fine, we were trying to remove this directory anyway.
1112 break :handle_entry;
1113 },
1114
1115 error.AccessDenied,
1116 error.PermissionDenied,
1117 error.SymLinkLoop,
1118 error.ProcessFdQuotaExceeded,
1119 error.NameTooLong,
1120 error.SystemFdQuotaExceeded,
1121 error.NoDevice,
1122 error.SystemResources,
1123 error.Unexpected,
1124 error.BadPathName,
1125 error.NetworkNotFound,
1126 error.DeviceBusy,
1127 error.Canceled,
1128 => |e| return e,
1129 };
1130 stack.appendAssumeCapacity(.{
1131 .name = entry.name,
1132 .parent_dir = top.iter.dir,
1133 .iter = iterable_dir.iterateAssumeFirstIteration(),
1134 });
1135 continue :process_stack;
1136 } else {
1137 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(io, entry.name, entry.kind);
1138 break :handle_entry;
1139 }
1140 } else {
1141 if (top.iter.dir.deleteFile(io, entry.name)) {
1142 break :handle_entry;
1143 } else |err| switch (err) {
1144 error.FileNotFound => break :handle_entry,
1145
1146 // Impossible because we do not pass any path separators.
1147 error.NotDir => unreachable,
1148
1149 error.IsDir => {
1150 treat_as_dir = true;
1151 continue :handle_entry;
1152 },
1153
1154 error.AccessDenied,
1155 error.PermissionDenied,
1156 error.SymLinkLoop,
1157 error.NameTooLong,
1158 error.SystemResources,
1159 error.ReadOnlyFileSystem,
1160 error.FileSystem,
1161 error.FileBusy,
1162 error.BadPathName,
1163 error.NetworkNotFound,
1164 error.Unexpected,
1165 => |e| return e,
1166 }
1167 }
1168 }
1169 }
1170
1171 // On Windows, we can't delete until the dir's handle has been closed, so
1172 // close it before we try to delete.
1173 top.iter.dir.close(io);
1174
1175 // In order to avoid double-closing the directory when cleaning up
1176 // the stack in the case of an error, we save the relevant portions and
1177 // pop the value from the stack.
1178 const parent_dir = top.parent_dir;
1179 const name = top.name;
1180 stack.items.len -= 1;
1181
1182 var need_to_retry: bool = false;
1183 parent_dir.deleteDir(name) catch |err| switch (err) {
1184 error.FileNotFound => {},
1185 error.DirNotEmpty => need_to_retry = true,
1186 else => |e| return e,
1187 };
1188
1189 if (need_to_retry) {
1190 // Since we closed the handle that the previous iterator used, we
1191 // need to re-open the dir and re-create the iterator.
1192 var iterable_dir = iterable_dir: {
1193 var treat_as_dir = true;
1194 handle_entry: while (true) {
1195 if (treat_as_dir) {
1196 break :iterable_dir parent_dir.openDir(name, .{
1197 .follow_symlinks = false,
1198 .iterate = true,
1199 }) catch |err| switch (err) {
1200 error.NotDir => {
1201 treat_as_dir = false;
1202 continue :handle_entry;
1203 },
1204 error.FileNotFound => {
1205 // That's fine, we were trying to remove this directory anyway.
1206 continue :process_stack;
1207 },
1208
1209 error.AccessDenied,
1210 error.PermissionDenied,
1211 error.SymLinkLoop,
1212 error.ProcessFdQuotaExceeded,
1213 error.NameTooLong,
1214 error.SystemFdQuotaExceeded,
1215 error.NoDevice,
1216 error.SystemResources,
1217 error.Unexpected,
1218 error.BadPathName,
1219 error.NetworkNotFound,
1220 error.DeviceBusy,
1221 error.Canceled,
1222 => |e| return e,
1223 };
1224 } else {
1225 if (parent_dir.deleteFile(name)) {
1226 continue :process_stack;
1227 } else |err| switch (err) {
1228 error.FileNotFound => continue :process_stack,
1229
1230 // Impossible because we do not pass any path separators.
1231 error.NotDir => unreachable,
1232
1233 error.IsDir => {
1234 treat_as_dir = true;
1235 continue :handle_entry;
1236 },
1237
1238 error.AccessDenied,
1239 error.PermissionDenied,
1240 error.SymLinkLoop,
1241 error.NameTooLong,
1242 error.SystemResources,
1243 error.ReadOnlyFileSystem,
1244 error.FileSystem,
1245 error.FileBusy,
1246 error.BadPathName,
1247 error.NetworkNotFound,
1248 error.Unexpected,
1249 => |e| return e,
1250 }
1251 }
1252 }
1253 };
1254 // We know there is room on the stack since we are just re-adding
1255 // the StackItem that we previously popped.
1256 stack.appendAssumeCapacity(.{
1257 .name = name,
1258 .parent_dir = parent_dir,
1259 .iter = iterable_dir.iterateAssumeFirstIteration(),
1260 });
1261 continue :process_stack;
1262 }
1263 }
1264}
1265
1266/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
1267/// This is slower than `deleteTree` but uses less stack space.
1268/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1269/// On WASI, `sub_path` should be encoded as valid UTF-8.
1270/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1271pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void {
1272 return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file);
1273}
1274
1275fn deleteTreeMinStackSizeWithKindHint(parent: Dir, io: Io, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
1276 start_over: while (true) {
1277 var dir = (try parent.deleteTreeOpenInitialSubpath(io, sub_path, kind_hint)) orelse return;
1278 var cleanup_dir_parent: ?Dir = null;
1279 defer if (cleanup_dir_parent) |*d| d.close();
1280
1281 var cleanup_dir = true;
1282 defer if (cleanup_dir) dir.close();
1283
1284 // Valid use of max_path_bytes because dir_name_buf will only
1285 // ever store a single path component that was returned from the
1286 // filesystem.
1287 var dir_name_buf: [std.fs.max_path_bytes]u8 = undefined;
1288 var dir_name: []const u8 = sub_path;
1289
1290 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
1291 // Go through each entry and if it is not a directory, delete it. If it is a directory,
1292 // open it, and close the original directory. Repeat. Then start the entire operation over.
1293
1294 scan_dir: while (true) {
1295 var dir_it = dir.iterateAssumeFirstIteration();
1296 dir_it: while (try dir_it.next()) |entry| {
1297 var treat_as_dir = entry.kind == .directory;
1298 handle_entry: while (true) {
1299 if (treat_as_dir) {
1300 const new_dir = dir.openDir(entry.name, .{
1301 .follow_symlinks = false,
1302 .iterate = true,
1303 }) catch |err| switch (err) {
1304 error.NotDir => {
1305 treat_as_dir = false;
1306 continue :handle_entry;
1307 },
1308 error.FileNotFound => {
1309 // That's fine, we were trying to remove this directory anyway.
1310 continue :dir_it;
1311 },
1312
1313 error.AccessDenied,
1314 error.PermissionDenied,
1315 error.SymLinkLoop,
1316 error.ProcessFdQuotaExceeded,
1317 error.NameTooLong,
1318 error.SystemFdQuotaExceeded,
1319 error.NoDevice,
1320 error.SystemResources,
1321 error.Unexpected,
1322 error.BadPathName,
1323 error.NetworkNotFound,
1324 error.DeviceBusy,
1325 error.Canceled,
1326 => |e| return e,
1327 };
1328 if (cleanup_dir_parent) |*d| d.close();
1329 cleanup_dir_parent = dir;
1330 dir = new_dir;
1331 const result = dir_name_buf[0..entry.name.len];
1332 @memcpy(result, entry.name);
1333 dir_name = result;
1334 continue :scan_dir;
1335 } else {
1336 if (dir.deleteFile(entry.name)) {
1337 continue :dir_it;
1338 } else |err| switch (err) {
1339 error.FileNotFound => continue :dir_it,
1340
1341 // Impossible because we do not pass any path separators.
1342 error.NotDir => unreachable,
1343
1344 error.IsDir => {
1345 treat_as_dir = true;
1346 continue :handle_entry;
1347 },
1348
1349 error.AccessDenied,
1350 error.PermissionDenied,
1351 error.SymLinkLoop,
1352 error.NameTooLong,
1353 error.SystemResources,
1354 error.ReadOnlyFileSystem,
1355 error.FileSystem,
1356 error.FileBusy,
1357 error.BadPathName,
1358 error.NetworkNotFound,
1359 error.Unexpected,
1360 => |e| return e,
1361 }
1362 }
1363 }
1364 }
1365 // Reached the end of the directory entries, which means we successfully deleted all of them.
1366 // Now to remove the directory itself.
1367 dir.close();
1368 cleanup_dir = false;
1369
1370 if (cleanup_dir_parent) |d| {
1371 d.deleteDir(io, dir_name) catch |err| switch (err) {
1372 // These two things can happen due to file system race conditions.
1373 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1374 else => |e| return e,
1375 };
1376 continue :start_over;
1377 } else {
1378 parent.deleteDir(io, sub_path) catch |err| switch (err) {
1379 error.FileNotFound => return,
1380 error.DirNotEmpty => continue :start_over,
1381 else => |e| return e,
1382 };
1383 return;
1384 }
1385 }
1386 }
1387}
1388
1389/// On successful delete, returns null.
1390fn deleteTreeOpenInitialSubpath(dir: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
1391 return iterable_dir: {
1392 // Treat as a file by default
1393 var treat_as_dir = kind_hint == .directory;
1394
1395 handle_entry: while (true) {
1396 if (treat_as_dir) {
1397 break :iterable_dir dir.openDir(sub_path, .{
1398 .follow_symlinks = false,
1399 .iterate = true,
1400 }) catch |err| switch (err) {
1401 error.NotDir => {
1402 treat_as_dir = false;
1403 continue :handle_entry;
1404 },
1405 error.FileNotFound => {
1406 // That's fine, we were trying to remove this directory anyway.
1407 return null;
1408 },
1409
1410 error.AccessDenied,
1411 error.PermissionDenied,
1412 error.SymLinkLoop,
1413 error.ProcessFdQuotaExceeded,
1414 error.NameTooLong,
1415 error.SystemFdQuotaExceeded,
1416 error.NoDevice,
1417 error.SystemResources,
1418 error.Unexpected,
1419 error.BadPathName,
1420 error.DeviceBusy,
1421 error.NetworkNotFound,
1422 error.Canceled,
1423 => |e| return e,
1424 };
1425 } else {
1426 if (dir.deleteFile(sub_path)) {
1427 return null;
1428 } else |err| switch (err) {
1429 error.FileNotFound => return null,
1430
1431 error.IsDir => {
1432 treat_as_dir = true;
1433 continue :handle_entry;
1434 },
1435
1436 error.AccessDenied,
1437 error.PermissionDenied,
1438 error.SymLinkLoop,
1439 error.NameTooLong,
1440 error.SystemResources,
1441 error.ReadOnlyFileSystem,
1442 error.NotDir,
1443 error.FileSystem,
1444 error.FileBusy,
1445 error.BadPathName,
1446 error.NetworkNotFound,
1447 error.Unexpected,
1448 => |e| return e,
1449 }
1450 }
1451 }
1452 };
1453}
1454
1455pub const CopyFileOptions = struct {
1456 /// When this is `null` the mode is copied from the source file.
1457 override_mode: ?File.Mode = null,
1458};
1459
1460pub const CopyFileError = File.OpenError || File.StatError ||
1461 File.Atomic.InitError || File.Atomic.FinishError ||
1462 File.ReadError || File.WriteError || error{InvalidFileName};
1463
1464/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1465/// same contents as `source_path` within `source_dir`, overwriting any already
1466/// existing file.
1467///
1468/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1469/// readily available, there is a possibility of power loss or application
1470/// termination leaving temporary files present in the same directory as
1471/// dest_path.
1472///
1473/// On Windows, both paths should be encoded as
1474/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
1475/// encoded as valid UTF-8. On other platforms, both paths are an opaque
1476/// sequence of bytes with no particular encoding.
1477pub fn copyFile(
1478 source_dir: Dir,
1479 source_path: []const u8,
1480 dest_dir: Dir,
1481 dest_path: []const u8,
1482 io: Io,
1483 options: CopyFileOptions,
1484) CopyFileError!void {
1485 const file = try source_dir.openFile(io, source_path, .{});
1486 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1487 defer file_reader.file.close(io);
1488
1489 const mode = options.override_mode orelse blk: {
1490 const st = try file_reader.file.stat(io);
1491 file_reader.size = st.size;
1492 break :blk st.mode;
1493 };
1494
1495 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1496 var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
1497 .mode = mode,
1498 .write_buffer = &buffer,
1499 });
1500 defer atomic_file.deinit(io);
1501
1502 _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1503 error.ReadFailed => return file_reader.err.?,
1504 error.WriteFailed => return atomic_file.file_writer.err.?,
1505 };
1506
1507 try atomic_file.finish();
1508}
1509
1510pub const AtomicFileOptions = struct {
1511 mode: File.Mode = File.default_mode,
1512 make_path: bool = false,
1513 write_buffer: []u8,
1514};
1515
1516/// Directly access the `.file` field, and then call `File.Atomic.finish` to
1517/// atomically replace `dest_path` with contents.
1518///
1519/// Always call `File.Atomic.deinit` to clean up, regardless of whether
1520/// `File.Atomic.finish` succeeded. `dest_path` must remain valid until
1521/// `File.Atomic.deinit` is called.
1522///
1523/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1524/// On WASI, `dest_path` should be encoded as valid UTF-8.
1525/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
1526pub fn atomicFile(parent: Dir, io: Io, dest_path: []const u8, options: AtomicFileOptions) !File.Atomic {
1527 if (std.fs.path.dirname(dest_path)) |dirname| {
1528 const dir = if (options.make_path)
1529 try parent.makeOpenPath(io, dirname, .{})
1530 else
1531 try parent.openDir(io, dirname, .{});
1532
1533 return .init(std.fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
1534 } else {
1535 return .init(dest_path, options.mode, parent, false, options.write_buffer);
1536 }
1537}
1538
1539pub const SetModeError = File.SetModeError;
1540
1541/// Also known as "chmod".
1542///
1543/// The process must have the correct privileges in order to do this
1544/// successfully, or must have the effective user ID matching the owner
1545/// of the directory. Additionally, the directory must have been opened
1546/// with `OpenOptions.iterate` set to `true`.
1547pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void {
1548 return io.vtable.dirSetMode(io.userdata, dir, new_mode);
1549}
1550
1551pub const SetOwnerError = File.SetOwnerError;
1552
1553/// Also known as "chown".
1554///
1555/// The process must have the correct privileges in order to do this
1556/// successfully. The group may be changed by the owner of the directory to
1557/// any group of which the owner is a member. Additionally, the directory
1558/// must have been opened with `OpenOptions.iterate` set to `true`. If the
1559/// owner or group is specified as `null`, the ID is not changed.
1560pub fn setOwner(dir: Dir, io: Io, owner: ?File.Uid, group: ?File.Gid) SetOwnerError!void {
1561 return io.vtable.dirSetOwner(io.userdata, dir, owner, group);
1562}
1563
1564pub const SetPermissionsError = File.SetPermissionsError;
1565pub const Permissions = File.Permissions;
1566
1567pub fn setPermissions(dir: Dir, io: Io, permissions: Permissions) SetPermissionsError!void {
1568 return io.vtable.dirSetPermissions(io.userdata, dir, permissions);
1569}
lib/std/Io/File.zig+184-6
......@@ -7,12 +7,23 @@ const is_windows = native_os == .windows;
77const std = @import("../std.zig");
88const Io = std.Io;
99const assert = std.debug.assert;
10const Dir = std.Io.Dir;
1011
1112handle: Handle,
1213
1314pub const Handle = std.posix.fd_t;
1415pub const Mode = std.posix.mode_t;
1516pub const INode = std.posix.ino_t;
17pub const Uid = std.posix.uid_t;
18pub const Gid = std.posix.gid_t;
19
20/// This is the default mode given to POSIX operating systems for creating
21/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
22/// since most people would expect "-rw-r--r--", for example, when using
23/// the `touch` command, which would correspond to `0o644`. However, POSIX
24/// libc implementations use `0o666` inside `fopen` and then rely on the
25/// process-scoped "umask" setting to adjust this number for file creation.
26pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
1627
1728pub const Kind = enum {
1829 block_device,
......@@ -92,6 +103,11 @@ pub const Lock = enum {
92103 exclusive,
93104};
94105
106pub const LockError = error{
107 SystemResources,
108 FileLocksNotSupported,
109} || Io.UnexpectedError;
110
95111pub const OpenFlags = struct {
96112 mode: OpenMode = .read_only,
97113
......@@ -141,7 +157,53 @@ pub const OpenFlags = struct {
141157 }
142158};
143159
144pub const CreateFlags = std.fs.File.CreateFlags;
160pub const CreateFlags = struct {
161 /// Whether the file will be created with read access.
162 read: bool = false,
163
164 /// If the file already exists, and is a regular file, and the access
165 /// mode allows writing, it will be truncated to length 0.
166 truncate: bool = true,
167
168 /// Ensures that this open call creates the file, otherwise causes
169 /// `error.PathAlreadyExists` to be returned.
170 exclusive: bool = false,
171
172 /// Open the file with an advisory lock to coordinate with other processes
173 /// accessing it at the same time. An exclusive lock will prevent other
174 /// processes from acquiring a lock. A shared lock will prevent other
175 /// processes from acquiring a exclusive lock, but does not prevent
176 /// other process from getting their own shared locks.
177 ///
178 /// The lock is advisory, except on Linux in very specific circumstances[1].
179 /// This means that a process that does not respect the locking API can still get access
180 /// to the file, despite the lock.
181 ///
182 /// On these operating systems, the lock is acquired atomically with
183 /// opening the file:
184 /// * Darwin
185 /// * DragonFlyBSD
186 /// * FreeBSD
187 /// * Haiku
188 /// * NetBSD
189 /// * OpenBSD
190 /// On these operating systems, the lock is acquired via a separate syscall
191 /// after opening the file:
192 /// * Linux
193 /// * Windows
194 ///
195 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
196 lock: Lock = .none,
197
198 /// Sets whether or not to wait until the file is locked to return. If set to true,
199 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
200 /// is available to proceed.
201 lock_nonblocking: bool = false,
202
203 /// For POSIX systems this is the file system mode the file will
204 /// be created with. On other systems this is always 0.
205 mode: Mode = default_mode,
206};
145207
146208pub const OpenError = error{
147209 SharingViolation,
......@@ -231,6 +293,17 @@ pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) Wr
231293 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
232294}
233295
296/// Opens a file for reading or writing, without attempting to create a new
297/// file, based on an absolute path.
298///
299/// Returns an open resource to be released with `close`.
300///
301/// Asserts that the path is absolute. See `Dir.openFile` for a function that
302/// operates on both absolute and relative paths.
303///
304/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
305/// On WASI, `absolute_path` should be encoded as valid UTF-8.
306/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
234307pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
235308 assert(std.fs.path.isAbsolute(absolute_path));
236309 return Io.Dir.cwd().openFile(io, absolute_path, flags);
......@@ -364,11 +437,6 @@ pub const Reader = struct {
364437 };
365438 }
366439
367 /// Takes a legacy `std.fs.File` to help with upgrading.
368 pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
369 return .init(.{ .handle = file.handle }, io, buffer);
370 }
371
372440 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
373441 return .{
374442 .io = io,
......@@ -652,3 +720,113 @@ pub const Reader = struct {
652720 return size - logicalPos(r) == 0;
653721 }
654722};
723
724pub const Atomic = struct {
725 file_writer: File.Writer,
726 random_integer: u64,
727 dest_basename: []const u8,
728 file_open: bool,
729 file_exists: bool,
730 close_dir_on_deinit: bool,
731 dir: Dir,
732
733 pub const InitError = File.OpenError;
734
735 /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
736 pub fn init(
737 dest_basename: []const u8,
738 mode: File.Mode,
739 dir: Dir,
740 close_dir_on_deinit: bool,
741 write_buffer: []u8,
742 ) InitError!Atomic {
743 while (true) {
744 const random_integer = std.crypto.random.int(u64);
745 const tmp_sub_path = std.fmt.hex(random_integer);
746 const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
747 error.PathAlreadyExists => continue,
748 else => |e| return e,
749 };
750 return .{
751 .file_writer = file.writer(write_buffer),
752 .random_integer = random_integer,
753 .dest_basename = dest_basename,
754 .file_open = true,
755 .file_exists = true,
756 .close_dir_on_deinit = close_dir_on_deinit,
757 .dir = dir,
758 };
759 }
760 }
761
762 /// Always call deinit, even after a successful finish().
763 pub fn deinit(af: *Atomic) void {
764 if (af.file_open) {
765 af.file_writer.file.close();
766 af.file_open = false;
767 }
768 if (af.file_exists) {
769 const tmp_sub_path = std.fmt.hex(af.random_integer);
770 af.dir.deleteFile(&tmp_sub_path) catch {};
771 af.file_exists = false;
772 }
773 if (af.close_dir_on_deinit) {
774 af.dir.close();
775 }
776 af.* = undefined;
777 }
778
779 pub const FlushError = File.WriteError;
780
781 pub fn flush(af: *Atomic) FlushError!void {
782 af.file_writer.interface.flush() catch |err| switch (err) {
783 error.WriteFailed => return af.file_writer.err.?,
784 };
785 }
786
787 pub const RenameIntoPlaceError = Dir.RenameError;
788
789 /// On Windows, this function introduces a period of time where some file
790 /// system operations on the destination file will result in
791 /// `error.AccessDenied`, including rename operations (such as the one used in
792 /// this function).
793 pub fn renameIntoPlace(af: *Atomic) RenameIntoPlaceError!void {
794 const io = af.file_writer.io;
795 assert(af.file_exists);
796 if (af.file_open) {
797 af.file_writer.file.close();
798 af.file_open = false;
799 }
800 const tmp_sub_path = std.fmt.hex(af.random_integer);
801 try af.dir.rename(&tmp_sub_path, af.dir, af.dest_basename, io);
802 af.file_exists = false;
803 }
804
805 pub const FinishError = FlushError || RenameIntoPlaceError;
806
807 /// Combination of `flush` followed by `renameIntoPlace`.
808 pub fn finish(af: *Atomic) FinishError!void {
809 try af.flush();
810 try af.renameIntoPlace();
811 }
812};
813
814pub const SetModeError = error{
815 AccessDenied,
816 PermissionDenied,
817 InputOutput,
818 SymLinkLoop,
819 FileNotFound,
820 SystemResources,
821 ReadOnlyFileSystem,
822} || Io.Cancelable || Io.UnexpectedError;
823
824pub const SetOwnerError = error{
825 AccessDenied,
826 PermissionDenied,
827 InputOutput,
828 SymLinkLoop,
829 FileNotFound,
830 SystemResources,
831 ReadOnlyFileSystem,
832} || Io.Cancelable || Io.UnexpectedError;
lib/std/Io/Threaded.zig+1584-145
......@@ -657,12 +657,22 @@ pub fn io(t: *Threaded) Io {
657657 .dirMakeOpenPath = dirMakeOpenPath,
658658 .dirStat = dirStat,
659659 .dirStatPath = dirStatPath,
660 .fileStat = fileStat,
661660 .dirAccess = dirAccess,
662661 .dirCreateFile = dirCreateFile,
663662 .dirOpenFile = dirOpenFile,
664663 .dirOpenDir = dirOpenDir,
665664 .dirClose = dirClose,
665 .dirRealPath = dirRealPath,
666 .dirDeleteFile = dirDeleteFile,
667 .dirDeleteDir = dirDeleteDir,
668 .dirRename = dirRename,
669 .dirSymLink = dirSymLink,
670 .dirReadLink = dirReadLink,
671 .dirSetMode = dirSetMode,
672 .dirSetOwner = dirSetOwner,
673 .dirSetPermissions = dirSetPermissions,
674
675 .fileStat = fileStat,
666676 .fileClose = fileClose,
667677 .fileWriteStreaming = fileWriteStreaming,
668678 .fileWritePositional = fileWritePositional,
......@@ -753,12 +763,22 @@ pub fn ioBasic(t: *Threaded) Io {
753763 .dirMakeOpenPath = dirMakeOpenPath,
754764 .dirStat = dirStat,
755765 .dirStatPath = dirStatPath,
756 .fileStat = fileStat,
757766 .dirAccess = dirAccess,
758767 .dirCreateFile = dirCreateFile,
759768 .dirOpenFile = dirOpenFile,
760769 .dirOpenDir = dirOpenDir,
761770 .dirClose = dirClose,
771 .dirRealPath = dirRealPath,
772 .dirDeleteFile = dirDeleteFile,
773 .dirDeleteDir = dirDeleteDir,
774 .dirRename = dirRename,
775 .dirSymLink = dirSymLink,
776 .dirReadLink = dirReadLink,
777 .dirSetMode = dirSetMode,
778 .dirSetOwner = dirSetOwner,
779 .dirSetPermissions = dirSetPermissions,
780
781 .fileStat = fileStat,
762782 .fileClose = fileClose,
763783 .fileWriteStreaming = fileWriteStreaming,
764784 .fileWritePositional = fileWritePositional,
......@@ -3093,50 +3113,69 @@ fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
30933113 posix.close(dir.handle);
30943114}
30953115
3096fn dirOpenDirWasi(
3097 userdata: ?*anyopaque,
3098 dir: Io.Dir,
3099 sub_path: []const u8,
3100 options: Io.Dir.OpenOptions,
3101) Io.Dir.OpenError!Io.Dir {
3102 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
3116const dirRealPath = switch (native_os) {
3117 .windows => dirRealPathWindows,
3118 else => dirRealPathPosix,
3119};
3120
3121fn dirRealPathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize {
31033122 const t: *Threaded = @ptrCast(@alignCast(userdata));
3123 const w = windows;
31043124 const current_thread = Thread.getCurrent(t);
3105 const wasi = std.os.wasi;
31063125
3107 var base: std.os.wasi.rights_t = .{
3108 .FD_FILESTAT_GET = true,
3109 .FD_FDSTAT_SET_FLAGS = true,
3110 .FD_FILESTAT_SET_TIMES = true,
3126 try current_thread.checkCancel();
3127
3128 var path_name_w = try w.sliceToPrefixedFileW(dir.handle, sub_path);
3129
3130 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
3131 const share_access = w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE;
3132 const creation = w.FILE_OPEN;
3133 const h_file = blk: {
3134 const res = w.OpenFile(path_name_w.span(), .{
3135 .dir = dir.handle,
3136 .access_mask = access_mask,
3137 .share_access = share_access,
3138 .creation = creation,
3139 .filter = .any,
3140 }) catch |err| switch (err) {
3141 error.WouldBlock => unreachable,
3142 else => |e| return e,
3143 };
3144 break :blk res;
31113145 };
3112 if (options.access_sub_paths) {
3113 base.FD_READDIR = true;
3114 base.PATH_CREATE_DIRECTORY = true;
3115 base.PATH_CREATE_FILE = true;
3116 base.PATH_LINK_SOURCE = true;
3117 base.PATH_LINK_TARGET = true;
3118 base.PATH_OPEN = true;
3119 base.PATH_READLINK = true;
3120 base.PATH_RENAME_SOURCE = true;
3121 base.PATH_RENAME_TARGET = true;
3122 base.PATH_FILESTAT_GET = true;
3123 base.PATH_FILESTAT_SET_SIZE = true;
3124 base.PATH_FILESTAT_SET_TIMES = true;
3125 base.PATH_SYMLINK = true;
3126 base.PATH_REMOVE_DIRECTORY = true;
3127 base.PATH_UNLINK_FILE = true;
3128 }
3146 defer w.CloseHandle(h_file);
3147
3148 const wide_slice = w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
3149
3150 const len = std.unicode.calcWtf8Len(wide_slice);
3151 if (len > out_buffer.len)
3152 return error.NameTooLong;
3153
3154 return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
3155}
3156
3157fn dirRealPathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize {
3158 if (native_os == .wasi) @compileError("unsupported operating system");
3159 const max_path_bytes = std.fs.max_path_bytes;
3160
3161 const t: *Threaded = @ptrCast(@alignCast(userdata));
3162 const current_thread = Thread.getCurrent(t);
3163
3164 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3165 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3166
3167 var flags: posix.O = .{};
3168 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
3169 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
3170 if (@hasField(posix.O, "PATH")) flags.PATH = true;
31293171
3130 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };
3131 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
3132 const fdflags: wasi.fdflags_t = .{};
3133 var fd: posix.fd_t = undefined;
31343172 try current_thread.beginSyscall();
3135 while (true) {
3136 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
3173 const fd: posix.fd_t = while (true) {
3174 const rc = openat_sym(dir.handle, sub_path_posix, flags, 0);
3175 switch (posix.errno(rc)) {
31373176 .SUCCESS => {
31383177 current_thread.endSyscall();
3139 return .{ .handle = fd };
3178 break @intCast(rc);
31403179 },
31413180 .INTR => {
31423181 try current_thread.checkCancel();
......@@ -3150,94 +3189,183 @@ fn dirOpenDirWasi(
31503189 .INVAL => return error.BadPathName,
31513190 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
31523191 .ACCES => return error.AccessDenied,
3192 .FBIG => return error.FileTooBig,
3193 .OVERFLOW => return error.FileTooBig,
3194 .ISDIR => return error.IsDir,
31533195 .LOOP => return error.SymLinkLoop,
31543196 .MFILE => return error.ProcessFdQuotaExceeded,
31553197 .NAMETOOLONG => return error.NameTooLong,
31563198 .NFILE => return error.SystemFdQuotaExceeded,
31573199 .NODEV => return error.NoDevice,
31583200 .NOENT => return error.FileNotFound,
3201 .SRCH => return error.ProcessNotFound,
31593202 .NOMEM => return error.SystemResources,
3203 .NOSPC => return error.NoSpaceLeft,
31603204 .NOTDIR => return error.NotDir,
31613205 .PERM => return error.PermissionDenied,
3206 .EXIST => return error.PathAlreadyExists,
31623207 .BUSY => return error.DeviceBusy,
3163 .NOTCAPABLE => return error.AccessDenied,
3208 .NXIO => return error.NoDevice,
31643209 .ILSEQ => return error.BadPathName,
31653210 else => |err| return posix.unexpectedErrno(err),
31663211 }
31673212 },
31683213 }
3169 }
3170}
3214 };
3215 errdefer posix.close(fd);
31713216
3172fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
3173 const t: *Threaded = @ptrCast(@alignCast(userdata));
3174 _ = t;
3175 posix.close(file.handle);
3217 switch (native_os) {
3218 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
3219 // On macOS, we can use F.GETPATH fcntl command to query the OS for
3220 // the path to the file descriptor.
3221 @memset(out_buffer, 0);
3222 try current_thread.beginSyscall();
3223 while (true) {
3224 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
3225 .SUCCESS => {
3226 current_thread.endSyscall();
3227 break;
3228 },
3229 .INTR => {
3230 try current_thread.checkCancel();
3231 continue;
3232 },
3233 .CANCELED => return current_thread.endSyscallCanceled(),
3234 else => |e| {
3235 current_thread.endSyscall();
3236 switch (e) {
3237 .BADF => return error.FileNotFound,
3238 .NOSPC => return error.NameTooLong,
3239 .NOENT => return error.FileNotFound,
3240 // TODO man pages for fcntl on macOS don't really tell you what
3241 // errno values to expect when command is F.GETPATH...
3242 else => |err| return posix.unexpectedErrno(err),
3243 }
3244 },
3245 }
3246 }
3247 return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len;
3248 },
3249 .linux, .serenity, .illumos => {
3250 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
3251 const template = if (native_os == .illumos) "/proc/self/path/{d}" else "/proc/self/fd/{d}";
3252 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, template, .{fd}, 0) catch unreachable;
3253 try current_thread.beginSyscall();
3254 while (true) {
3255 const rc = posix.system.readlink(proc_path, out_buffer.ptr, out_buffer.len);
3256 switch (posix.errno(rc)) {
3257 .SUCCESS => {
3258 current_thread.endSyscall();
3259 const len: usize = @bitCast(rc);
3260 return len;
3261 },
3262 .INTR => {
3263 try current_thread.checkCancel();
3264 continue;
3265 },
3266 .CANCELED => return current_thread.endSyscallCanceled(),
3267 else => |e| {
3268 current_thread.endSyscall();
3269 switch (e) {
3270 .ACCES => return error.AccessDenied,
3271 .FAULT => |err| return errnoBug(err),
3272 .INVAL => return error.NotLink,
3273 .IO => return error.FileSystem,
3274 .LOOP => return error.SymLinkLoop,
3275 .NAMETOOLONG => return error.NameTooLong,
3276 .NOENT => return error.FileNotFound,
3277 .NOMEM => return error.SystemResources,
3278 .NOTDIR => return error.NotDir,
3279 .ILSEQ => |err| return errnoBug(err),
3280 else => |err| return posix.unexpectedErrno(err),
3281 }
3282 },
3283 }
3284 }
3285 },
3286 .freebsd => {
3287 var kfile: std.c.kinfo_file = undefined;
3288 kfile.structsize = std.c.KINFO_FILE_SIZE;
3289 try current_thread.beginSyscall();
3290 while (true) {
3291 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
3292 .SUCCESS => {
3293 current_thread.endSyscall();
3294 break;
3295 },
3296 .INTR => {
3297 try current_thread.checkCancel();
3298 continue;
3299 },
3300 .CANCELED => return current_thread.endSyscallCanceled(),
3301 else => |e| {
3302 current_thread.endSyscall();
3303 switch (e) {
3304 .BADF => return error.FileNotFound,
3305 else => |err| return posix.unexpectedErrno(err),
3306 }
3307 },
3308 }
3309 }
3310 const len = std.mem.indexOfScalar(u8, &kfile.path, 0) orelse kfile.path.len;
3311 if (len == 0) return error.NameTooLong;
3312 return len;
3313 },
3314 .netbsd, .dragonfly => {
3315 @memset(out_buffer[0..max_path_bytes], 0);
3316 try current_thread.beginSyscall();
3317 while (true) {
3318 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
3319 .SUCCESS => {
3320 current_thread.endSyscall();
3321 break;
3322 },
3323 .INTR => {
3324 try current_thread.checkCancel();
3325 continue;
3326 },
3327 .CANCELED => return current_thread.endSyscallCanceled(),
3328 else => |e| {
3329 current_thread.endSyscall();
3330 switch (e) {
3331 .ACCES => return error.AccessDenied,
3332 .BADF => return error.FileNotFound,
3333 .NOENT => return error.FileNotFound,
3334 .NOMEM => return error.SystemResources,
3335 .RANGE => return error.NameTooLong,
3336 else => |err| return posix.unexpectedErrno(err),
3337 }
3338 },
3339 }
3340 }
3341 return std.mem.indexOfScalar(u8, &out_buffer, 0) orelse out_buffer.len;
3342 },
3343 else => @compileError("unsupported OS"),
3344 }
3345 comptime unreachable;
31763346}
31773347
3178const fileReadStreaming = switch (native_os) {
3179 .windows => fileReadStreamingWindows,
3180 else => fileReadStreamingPosix,
3348const dirDeleteFile = switch (native_os) {
3349 .windows => dirDeleteFileWindows,
3350 .wasi => dirDeleteFileWasi,
3351 else => dirDeleteFilePosix,
31813352};
31823353
3183fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
3354fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
3355 return dirDeleteWindows(userdata, dir, sub_path, false);
3356}
3357
3358fn dirDeleteFileWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
3359 if (builtin.link_libc) return dirDeleteFilePosix(userdata, dir, sub_path);
31843360 const t: *Threaded = @ptrCast(@alignCast(userdata));
31853361 const current_thread = Thread.getCurrent(t);
3186
3187 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
3188 var i: usize = 0;
3189 for (data) |buf| {
3190 if (iovecs_buffer.len - i == 0) break;
3191 if (buf.len != 0) {
3192 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
3193 i += 1;
3194 }
3195 }
3196 const dest = iovecs_buffer[0..i];
3197 assert(dest[0].len > 0);
3198
3199 if (native_os == .wasi and !builtin.link_libc) {
3200 try current_thread.beginSyscall();
3201 while (true) {
3202 var nread: usize = undefined;
3203 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
3204 .SUCCESS => {
3205 current_thread.endSyscall();
3206 return nread;
3207 },
3208 .INTR => {
3209 try current_thread.checkCancel();
3210 continue;
3211 },
3212 .CANCELED => return current_thread.endSyscallCanceled(),
3213 else => |e| {
3214 current_thread.endSyscall();
3215 switch (e) {
3216 .INVAL => |err| return errnoBug(err),
3217 .FAULT => |err| return errnoBug(err),
3218 .BADF => return error.NotOpenForReading, // File operation on directory.
3219 .IO => return error.InputOutput,
3220 .ISDIR => return error.IsDir,
3221 .NOBUFS => return error.SystemResources,
3222 .NOMEM => return error.SystemResources,
3223 .NOTCONN => return error.SocketUnconnected,
3224 .CONNRESET => return error.ConnectionResetByPeer,
3225 .TIMEDOUT => return error.Timeout,
3226 .NOTCAPABLE => return error.AccessDenied,
3227 else => |err| return posix.unexpectedErrno(err),
3228 }
3229 },
3230 }
3231 }
3232 }
3233
32343362 try current_thread.beginSyscall();
32353363 while (true) {
3236 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
3237 switch (posix.errno(rc)) {
3364 const res = std.os.wasi.path_unlink_file(dir.handle, sub_path.ptr, sub_path.len);
3365 switch (res) {
32383366 .SUCCESS => {
32393367 current_thread.endSyscall();
3240 return @intCast(rc);
3368 return;
32413369 },
32423370 .INTR => {
32433371 try current_thread.checkCancel();
......@@ -3247,21 +3375,23 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
32473375 else => |e| {
32483376 current_thread.endSyscall();
32493377 switch (e) {
3250 .INVAL => |err| return errnoBug(err),
3378 .ACCES => return error.AccessDenied,
3379 .PERM => return error.PermissionDenied,
3380 .BUSY => return error.FileBusy,
32513381 .FAULT => |err| return errnoBug(err),
3252 .SRCH => return error.ProcessNotFound,
3253 .AGAIN => return error.WouldBlock,
3254 .BADF => |err| {
3255 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
3256 return errnoBug(err); // File descriptor used after closed.
3257 },
3258 .IO => return error.InputOutput,
3382 .IO => return error.FileSystem,
32593383 .ISDIR => return error.IsDir,
3260 .NOBUFS => return error.SystemResources,
3384 .LOOP => return error.SymLinkLoop,
3385 .NAMETOOLONG => return error.NameTooLong,
3386 .NOENT => return error.FileNotFound,
3387 .NOTDIR => return error.NotDir,
32613388 .NOMEM => return error.SystemResources,
3262 .NOTCONN => return error.SocketUnconnected,
3263 .CONNRESET => return error.ConnectionResetByPeer,
3264 .TIMEDOUT => return error.Timeout,
3389 .ROFS => return error.ReadOnlyFileSystem,
3390 .NOTEMPTY => return error.DirNotEmpty,
3391 .NOTCAPABLE => return error.AccessDenied,
3392 .ILSEQ => return error.BadPathName,
3393 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
3394 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
32653395 else => |err| return posix.unexpectedErrno(err),
32663396 }
32673397 },
......@@ -3269,40 +3399,1259 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io
32693399 }
32703400}
32713401
3272fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
3402fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void {
32733403 const t: *Threaded = @ptrCast(@alignCast(userdata));
32743404 const current_thread = Thread.getCurrent(t);
32753405
3276 const DWORD = windows.DWORD;
3277 var index: usize = 0;
3278 while (data[index].len == 0) index += 1;
3279 const buffer = data[index];
3280 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
3406 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3407 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
32813408
3409 try current_thread.beginSyscall();
32823410 while (true) {
3283 try current_thread.checkCancel();
3284 var n: DWORD = undefined;
3285 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
3286 return n;
3287 switch (windows.GetLastError()) {
3288 .IO_PENDING => |err| return windows.errorBug(err),
3289 .OPERATION_ABORTED => continue,
3290 .BROKEN_PIPE => return 0,
3291 .HANDLE_EOF => return 0,
3292 .NETNAME_DELETED => return error.ConnectionResetByPeer,
3293 .LOCK_VIOLATION => return error.LockViolation,
3294 .ACCESS_DENIED => return error.AccessDenied,
3295 .INVALID_HANDLE => return error.NotOpenForReading,
3296 else => |err| return windows.unexpectedError(err),
3297 }
3298 }
3299}
3300
3301fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
3302 const t: *Threaded = @ptrCast(@alignCast(userdata));
3303 const current_thread = Thread.getCurrent(t);
3304
3305 if (!have_preadv) @compileError("TODO");
3411 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, 0))) {
3412 .SUCCESS => {
3413 current_thread.endSyscall();
3414 return;
3415 },
3416 .INTR => {
3417 try current_thread.checkCancel();
3418 continue;
3419 },
3420 .CANCELED => return current_thread.endSyscallCanceled(),
3421 // Some systems return permission errors when trying to delete a
3422 // directory, so we need to handle that case specifically and
3423 // translate the error.
3424 .PERM => switch (native_os) {
3425 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
3426
3427 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them).
3428 var st = std.mem.zeroes(posix.Stat);
3429 while (true) {
3430 try current_thread.checkCancel();
3431 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &st, posix.AT.SYMLINK_NOFOLLOW))) {
3432 .SUCCESS => {
3433 current_thread.endSyscall();
3434 break;
3435 },
3436 .INTR => continue,
3437 .CANCELED => return current_thread.endSyscallCanceled(),
3438 else => {
3439 current_thread.endSyscall();
3440 return error.PermissionDenied;
3441 },
3442 }
3443 }
3444 const is_dir = st.mode & posix.S.IFMT == posix.S.IFDIR;
3445 if (is_dir)
3446 return error.IsDir
3447 else
3448 return error.PermissionDenied;
3449 },
3450 else => {
3451 current_thread.endSyscall();
3452 return error.PermissionDenied;
3453 },
3454 },
3455 else => |e| {
3456 current_thread.endSyscall();
3457 switch (e) {
3458 .ACCES => return error.AccessDenied,
3459 .BUSY => return error.FileBusy,
3460 .FAULT => |err| return errnoBug(err),
3461 .IO => return error.FileSystem,
3462 .ISDIR => return error.IsDir,
3463 .LOOP => return error.SymLinkLoop,
3464 .NAMETOOLONG => return error.NameTooLong,
3465 .NOENT => return error.FileNotFound,
3466 .NOTDIR => return error.NotDir,
3467 .NOMEM => return error.SystemResources,
3468 .ROFS => return error.ReadOnlyFileSystem,
3469 .EXIST => |err| return errnoBug(err),
3470 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR
3471 .ILSEQ => return error.BadPathName,
3472 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
3473 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3474 else => |err| return posix.unexpectedErrno(err),
3475 }
3476 },
3477 }
3478 }
3479}
3480
3481const dirDeleteDir = switch (native_os) {
3482 .windows => dirDeleteDirWindows,
3483 .wasi => dirDeleteDirWasi,
3484 else => dirDeleteDirPosix,
3485};
3486
3487fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
3488 return dirDeleteWindows(userdata, dir, sub_path, true);
3489}
3490
3491fn dirDeleteWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, remove_dir: bool) Io.Dir.DeleteFileError!void {
3492 const t: *Threaded = @ptrCast(@alignCast(userdata));
3493 const current_thread = Thread.getCurrent(t);
3494 const w = windows;
3495
3496 try current_thread.checkCancel();
3497
3498 const sub_path_w = try w.sliceToPrefixedFileW(dir.handle, sub_path);
3499
3500 const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
3501 var nt_name: w.UNICODE_STRING = .{
3502 .Length = path_len_bytes,
3503 .MaximumLength = path_len_bytes,
3504 // The Windows API makes this mutable, but it will not mutate here.
3505 .Buffer = @constCast(sub_path_w.ptr),
3506 };
3507
3508 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
3509 // Windows does not recognize this, but it does work with empty string.
3510 nt_name.Length = 0;
3511 }
3512 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
3513 // Can't remove the parent directory with an open handle.
3514 return error.FileBusy;
3515 }
3516
3517 const create_options_flags: w.ULONG = if (remove_dir)
3518 w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT
3519 else
3520 w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT;
3521
3522 var attr: w.OBJECT_ATTRIBUTES = .{
3523 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
3524 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
3525 .Attributes = w.OBJ_CASE_INSENSITIVE,
3526 .ObjectName = &nt_name,
3527 .SecurityDescriptor = null,
3528 .SecurityQualityOfService = null,
3529 };
3530 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3531 var tmp_handle: w.HANDLE = undefined;
3532 var rc = w.ntdll.NtCreateFile(
3533 &tmp_handle,
3534 w.SYNCHRONIZE | w.DELETE,
3535 &attr,
3536 &io_status_block,
3537 null,
3538 0,
3539 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
3540 w.FILE_OPEN,
3541 create_options_flags,
3542 null,
3543 0,
3544 );
3545 switch (rc) {
3546 .SUCCESS => {},
3547 .OBJECT_NAME_INVALID => unreachable,
3548 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3549 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3550 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
3551 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
3552 .INVALID_PARAMETER => unreachable,
3553 .FILE_IS_A_DIRECTORY => return error.IsDir,
3554 .NOT_A_DIRECTORY => return error.NotDir,
3555 .SHARING_VIOLATION => return error.FileBusy,
3556 .ACCESS_DENIED => return error.AccessDenied,
3557 .DELETE_PENDING => return,
3558 else => return w.unexpectedStatus(rc),
3559 }
3560 defer w.CloseHandle(tmp_handle);
3561
3562 // FileDispositionInformationEx has varying levels of support:
3563 // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
3564 // (INVALID_INFO_CLASS is returned if not supported)
3565 // - Requires the NTFS filesystem
3566 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
3567 // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
3568 // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
3569 // (NOT_SUPPORTED is returned if a flag is unsupported)
3570 //
3571 // The strategy here is just to try using FileDispositionInformationEx and fall back to
3572 // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
3573 const need_fallback = need_fallback: {
3574 try current_thread.checkCancel();
3575
3576 // Deletion with posix semantics if the filesystem supports it.
3577 var info: w.FILE_DISPOSITION_INFORMATION_EX = .{
3578 .Flags = w.FILE_DISPOSITION_DELETE |
3579 w.FILE_DISPOSITION_POSIX_SEMANTICS |
3580 w.FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
3581 };
3582
3583 rc = w.ntdll.NtSetInformationFile(
3584 tmp_handle,
3585 &io_status_block,
3586 &info,
3587 @sizeOf(w.FILE_DISPOSITION_INFORMATION_EX),
3588 .FileDispositionInformationEx,
3589 );
3590 switch (rc) {
3591 .SUCCESS => return,
3592 // The filesystem does not support FileDispositionInformationEx
3593 .INVALID_PARAMETER,
3594 // The operating system does not support FileDispositionInformationEx
3595 .INVALID_INFO_CLASS,
3596 // The operating system does not support one of the flags
3597 .NOT_SUPPORTED,
3598 => break :need_fallback true,
3599 // For all other statuses, fall down to the switch below to handle them.
3600 else => break :need_fallback false,
3601 }
3602 };
3603
3604 if (need_fallback) {
3605 try current_thread.checkCancel();
3606
3607 // Deletion with file pending semantics, which requires waiting or moving
3608 // files to get them removed (from here).
3609 var file_dispo: w.FILE_DISPOSITION_INFORMATION = .{
3610 .DeleteFile = w.TRUE,
3611 };
3612
3613 rc = w.ntdll.NtSetInformationFile(
3614 tmp_handle,
3615 &io_status_block,
3616 &file_dispo,
3617 @sizeOf(w.FILE_DISPOSITION_INFORMATION),
3618 .FileDispositionInformation,
3619 );
3620 }
3621 switch (rc) {
3622 .SUCCESS => {},
3623 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
3624 .INVALID_PARAMETER => unreachable,
3625 .CANNOT_DELETE => return error.AccessDenied,
3626 .MEDIA_WRITE_PROTECTED => return error.AccessDenied,
3627 .ACCESS_DENIED => return error.AccessDenied,
3628 else => return w.unexpectedStatus(rc),
3629 }
3630}
3631
3632fn dirDeleteDirWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
3633 if (builtin.link_libc) return dirDeleteDirPosix(userdata, dir, sub_path);
3634
3635 const t: *Threaded = @ptrCast(@alignCast(userdata));
3636 const current_thread = Thread.getCurrent(t);
3637
3638 try current_thread.beginSyscall();
3639 while (true) {
3640 const res = std.os.wasi.path_remove_directory(dir.handle, sub_path.ptr, sub_path.len);
3641 switch (res) {
3642 .SUCCESS => {
3643 current_thread.endSyscall();
3644 return;
3645 },
3646 .INTR => {
3647 try current_thread.checkCancel();
3648 continue;
3649 },
3650 .CANCELED => return current_thread.endSyscallCanceled(),
3651 else => |e| {
3652 current_thread.endSyscall();
3653 switch (e) {
3654 .ACCES => return error.AccessDenied,
3655 .PERM => return error.PermissionDenied,
3656 .BUSY => return error.FileBusy,
3657 .FAULT => |err| return errnoBug(err),
3658 .IO => return error.FileSystem,
3659 .ISDIR => return error.IsDir,
3660 .LOOP => return error.SymLinkLoop,
3661 .NAMETOOLONG => return error.NameTooLong,
3662 .NOENT => return error.FileNotFound,
3663 .NOTDIR => return error.NotDir,
3664 .NOMEM => return error.SystemResources,
3665 .ROFS => return error.ReadOnlyFileSystem,
3666 .NOTEMPTY => return error.DirNotEmpty,
3667 .NOTCAPABLE => return error.AccessDenied,
3668 .ILSEQ => return error.BadPathName,
3669 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
3670 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3671 else => |err| return posix.unexpectedErrno(err),
3672 }
3673 },
3674 }
3675 }
3676}
3677
3678fn dirDeleteDirPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void {
3679 const t: *Threaded = @ptrCast(@alignCast(userdata));
3680 const current_thread = Thread.getCurrent(t);
3681
3682 var path_buffer: [posix.PATH_MAX]u8 = undefined;
3683 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3684
3685 try current_thread.beginSyscall();
3686 while (true) {
3687 switch (posix.errno(posix.system.unlinkat(dir.handle, sub_path_posix, posix.AT.REMOVEDIR))) {
3688 .SUCCESS => {
3689 current_thread.endSyscall();
3690 return;
3691 },
3692 .INTR => {
3693 try current_thread.checkCancel();
3694 continue;
3695 },
3696 .CANCELED => return current_thread.endSyscallCanceled(),
3697 else => |e| {
3698 current_thread.endSyscall();
3699 switch (e) {
3700 .ACCES => return error.AccessDenied,
3701 .PERM => return error.PermissionDenied,
3702 .BUSY => return error.FileBusy,
3703 .FAULT => |err| return errnoBug(err),
3704 .IO => return error.FileSystem,
3705 .ISDIR => |err| return errnoBug(err),
3706 .LOOP => return error.SymLinkLoop,
3707 .NAMETOOLONG => return error.NameTooLong,
3708 .NOENT => return error.FileNotFound,
3709 .NOTDIR => return error.NotDir,
3710 .NOMEM => return error.SystemResources,
3711 .ROFS => return error.ReadOnlyFileSystem,
3712 .EXIST => |err| return errnoBug(err),
3713 .NOTEMPTY => |err| return errnoBug(err), // Not passing AT.REMOVEDIR
3714 .ILSEQ => return error.BadPathName,
3715 .INVAL => |err| return errnoBug(err), // invalid flags, or pathname has . as last component
3716 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3717 else => |err| return posix.unexpectedErrno(err),
3718 }
3719 },
3720 }
3721 }
3722}
3723
3724const dirRename = switch (native_os) {
3725 .windows => dirRenameWindows,
3726 .wasi => dirRenameWasi,
3727 else => dirRenamePosix,
3728};
3729
3730fn dirRenameWindows(
3731 userdata: ?*anyopaque,
3732 old_dir: Io.Dir,
3733 old_sub_path: []const u8,
3734 new_dir: Io.Dir,
3735 new_sub_path: []const u8,
3736) Io.Dir.RenameError!void {
3737 const w = windows;
3738 const t: *Threaded = @ptrCast(@alignCast(userdata));
3739 const current_thread = Thread.getCurrent(t);
3740
3741 const old_path_w = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
3742 const new_path_w = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
3743 const replace_if_exists = true;
3744
3745 try current_thread.checkCancel();
3746
3747 const src_fd = w.OpenFile(old_path_w, .{
3748 .dir = old_dir.handle,
3749 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | w.DELETE,
3750 .creation = w.FILE_OPEN,
3751 .filter = .any, // This function is supposed to rename both files and directories.
3752 .follow_symlinks = false,
3753 }) catch |err| switch (err) {
3754 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
3755 else => |e| return e,
3756 };
3757 defer w.CloseHandle(src_fd);
3758
3759 var rc: w.NTSTATUS = undefined;
3760 // FileRenameInformationEx has varying levels of support:
3761 // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
3762 // (INVALID_INFO_CLASS is returned if not supported)
3763 // - Requires the NTFS filesystem
3764 // (on filesystems like FAT32, INVALID_PARAMETER is returned)
3765 // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
3766 // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
3767 // (NOT_SUPPORTED is returned if a flag is unsupported)
3768 //
3769 // The strategy here is just to try using FileRenameInformationEx and fall back to
3770 // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
3771 const need_fallback = need_fallback: {
3772 const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + (w.PATH_MAX_WIDE * 2);
3773 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION_EX)) = undefined;
3774 const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + new_path_w.len * 2;
3775 if (struct_len > struct_buf_len) return error.NameTooLong;
3776
3777 const rename_info: *w.FILE_RENAME_INFORMATION_EX = @ptrCast(&rename_info_buf);
3778 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3779
3780 var flags: w.ULONG = w.FILE_RENAME_POSIX_SEMANTICS | w.FILE_RENAME_IGNORE_READONLY_ATTRIBUTE;
3781 if (replace_if_exists) flags |= w.FILE_RENAME_REPLACE_IF_EXISTS;
3782 rename_info.* = .{
3783 .Flags = flags,
3784 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
3785 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
3786 .FileName = undefined,
3787 };
3788 @memcpy((&rename_info.FileName).ptr, new_path_w);
3789 rc = w.ntdll.NtSetInformationFile(
3790 src_fd,
3791 &io_status_block,
3792 rename_info,
3793 @intCast(struct_len), // already checked for error.NameTooLong
3794 .FileRenameInformationEx,
3795 );
3796 switch (rc) {
3797 .SUCCESS => return,
3798 // The filesystem does not support FileDispositionInformationEx
3799 .INVALID_PARAMETER,
3800 // The operating system does not support FileDispositionInformationEx
3801 .INVALID_INFO_CLASS,
3802 // The operating system does not support one of the flags
3803 .NOT_SUPPORTED,
3804 => break :need_fallback true,
3805 // For all other statuses, fall down to the switch below to handle them.
3806 else => break :need_fallback false,
3807 }
3808 };
3809
3810 if (need_fallback) {
3811 const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION) + (w.PATH_MAX_WIDE * 2);
3812 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION)) = undefined;
3813 const struct_len = @sizeOf(w.FILE_RENAME_INFORMATION) + new_path_w.len * 2;
3814 if (struct_len > struct_buf_len) return error.NameTooLong;
3815
3816 const rename_info: *w.FILE_RENAME_INFORMATION = @ptrCast(&rename_info_buf);
3817 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3818
3819 rename_info.* = .{
3820 .Flags = @intFromBool(replace_if_exists),
3821 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir.handle,
3822 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
3823 .FileName = undefined,
3824 };
3825 @memcpy((&rename_info.FileName).ptr, new_path_w);
3826
3827 rc = w.ntdll.NtSetInformationFile(
3828 src_fd,
3829 &io_status_block,
3830 rename_info,
3831 @intCast(struct_len), // already checked for error.NameTooLong
3832 .FileRenameInformation,
3833 );
3834 }
3835
3836 switch (rc) {
3837 .SUCCESS => {},
3838 .INVALID_HANDLE => unreachable,
3839 .INVALID_PARAMETER => unreachable,
3840 .OBJECT_PATH_SYNTAX_BAD => unreachable,
3841 .ACCESS_DENIED => return error.AccessDenied,
3842 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3843 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3844 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
3845 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
3846 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
3847 .FILE_IS_A_DIRECTORY => return error.IsDir,
3848 .NOT_A_DIRECTORY => return error.NotDir,
3849 else => return w.unexpectedStatus(rc),
3850 }
3851}
3852
3853fn dirRenameWasi(
3854 userdata: ?*anyopaque,
3855 old_dir: Io.Dir,
3856 old_sub_path: []const u8,
3857 new_dir: Io.Dir,
3858 new_sub_path: []const u8,
3859) Io.Dir.RenameError!void {
3860 if (builtin.link_libc) return dirRenamePosix(userdata, old_dir, old_sub_path, new_dir, new_sub_path);
3861
3862 const t: *Threaded = @ptrCast(@alignCast(userdata));
3863 const current_thread = Thread.getCurrent(t);
3864
3865 try current_thread.beginSyscall();
3866 while (true) {
3867 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)) {
3868 .SUCCESS => return current_thread.endSyscall(),
3869 .CANCELED => return current_thread.endSyscallCanceled(),
3870 .INTR => {
3871 try current_thread.checkCancel();
3872 continue;
3873 },
3874 else => |e| {
3875 current_thread.endSyscall();
3876 switch (e) {
3877 .ACCES => return error.AccessDenied,
3878 .PERM => return error.PermissionDenied,
3879 .BUSY => return error.FileBusy,
3880 .DQUOT => return error.DiskQuota,
3881 .FAULT => |err| return errnoBug(err),
3882 .INVAL => |err| return errnoBug(err),
3883 .ISDIR => return error.IsDir,
3884 .LOOP => return error.SymLinkLoop,
3885 .MLINK => return error.LinkQuotaExceeded,
3886 .NAMETOOLONG => return error.NameTooLong,
3887 .NOENT => return error.FileNotFound,
3888 .NOTDIR => return error.NotDir,
3889 .NOMEM => return error.SystemResources,
3890 .NOSPC => return error.NoSpaceLeft,
3891 .EXIST => return error.PathAlreadyExists,
3892 .NOTEMPTY => return error.PathAlreadyExists,
3893 .ROFS => return error.ReadOnlyFileSystem,
3894 .XDEV => return error.RenameAcrossMountPoints,
3895 .NOTCAPABLE => return error.AccessDenied,
3896 .ILSEQ => return error.BadPathName,
3897 else => |err| return posix.unexpectedErrno(err),
3898 }
3899 },
3900 }
3901 }
3902}
3903
3904fn dirRenamePosix(
3905 userdata: ?*anyopaque,
3906 old_dir: Io.Dir,
3907 old_sub_path: []const u8,
3908 new_dir: Io.Dir,
3909 new_sub_path: []const u8,
3910) Io.Dir.RenameError!void {
3911 const t: *Threaded = @ptrCast(@alignCast(userdata));
3912 const current_thread = Thread.getCurrent(t);
3913
3914 var old_path_buffer: [posix.PATH_MAX]u8 = undefined;
3915 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
3916
3917 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
3918 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
3919
3920 try current_thread.beginSyscall();
3921 while (true) {
3922 switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
3923 .SUCCESS => return current_thread.endSyscall(),
3924 .CANCELED => return current_thread.endSyscallCanceled(),
3925 .INTR => {
3926 try current_thread.checkCancel();
3927 continue;
3928 },
3929 else => |e| {
3930 current_thread.endSyscall();
3931 switch (e) {
3932 .ACCES => return error.AccessDenied,
3933 .PERM => return error.PermissionDenied,
3934 .BUSY => return error.FileBusy,
3935 .DQUOT => return error.DiskQuota,
3936 .FAULT => |err| return errnoBug(err),
3937 .INVAL => |err| return errnoBug(err),
3938 .ISDIR => return error.IsDir,
3939 .LOOP => return error.SymLinkLoop,
3940 .MLINK => return error.LinkQuotaExceeded,
3941 .NAMETOOLONG => return error.NameTooLong,
3942 .NOENT => return error.FileNotFound,
3943 .NOTDIR => return error.NotDir,
3944 .NOMEM => return error.SystemResources,
3945 .NOSPC => return error.NoSpaceLeft,
3946 .EXIST => return error.PathAlreadyExists,
3947 .NOTEMPTY => return error.PathAlreadyExists,
3948 .ROFS => return error.ReadOnlyFileSystem,
3949 .XDEV => return error.RenameAcrossMountPoints,
3950 .ILSEQ => return error.BadPathName,
3951 else => |err| return posix.unexpectedErrno(err),
3952 }
3953 },
3954 }
3955 }
3956}
3957
3958const dirSymLink = switch (native_os) {
3959 .windows => dirSymLinkWindows,
3960 .wasi => dirSymLinkWasi,
3961 else => dirSymLinkPosix,
3962};
3963
3964fn dirSymLinkWindows(
3965 userdata: ?*anyopaque,
3966 dir: Io.Dir,
3967 target_path: []const u8,
3968 sym_link_path: []const u8,
3969 flags: Io.Dir.SymLinkFlags,
3970) Io.Dir.SymLinkError!void {
3971 const t: *Threaded = @ptrCast(@alignCast(userdata));
3972 const current_thread = Thread.getCurrent(t);
3973 const w = windows;
3974
3975 try current_thread.checkCancel();
3976
3977 // Target path does not use sliceToPrefixedFileW because certain paths
3978 // are handled differently when creating a symlink than they would be
3979 // when converting to an NT namespaced path. CreateSymbolicLink in
3980 // symLinkW will handle the necessary conversion.
3981 var target_path_w: w.PathSpace = undefined;
3982 target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path);
3983 target_path_w.data[target_path_w.len] = 0;
3984 // However, we need to canonicalize any path separators to `\`, since if
3985 // the target path is relative, then it must use `\` as the path separator.
3986 std.mem.replaceScalar(
3987 u16,
3988 target_path_w.data[0..target_path_w.len],
3989 std.mem.nativeToLittle(u16, '/'),
3990 std.mem.nativeToLittle(u16, '\\'),
3991 );
3992
3993 const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path);
3994
3995 const SYMLINK_DATA = extern struct {
3996 ReparseTag: w.ULONG,
3997 ReparseDataLength: w.USHORT,
3998 Reserved: w.USHORT,
3999 SubstituteNameOffset: w.USHORT,
4000 SubstituteNameLength: w.USHORT,
4001 PrintNameOffset: w.USHORT,
4002 PrintNameLength: w.USHORT,
4003 Flags: w.ULONG,
4004 };
4005
4006 const symlink_handle = w.OpenFile(sym_link_path_w.span(), .{
4007 .access_mask = w.SYNCHRONIZE | w.GENERIC_READ | w.GENERIC_WRITE,
4008 .dir = dir,
4009 .creation = w.FILE_CREATE,
4010 .filter = if (flags.is_directory) .dir_only else .file_only,
4011 }) catch |err| switch (err) {
4012 error.IsDir => return error.PathAlreadyExists,
4013 error.NotDir => return error.Unexpected,
4014 error.WouldBlock => return error.Unexpected,
4015 error.PipeBusy => return error.Unexpected,
4016 error.NoDevice => return error.Unexpected,
4017 error.AntivirusInterference => return error.Unexpected,
4018 else => |e| return e,
4019 };
4020 defer w.CloseHandle(symlink_handle);
4021
4022 // Relevant portions of the documentation:
4023 // > Relative links are specified using the following conventions:
4024 // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32".
4025 // > - Current working directory–relative—for example, if the current working directory is
4026 // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt".
4027 // > Note: If you specify a current working directory–relative link, it is created as an absolute
4028 // > link, due to the way the current working directory is processed based on the user and the thread.
4029 // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
4030 var is_target_absolute = false;
4031 const final_target_path = target_path: {
4032 if (w.hasCommonNtPrefix(u16, target_path)) {
4033 // Already an NT path, no need to do anything to it
4034 break :target_path target_path;
4035 } else {
4036 switch (w.getWin32PathType(u16, target_path)) {
4037 // Rooted paths need to avoid getting put through wToPrefixedFileW
4038 // (and they are treated as relative in this context)
4039 // Note: It seems that rooted paths in symbolic links are relative to
4040 // the drive that the symbolic exists on, not to the CWD's drive.
4041 // So, if the symlink is on C:\ and the CWD is on D:\,
4042 // it will still resolve the path relative to the root of
4043 // the C:\ drive.
4044 .rooted => break :target_path target_path,
4045 // Keep relative paths relative, but anything else needs to get NT-prefixed.
4046 else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
4047 break :target_path target_path,
4048 }
4049 }
4050 var prefixed_target_path = try w.wToPrefixedFileW(dir, target_path);
4051 // We do this after prefixing to ensure that drive-relative paths are treated as absolute
4052 is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
4053 break :target_path prefixed_target_path.span();
4054 };
4055
4056 // prepare reparse data buffer
4057 var buffer: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
4058 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
4059 const header_len = @sizeOf(w.ULONG) + @sizeOf(w.USHORT) * 2;
4060 const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);
4061 const symlink_data = SYMLINK_DATA{
4062 .ReparseTag = w.IO_REPARSE_TAG_SYMLINK,
4063 .ReparseDataLength = @intCast(buf_len - header_len),
4064 .Reserved = 0,
4065 .SubstituteNameOffset = @intCast(final_target_path.len * 2),
4066 .SubstituteNameLength = @intCast(final_target_path.len * 2),
4067 .PrintNameOffset = 0,
4068 .PrintNameLength = @intCast(final_target_path.len * 2),
4069 .Flags = if (!target_is_absolute) w.SYMLINK_FLAG_RELATIVE else 0,
4070 };
4071
4072 @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data));
4073 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
4074 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
4075 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
4076 _ = try w.DeviceIoControl(symlink_handle, w.FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
4077}
4078
4079fn dirSymLinkWasi(
4080 userdata: ?*anyopaque,
4081 dir: Io.Dir,
4082 target_path: []const u8,
4083 sym_link_path: []const u8,
4084 flags: Io.Dir.SymLinkFlags,
4085) Io.Dir.SymLinkError!void {
4086 if (builtin.link_libc) return dirSymLinkPosix(dir, target_path, sym_link_path, flags);
4087
4088 const t: *Threaded = @ptrCast(@alignCast(userdata));
4089 const current_thread = Thread.getCurrent(t);
4090
4091 try current_thread.beginSyscall();
4092 while (true) {
4093 switch (std.os.wasi.path_symlink(target_path.ptr, target_path.len, dir.handle, sym_link_path.ptr, sym_link_path.len)) {
4094 .SUCCESS => return current_thread.endSyscall(),
4095 .CANCELED => return current_thread.endSyscallCanceled(),
4096 .INTR => {
4097 try current_thread.checkCancel();
4098 continue;
4099 },
4100 else => |e| {
4101 current_thread.endSyscall();
4102 switch (e) {
4103 .FAULT => |err| return errnoBug(err),
4104 .INVAL => |err| return errnoBug(err),
4105 .BADF => |err| return errnoBug(err),
4106 .ACCES => return error.AccessDenied,
4107 .PERM => return error.PermissionDenied,
4108 .DQUOT => return error.DiskQuota,
4109 .EXIST => return error.PathAlreadyExists,
4110 .IO => return error.FileSystem,
4111 .LOOP => return error.SymLinkLoop,
4112 .NAMETOOLONG => return error.NameTooLong,
4113 .NOENT => return error.FileNotFound,
4114 .NOTDIR => return error.NotDir,
4115 .NOMEM => return error.SystemResources,
4116 .NOSPC => return error.NoSpaceLeft,
4117 .ROFS => return error.ReadOnlyFileSystem,
4118 .NOTCAPABLE => return error.AccessDenied,
4119 .ILSEQ => return error.BadPathName,
4120 else => |err| return posix.unexpectedErrno(err),
4121 }
4122 },
4123 }
4124 }
4125}
4126
4127fn dirSymLinkPosix(
4128 userdata: ?*anyopaque,
4129 dir: Io.Dir,
4130 target_path: []const u8,
4131 sym_link_path: []const u8,
4132 flags: Io.Dir.SymLinkFlags,
4133) Io.Dir.SymLinkError!void {
4134 _ = flags;
4135 const t: *Threaded = @ptrCast(@alignCast(userdata));
4136 const current_thread = Thread.getCurrent(t);
4137
4138 var target_path_buffer: [posix.PATH_MAX]u8 = undefined;
4139 var sym_link_path_buffer: [posix.PATH_MAX]u8 = undefined;
4140
4141 const target_path_posix = try pathToPosix(target_path, &target_path_buffer);
4142 const sym_link_path_posix = try pathToPosix(sym_link_path, &sym_link_path_buffer);
4143
4144 try current_thread.beginSyscall();
4145 while (true) {
4146 switch (posix.errno(posix.system.symlinkat(target_path_posix, dir.handle, sym_link_path_posix))) {
4147 .SUCCESS => return current_thread.endSyscall(),
4148 .CANCELED => return current_thread.endSyscallCanceled(),
4149 .INTR => {
4150 try current_thread.checkCancel();
4151 continue;
4152 },
4153 else => |e| {
4154 current_thread.endSyscall();
4155 switch (e) {
4156 .FAULT => |err| return errnoBug(err),
4157 .INVAL => |err| return errnoBug(err),
4158 .ACCES => return error.AccessDenied,
4159 .PERM => return error.PermissionDenied,
4160 .DQUOT => return error.DiskQuota,
4161 .EXIST => return error.PathAlreadyExists,
4162 .IO => return error.FileSystem,
4163 .LOOP => return error.SymLinkLoop,
4164 .NAMETOOLONG => return error.NameTooLong,
4165 .NOENT => return error.FileNotFound,
4166 .NOTDIR => return error.NotDir,
4167 .NOMEM => return error.SystemResources,
4168 .NOSPC => return error.NoSpaceLeft,
4169 .ROFS => return error.ReadOnlyFileSystem,
4170 .ILSEQ => return error.BadPathName,
4171 else => |err| return posix.unexpectedErrno(err),
4172 }
4173 },
4174 }
4175 }
4176}
4177
4178const dirReadLink = switch (native_os) {
4179 .windows => dirReadLinkWindows,
4180 .wasi => dirReadLinkWasi,
4181 else => dirReadLinkPosix,
4182};
4183
4184fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
4185 const t: *Threaded = @ptrCast(@alignCast(userdata));
4186 const current_thread = Thread.getCurrent(t);
4187 const w = windows;
4188
4189 try current_thread.checkCancel();
4190
4191 var sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
4192
4193 const result_handle = w.OpenFile(sub_path_w.span(), .{
4194 .access_mask = w.FILE_READ_ATTRIBUTES | w.SYNCHRONIZE,
4195 .dir = dir,
4196 .creation = w.FILE_OPEN,
4197 .follow_symlinks = false,
4198 .filter = .any,
4199 }) catch |err| switch (err) {
4200 error.IsDir, error.NotDir => return error.Unexpected, // filter = .any
4201 error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN
4202 error.WouldBlock => return error.Unexpected,
4203 error.NoDevice => return error.FileNotFound,
4204 error.PipeBusy => return error.AccessDenied,
4205 else => |e| return e,
4206 };
4207 defer w.CloseHandle(result_handle);
4208
4209 var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(w.REPARSE_DATA_BUFFER)) = undefined;
4210 _ = w.DeviceIoControl(result_handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) {
4211 error.AccessDenied => return error.Unexpected,
4212 error.UnrecognizedVolume => return error.Unexpected,
4213 else => |e| return e,
4214 };
4215
4216 const reparse_struct: *const w.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
4217 const wide_result = switch (reparse_struct.ReparseTag) {
4218 w.IO_REPARSE_TAG_SYMLINK => r: {
4219 const buf: *const w.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
4220 const offset = buf.SubstituteNameOffset >> 1;
4221 const len = buf.SubstituteNameLength >> 1;
4222 const path_buf: [*]const u16 = &buf.PathBuffer;
4223 const is_relative = buf.Flags & w.SYMLINK_FLAG_RELATIVE != 0;
4224 break :r try w.parseReadLinkPath(path_buf[offset..][0..len], is_relative, buffer);
4225 },
4226 w.IO_REPARSE_TAG_MOUNT_POINT => r: {
4227 const buf: *const w.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
4228 const offset = buf.SubstituteNameOffset >> 1;
4229 const len = buf.SubstituteNameLength >> 1;
4230 const path_buf: [*]const u16 = &buf.PathBuffer;
4231 break :r try w.parseReadLinkPath(path_buf[offset..][0..len], false, buffer);
4232 },
4233 else => return error.UnsupportedReparsePointType,
4234 };
4235
4236 const len = std.unicode.calcWtf8Len(wide_result);
4237 if (len > buffer.len) return error.NameTooLong;
4238
4239 return std.unicode.wtf16LeToWtf8(buffer, wide_result);
4240}
4241
4242fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
4243 if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer);
4244
4245 const t: *Threaded = @ptrCast(@alignCast(userdata));
4246 const current_thread = Thread.getCurrent(t);
4247
4248 var n: usize = undefined;
4249 try current_thread.beginSyscall();
4250 while (true) {
4251 switch (std.os.wasi.path_readlink(dir.handle, sub_path.ptr, sub_path.len, buffer.ptr, buffer.len, &n)) {
4252 .SUCCESS => {
4253 current_thread.endSyscall();
4254 return buffer[0..n];
4255 },
4256 .CANCELED => return current_thread.endSyscallCanceled(),
4257 .INTR => {
4258 try current_thread.checkCancel();
4259 continue;
4260 },
4261 else => |e| {
4262 current_thread.endSyscall();
4263 switch (e) {
4264 .ACCES => return error.AccessDenied,
4265 .FAULT => |err| return errnoBug(err),
4266 .INVAL => return error.NotLink,
4267 .IO => return error.FileSystem,
4268 .LOOP => return error.SymLinkLoop,
4269 .NAMETOOLONG => return error.NameTooLong,
4270 .NOENT => return error.FileNotFound,
4271 .NOMEM => return error.SystemResources,
4272 .NOTDIR => return error.NotDir,
4273 .NOTCAPABLE => return error.AccessDenied,
4274 .ILSEQ => return error.BadPathName,
4275 else => |err| return posix.unexpectedErrno(err),
4276 }
4277 },
4278 }
4279 }
4280}
4281
4282fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, buffer: []u8) Io.Dir.ReadLinkError!usize {
4283 const t: *Threaded = @ptrCast(@alignCast(userdata));
4284 const current_thread = Thread.getCurrent(t);
4285
4286 var sub_path_buffer: [posix.PATH_MAX]u8 = undefined;
4287 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
4288
4289 try current_thread.beginSyscall();
4290 while (true) {
4291 const rc = posix.system.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
4292 switch (posix.errno(rc)) {
4293 .SUCCESS => {
4294 current_thread.endSyscall();
4295 const len: usize = @bitCast(rc);
4296 return len;
4297 },
4298 .CANCELED => return current_thread.endSyscallCanceled(),
4299 .INTR => {
4300 try current_thread.checkCancel();
4301 continue;
4302 },
4303 else => |e| {
4304 current_thread.endSyscall();
4305 switch (e) {
4306 .ACCES => return error.AccessDenied,
4307 .FAULT => |err| return errnoBug(err),
4308 .INVAL => return error.NotLink,
4309 .IO => return error.FileSystem,
4310 .LOOP => return error.SymLinkLoop,
4311 .NAMETOOLONG => return error.NameTooLong,
4312 .NOENT => return error.FileNotFound,
4313 .NOMEM => return error.SystemResources,
4314 .NOTDIR => return error.NotDir,
4315 .ILSEQ => return error.BadPathName,
4316 else => |err| return posix.unexpectedErrno(err),
4317 }
4318 },
4319 }
4320 }
4321}
4322
4323const dirSetMode = switch (native_os) {
4324 .windows => dirSetModeUnsupported,
4325 else => dirSetModePosix,
4326};
4327
4328fn dirSetModeUnsupported(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void {
4329 _ = userdata;
4330 _ = dir;
4331 _ = new_mode;
4332 return error.Unexpected;
4333}
4334
4335fn dirSetModePosix(userdata: ?*anyopaque, dir: Io.Dir, new_mode: Io.Dir.Mode) Io.Dir.SetModeError!void {
4336 const t: *Threaded = @ptrCast(@alignCast(userdata));
4337 const current_thread = Thread.getCurrent(t);
4338
4339 try current_thread.beginSyscall();
4340 while (true) {
4341 switch (posix.errno(posix.system.fchmod(dir.handle, new_mode))) {
4342 .SUCCESS => return current_thread.endSyscall(),
4343 .CANCELED => return current_thread.endSyscallCanceled(),
4344 .INTR => {
4345 try current_thread.checkCancel();
4346 continue;
4347 },
4348 else => |e| {
4349 current_thread.endSyscall();
4350 switch (e) {
4351 .BADF => |err| return errnoBug(err),
4352 .FAULT => |err| return errnoBug(err),
4353 .INVAL => |err| return errnoBug(err),
4354 .ACCES => return error.AccessDenied,
4355 .IO => return error.InputOutput,
4356 .LOOP => return error.SymLinkLoop,
4357 .NOENT => return error.FileNotFound,
4358 .NOMEM => return error.SystemResources,
4359 .NOTDIR => return error.FileNotFound,
4360 .PERM => return error.PermissionDenied,
4361 .ROFS => return error.ReadOnlyFileSystem,
4362 else => |err| return posix.unexpectedErrno(err),
4363 }
4364 },
4365 }
4366 }
4367}
4368
4369const dirSetOwner = switch (native_os) {
4370 .windows => dirSetOwnerUnsupported,
4371 else => dirSetOwnerPosix,
4372};
4373
4374fn dirSetOwnerUnsupported(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
4375 _ = userdata;
4376 _ = dir;
4377 _ = owner;
4378 _ = group;
4379 return error.Unexpected;
4380}
4381
4382fn dirSetOwnerPosix(userdata: ?*anyopaque, dir: Io.Dir, owner: ?Io.File.Uid, group: ?Io.File.Gid) Io.Dir.SetOwnerError!void {
4383 const t: *Threaded = @ptrCast(@alignCast(userdata));
4384 const current_thread = Thread.getCurrent(t);
4385 const uid = owner orelse ~@as(posix.uid_t, 0);
4386 const gid = group orelse ~@as(posix.gid_t, 0);
4387
4388 try current_thread.beginSyscall();
4389 while (true) {
4390 switch (posix.errno(posix.system.fchown(dir.handle, uid, gid))) {
4391 .SUCCESS => return current_thread.endSyscall(),
4392 .CANCELED => return current_thread.endSyscallCanceled(),
4393 .INTR => {
4394 try current_thread.checkCancel();
4395 continue;
4396 },
4397 else => |e| {
4398 current_thread.endSyscall();
4399 switch (e) {
4400 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Io.Dir.OpenOptions.iterate`
4401 .FAULT => |err| return errnoBug(err),
4402 .INVAL => |err| return errnoBug(err),
4403 .ACCES => return error.AccessDenied,
4404 .IO => return error.InputOutput,
4405 .LOOP => return error.SymLinkLoop,
4406 .NOENT => return error.FileNotFound,
4407 .NOMEM => return error.SystemResources,
4408 .NOTDIR => return error.FileNotFound,
4409 .PERM => return error.PermissionDenied,
4410 .ROFS => return error.ReadOnlyFileSystem,
4411 else => |err| return posix.unexpectedErrno(err),
4412 }
4413 },
4414 }
4415 }
4416}
4417
4418const dirSetPermissions = switch (native_os) {
4419 .windows => dirSetPermissionsWindows,
4420 else => dirSetPermissionsPosix,
4421};
4422
4423fn dirSetPermissionsWindows(
4424 userdata: ?*anyopaque,
4425 dir: Io.Dir,
4426 permissions: Io.Dir.Permissions,
4427) Io.Dir.SetPermissionsError!void {
4428 _ = userdata;
4429 _ = dir;
4430 _ = permissions;
4431 @panic("TODO");
4432}
4433
4434fn dirSetPermissionsPosix(
4435 userdata: ?*anyopaque,
4436 dir: Io.Dir,
4437 permissions: Io.Dir.Permissions,
4438) Io.Dir.SetPermissionsError!void {
4439 _ = userdata;
4440 _ = dir;
4441 _ = permissions;
4442 @panic("TODO");
4443}
4444
4445fn dirOpenDirWasi(
4446 userdata: ?*anyopaque,
4447 dir: Io.Dir,
4448 sub_path: []const u8,
4449 options: Io.Dir.OpenOptions,
4450) Io.Dir.OpenError!Io.Dir {
4451 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
4452 const t: *Threaded = @ptrCast(@alignCast(userdata));
4453 const current_thread = Thread.getCurrent(t);
4454 const wasi = std.os.wasi;
4455
4456 var base: std.os.wasi.rights_t = .{
4457 .FD_FILESTAT_GET = true,
4458 .FD_FDSTAT_SET_FLAGS = true,
4459 .FD_FILESTAT_SET_TIMES = true,
4460 };
4461 if (options.access_sub_paths) {
4462 base.FD_READDIR = true;
4463 base.PATH_CREATE_DIRECTORY = true;
4464 base.PATH_CREATE_FILE = true;
4465 base.PATH_LINK_SOURCE = true;
4466 base.PATH_LINK_TARGET = true;
4467 base.PATH_OPEN = true;
4468 base.PATH_READLINK = true;
4469 base.PATH_RENAME_SOURCE = true;
4470 base.PATH_RENAME_TARGET = true;
4471 base.PATH_FILESTAT_GET = true;
4472 base.PATH_FILESTAT_SET_SIZE = true;
4473 base.PATH_FILESTAT_SET_TIMES = true;
4474 base.PATH_SYMLINK = true;
4475 base.PATH_REMOVE_DIRECTORY = true;
4476 base.PATH_UNLINK_FILE = true;
4477 }
4478
4479 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };
4480 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
4481 const fdflags: wasi.fdflags_t = .{};
4482 var fd: posix.fd_t = undefined;
4483 try current_thread.beginSyscall();
4484 while (true) {
4485 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
4486 .SUCCESS => {
4487 current_thread.endSyscall();
4488 return .{ .handle = fd };
4489 },
4490 .INTR => {
4491 try current_thread.checkCancel();
4492 continue;
4493 },
4494 .CANCELED => return current_thread.endSyscallCanceled(),
4495 else => |e| {
4496 current_thread.endSyscall();
4497 switch (e) {
4498 .FAULT => |err| return errnoBug(err),
4499 .INVAL => return error.BadPathName,
4500 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4501 .ACCES => return error.AccessDenied,
4502 .LOOP => return error.SymLinkLoop,
4503 .MFILE => return error.ProcessFdQuotaExceeded,
4504 .NAMETOOLONG => return error.NameTooLong,
4505 .NFILE => return error.SystemFdQuotaExceeded,
4506 .NODEV => return error.NoDevice,
4507 .NOENT => return error.FileNotFound,
4508 .NOMEM => return error.SystemResources,
4509 .NOTDIR => return error.NotDir,
4510 .PERM => return error.PermissionDenied,
4511 .BUSY => return error.DeviceBusy,
4512 .NOTCAPABLE => return error.AccessDenied,
4513 .ILSEQ => return error.BadPathName,
4514 else => |err| return posix.unexpectedErrno(err),
4515 }
4516 },
4517 }
4518 }
4519}
4520
4521fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
4522 const t: *Threaded = @ptrCast(@alignCast(userdata));
4523 _ = t;
4524 posix.close(file.handle);
4525}
4526
4527const fileReadStreaming = switch (native_os) {
4528 .windows => fileReadStreamingWindows,
4529 else => fileReadStreamingPosix,
4530};
4531
4532fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
4533 const t: *Threaded = @ptrCast(@alignCast(userdata));
4534 const current_thread = Thread.getCurrent(t);
4535
4536 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
4537 var i: usize = 0;
4538 for (data) |buf| {
4539 if (iovecs_buffer.len - i == 0) break;
4540 if (buf.len != 0) {
4541 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
4542 i += 1;
4543 }
4544 }
4545 const dest = iovecs_buffer[0..i];
4546 assert(dest[0].len > 0);
4547
4548 if (native_os == .wasi and !builtin.link_libc) {
4549 try current_thread.beginSyscall();
4550 while (true) {
4551 var nread: usize = undefined;
4552 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
4553 .SUCCESS => {
4554 current_thread.endSyscall();
4555 return nread;
4556 },
4557 .INTR => {
4558 try current_thread.checkCancel();
4559 continue;
4560 },
4561 .CANCELED => return current_thread.endSyscallCanceled(),
4562 else => |e| {
4563 current_thread.endSyscall();
4564 switch (e) {
4565 .INVAL => |err| return errnoBug(err),
4566 .FAULT => |err| return errnoBug(err),
4567 .BADF => return error.NotOpenForReading, // File operation on directory.
4568 .IO => return error.InputOutput,
4569 .ISDIR => return error.IsDir,
4570 .NOBUFS => return error.SystemResources,
4571 .NOMEM => return error.SystemResources,
4572 .NOTCONN => return error.SocketUnconnected,
4573 .CONNRESET => return error.ConnectionResetByPeer,
4574 .TIMEDOUT => return error.Timeout,
4575 .NOTCAPABLE => return error.AccessDenied,
4576 else => |err| return posix.unexpectedErrno(err),
4577 }
4578 },
4579 }
4580 }
4581 }
4582
4583 try current_thread.beginSyscall();
4584 while (true) {
4585 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
4586 switch (posix.errno(rc)) {
4587 .SUCCESS => {
4588 current_thread.endSyscall();
4589 return @intCast(rc);
4590 },
4591 .INTR => {
4592 try current_thread.checkCancel();
4593 continue;
4594 },
4595 .CANCELED => return current_thread.endSyscallCanceled(),
4596 else => |e| {
4597 current_thread.endSyscall();
4598 switch (e) {
4599 .INVAL => |err| return errnoBug(err),
4600 .FAULT => |err| return errnoBug(err),
4601 .SRCH => return error.ProcessNotFound,
4602 .AGAIN => return error.WouldBlock,
4603 .BADF => |err| {
4604 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
4605 return errnoBug(err); // File descriptor used after closed.
4606 },
4607 .IO => return error.InputOutput,
4608 .ISDIR => return error.IsDir,
4609 .NOBUFS => return error.SystemResources,
4610 .NOMEM => return error.SystemResources,
4611 .NOTCONN => return error.SocketUnconnected,
4612 .CONNRESET => return error.ConnectionResetByPeer,
4613 .TIMEDOUT => return error.Timeout,
4614 else => |err| return posix.unexpectedErrno(err),
4615 }
4616 },
4617 }
4618 }
4619}
4620
4621fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
4622 const t: *Threaded = @ptrCast(@alignCast(userdata));
4623 const current_thread = Thread.getCurrent(t);
4624
4625 const DWORD = windows.DWORD;
4626 var index: usize = 0;
4627 while (data[index].len == 0) index += 1;
4628 const buffer = data[index];
4629 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
4630
4631 while (true) {
4632 try current_thread.checkCancel();
4633 var n: DWORD = undefined;
4634 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
4635 return n;
4636 switch (windows.GetLastError()) {
4637 .IO_PENDING => |err| return windows.errorBug(err),
4638 .OPERATION_ABORTED => continue,
4639 .BROKEN_PIPE => return 0,
4640 .HANDLE_EOF => return 0,
4641 .NETNAME_DELETED => return error.ConnectionResetByPeer,
4642 .LOCK_VIOLATION => return error.LockViolation,
4643 .ACCESS_DENIED => return error.AccessDenied,
4644 .INVALID_HANDLE => return error.NotOpenForReading,
4645 else => |err| return windows.unexpectedError(err),
4646 }
4647 }
4648}
4649
4650fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
4651 const t: *Threaded = @ptrCast(@alignCast(userdata));
4652 const current_thread = Thread.getCurrent(t);
4653
4654 if (!have_preadv) @compileError("TODO");
33064655
33074656 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
33084657 var i: usize = 0;
......@@ -3445,10 +4794,100 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8,
34454794
34464795fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
34474796 const t: *Threaded = @ptrCast(@alignCast(userdata));
3448 _ = t;
3449 _ = file;
3450 _ = offset;
3451 @panic("TODO implement fileSeekBy");
4797 const current_thread = Thread.getCurrent(t);
4798 const fd = file.handle;
4799
4800 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
4801 var result: u64 = undefined;
4802 try current_thread.beginSyscall();
4803 while (true) {
4804 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.CUR))) {
4805 .SUCCESS => {
4806 current_thread.endSyscall();
4807 return;
4808 },
4809 .INTR => {
4810 try current_thread.checkCancel();
4811 continue;
4812 },
4813 .CANCELED => return current_thread.endSyscallCanceled(),
4814 else => |e| {
4815 current_thread.endSyscall();
4816 switch (e) {
4817 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4818 .INVAL => return error.Unseekable,
4819 .OVERFLOW => return error.Unseekable,
4820 .SPIPE => return error.Unseekable,
4821 .NXIO => return error.Unseekable,
4822 else => |err| return posix.unexpectedErrno(err),
4823 }
4824 },
4825 }
4826 }
4827 }
4828
4829 if (native_os == .windows) {
4830 try current_thread.checkCancel();
4831 return windows.SetFilePointerEx_CURRENT(fd, offset);
4832 }
4833
4834 if (native_os == .wasi and !builtin.link_libc) {
4835 var new_offset: std.os.wasi.filesize_t = undefined;
4836 try current_thread.beginSyscall();
4837 while (true) {
4838 switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) {
4839 .SUCCESS => {
4840 current_thread.endSyscall();
4841 return;
4842 },
4843 .INTR => {
4844 try current_thread.checkCancel();
4845 continue;
4846 },
4847 .CANCELED => return current_thread.endSyscallCanceled(),
4848 else => |e| {
4849 current_thread.endSyscall();
4850 switch (e) {
4851 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4852 .INVAL => return error.Unseekable,
4853 .OVERFLOW => return error.Unseekable,
4854 .SPIPE => return error.Unseekable,
4855 .NXIO => return error.Unseekable,
4856 .NOTCAPABLE => return error.AccessDenied,
4857 else => |err| return posix.unexpectedErrno(err),
4858 }
4859 },
4860 }
4861 }
4862 }
4863
4864 if (posix.SEEK == void) return error.Unseekable;
4865
4866 try current_thread.beginSyscall();
4867 while (true) {
4868 switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) {
4869 .SUCCESS => {
4870 current_thread.endSyscall();
4871 return;
4872 },
4873 .INTR => {
4874 try current_thread.checkCancel();
4875 continue;
4876 },
4877 .CANCELED => return current_thread.endSyscallCanceled(),
4878 else => |e| {
4879 current_thread.endSyscall();
4880 switch (e) {
4881 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4882 .INVAL => return error.Unseekable,
4883 .OVERFLOW => return error.Unseekable,
4884 .SPIPE => return error.Unseekable,
4885 .NXIO => return error.Unseekable,
4886 else => |err| return posix.unexpectedErrno(err),
4887 }
4888 },
4889 }
4890 }
34524891}
34534892
34544893fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
lib/std/debug.zig+46-34
......@@ -1104,7 +1104,14 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
11041104 return ptr;
11051105}
11061106
1107fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1107fn printSourceAtAddress(
1108 gpa: Allocator,
1109 io: Io,
1110 debug_info: *SelfInfo,
1111 writer: *Writer,
1112 address: usize,
1113 tty_config: tty.Config,
1114) Writer.Error!void {
11081115 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
11091116 error.MissingDebugInfo,
11101117 error.UnsupportedDebugInfo,
......@@ -1125,6 +1132,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *
11251132 };
11261133 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
11271134 return printLineInfo(
1135 io,
11281136 writer,
11291137 symbol.source_location,
11301138 address,
......@@ -1134,6 +1142,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *
11341142 );
11351143}
11361144fn printLineInfo(
1145 io: Io,
11371146 writer: *Writer,
11381147 source_location: ?SourceLocation,
11391148 address: usize,
......@@ -1159,7 +1168,7 @@ fn printLineInfo(
11591168
11601169 // Show the matching source code line if possible
11611170 if (source_location) |sl| {
1162 if (printLineFromFile(writer, sl)) {
1171 if (printLineFromFile(io, writer, sl)) {
11631172 if (sl.column > 0) {
11641173 // The caret already takes one char
11651174 const space_needed = @as(usize, @intCast(sl.column - 1));
......@@ -1177,16 +1186,17 @@ fn printLineInfo(
11771186 }
11781187 }
11791188}
1180fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
1189fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void {
11811190 // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.
11821191 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
1183 return root.debug.printLineFromFile(writer, source_location);
1192 return root.debug.printLineFromFile(io, writer, source_location);
11841193 }
11851194
11861195 // Need this to always block even in async I/O mode, because this could potentially
11871196 // be called from e.g. the event loop code crashing.
1188 var f = try fs.cwd().openFile(source_location.file_name, .{});
1189 defer f.close();
1197 const cwd: Io.Dir = .cwd();
1198 var f = try cwd.openFile(io, source_location.file_name, .{});
1199 defer f.close(io);
11901200 // TODO fstat and make sure that the file has the correct size
11911201
11921202 var buf: [4096]u8 = undefined;
......@@ -1237,11 +1247,13 @@ fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
12371247}
12381248
12391249test printLineFromFile {
1240 var aw: Writer.Allocating = .init(std.testing.allocator);
1250 const io = std.testing.io;
1251 const gpa = std.testing.allocator;
1252
1253 var aw: Writer.Allocating = .init(gpa);
12411254 defer aw.deinit();
12421255 const output_stream = &aw.writer;
12431256
1244 const allocator = std.testing.allocator;
12451257 const join = std.fs.path.join;
12461258 const expectError = std.testing.expectError;
12471259 const expectEqualStrings = std.testing.expectEqualStrings;
......@@ -1249,24 +1261,24 @@ test printLineFromFile {
12491261 var test_dir = std.testing.tmpDir(.{});
12501262 defer test_dir.cleanup();
12511263 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
1252 const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
1253 defer allocator.free(test_dir_path);
1264 const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
1265 defer gpa.free(test_dir_path);
12541266
12551267 // Cases
12561268 {
1257 const path = try join(allocator, &.{ test_dir_path, "one_line.zig" });
1258 defer allocator.free(path);
1269 const path = try join(gpa, &.{ test_dir_path, "one_line.zig" });
1270 defer gpa.free(path);
12591271 try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
12601272
1261 try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1273 try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
12621274
1263 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1275 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12641276 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
12651277 aw.clearRetainingCapacity();
12661278 }
12671279 {
1268 const path = try fs.path.join(allocator, &.{ test_dir_path, "three_lines.zig" });
1269 defer allocator.free(path);
1280 const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" });
1281 defer gpa.free(path);
12701282 try test_dir.dir.writeFile(.{
12711283 .sub_path = "three_lines.zig",
12721284 .data =
......@@ -1276,19 +1288,19 @@ test printLineFromFile {
12761288 ,
12771289 });
12781290
1279 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1291 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
12801292 try expectEqualStrings("1\n", aw.written());
12811293 aw.clearRetainingCapacity();
12821294
1283 try printLineFromFile(output_stream, .{ .file_name = path, .line = 3, .column = 0 });
1295 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 });
12841296 try expectEqualStrings("3\n", aw.written());
12851297 aw.clearRetainingCapacity();
12861298 }
12871299 {
12881300 const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{});
12891301 defer file.close();
1290 const path = try fs.path.join(allocator, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1291 defer allocator.free(path);
1302 const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1303 defer gpa.free(path);
12921304
12931305 const overlap = 10;
12941306 var buf: [16]u8 = undefined;
......@@ -1299,55 +1311,55 @@ test printLineFromFile {
12991311 try writer.splatByteAll('a', overlap);
13001312 try writer.flush();
13011313
1302 try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1314 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
13031315 try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written());
13041316 aw.clearRetainingCapacity();
13051317 }
13061318 {
13071319 const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{});
13081320 defer file.close();
1309 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1310 defer allocator.free(path);
1321 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1322 defer gpa.free(path);
13111323
13121324 var file_writer = file.writer(&.{});
13131325 const writer = &file_writer.interface;
13141326 try writer.splatByteAll('a', std.heap.page_size_max);
13151327
1316 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1328 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
13171329 try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written());
13181330 aw.clearRetainingCapacity();
13191331 }
13201332 {
13211333 const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{});
13221334 defer file.close();
1323 const path = try fs.path.join(allocator, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1324 defer allocator.free(path);
1335 const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1336 defer gpa.free(path);
13251337
13261338 var file_writer = file.writer(&.{});
13271339 const writer = &file_writer.interface;
13281340 try writer.splatByteAll('a', 3 * std.heap.page_size_max);
13291341
1330 try expectError(error.EndOfFile, printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1342 try expectError(error.EndOfFile, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
13311343
1332 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1344 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
13331345 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written());
13341346 aw.clearRetainingCapacity();
13351347
13361348 try writer.writeAll("a\na");
13371349
1338 try printLineFromFile(output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1350 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
13391351 try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written());
13401352 aw.clearRetainingCapacity();
13411353
1342 try printLineFromFile(output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1354 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
13431355 try expectEqualStrings("a\n", aw.written());
13441356 aw.clearRetainingCapacity();
13451357 }
13461358 {
13471359 const file = try test_dir.dir.createFile("file_of_newlines.zig", .{});
13481360 defer file.close();
1349 const path = try fs.path.join(allocator, &.{ test_dir_path, "file_of_newlines.zig" });
1350 defer allocator.free(path);
1361 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" });
1362 defer gpa.free(path);
13511363
13521364 var file_writer = file.writer(&.{});
13531365 const writer = &file_writer.interface;
......@@ -1355,11 +1367,11 @@ test printLineFromFile {
13551367 try writer.splatByteAll('\n', real_file_start);
13561368 try writer.writeAll("abc\ndef");
13571369
1358 try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
1370 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
13591371 try expectEqualStrings("abc\n", aw.written());
13601372 aw.clearRetainingCapacity();
13611373
1362 try printLineFromFile(output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
1374 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
13631375 try expectEqualStrings("def\n", aw.written());
13641376 aw.clearRetainingCapacity();
13651377 }
lib/std/fs.zig+14-55
......@@ -15,9 +15,13 @@ const windows = std.os.windows;
1515
1616const is_darwin = native_os.isDarwin();
1717
18pub const AtomicFile = @import("fs/AtomicFile.zig");
19pub const Dir = @import("fs/Dir.zig");
20pub const File = @import("fs/File.zig");
18/// Deprecated.
19pub const AtomicFile = std.Io.File.Atomic;
20/// Deprecated.
21pub const Dir = std.Io.Dir;
22/// Deprecated.
23pub const File = std.Io.File;
24
2125pub const path = @import("fs/path.zig");
2226
2327pub const has_executable_bit = switch (native_os) {
......@@ -153,42 +157,9 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
153157 return posix.rmdirZ(dir_path);
154158}
155159
156/// Same as `Dir.rename` except the paths are absolute.
157/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
158/// On WASI, both paths should be encoded as valid UTF-8.
159/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
160pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
161 assert(path.isAbsolute(old_path));
162 assert(path.isAbsolute(new_path));
163 return posix.rename(old_path, new_path);
164}
165
166/// Same as `renameAbsolute` except the path parameters are null-terminated.
167pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
168 assert(path.isAbsoluteZ(old_path));
169 assert(path.isAbsoluteZ(new_path));
170 return posix.renameZ(old_path, new_path);
171}
172
173/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
174pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
175 return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
176}
177
178/// Same as `rename` except the parameters are null-terminated.
179pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
180 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
181}
182
183160/// Deprecated in favor of `Io.Dir.cwd`.
184pub fn cwd() Dir {
185 if (native_os == .windows) {
186 return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };
187 } else if (native_os == .wasi) {
188 return .{ .fd = std.options.wasiCwd() };
189 } else {
190 return .{ .fd = posix.AT.FDCWD };
191 }
161pub fn cwd() Io.Dir {
162 return .cwd();
192163}
193164
194165pub fn defaultWasiCwd() std.os.wasi.fd_t {
......@@ -209,23 +180,11 @@ pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.O
209180 return cwd().openDir(absolute_path, flags);
210181}
211182
212/// Same as `openDirAbsolute` but the path parameter is null-terminated.
213pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions) File.OpenError!Dir {
214 assert(path.isAbsoluteZ(absolute_path_c));
215 return cwd().openDirZ(absolute_path_c, flags);
216}
217/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
218/// Call `File.close` to release the resource.
219/// Asserts that the path is absolute. See `Dir.openFile` for a function that
220/// operates on both absolute and relative paths.
221/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteZ` for a function
222/// that accepts a null-terminated path.
223/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
224/// On WASI, `absolute_path` should be encoded as valid UTF-8.
225/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
226pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
227 assert(path.isAbsolute(absolute_path));
228 return cwd().openFile(absolute_path, flags);
183/// Deprecated in favor of `Io.File.openAbsolute`.
184pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Io.File.OpenError!Io.File {
185 var threaded: Io.Threaded = .init_single_threaded;
186 const io = threaded.ioBasic();
187 return Io.File.openAbsolute(io, absolute_path, flags);
229188}
230189
231190/// Test accessing `path`.
lib/std/fs/AtomicFile.zig deleted-94
......@@ -1,94 +0,0 @@
1const AtomicFile = @This();
2const std = @import("../std.zig");
3const File = std.fs.File;
4const Dir = std.fs.Dir;
5const fs = std.fs;
6const assert = std.debug.assert;
7const posix = std.posix;
8
9file_writer: File.Writer,
10random_integer: u64,
11dest_basename: []const u8,
12file_open: bool,
13file_exists: bool,
14close_dir_on_deinit: bool,
15dir: Dir,
16
17pub const InitError = File.OpenError;
18
19/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
20pub fn init(
21 dest_basename: []const u8,
22 mode: File.Mode,
23 dir: Dir,
24 close_dir_on_deinit: bool,
25 write_buffer: []u8,
26) InitError!AtomicFile {
27 while (true) {
28 const random_integer = std.crypto.random.int(u64);
29 const tmp_sub_path = std.fmt.hex(random_integer);
30 const file = dir.createFile(&tmp_sub_path, .{ .mode = mode, .exclusive = true }) catch |err| switch (err) {
31 error.PathAlreadyExists => continue,
32 else => |e| return e,
33 };
34 return .{
35 .file_writer = file.writer(write_buffer),
36 .random_integer = random_integer,
37 .dest_basename = dest_basename,
38 .file_open = true,
39 .file_exists = true,
40 .close_dir_on_deinit = close_dir_on_deinit,
41 .dir = dir,
42 };
43 }
44}
45
46/// Always call deinit, even after a successful finish().
47pub fn deinit(af: *AtomicFile) void {
48 if (af.file_open) {
49 af.file_writer.file.close();
50 af.file_open = false;
51 }
52 if (af.file_exists) {
53 const tmp_sub_path = std.fmt.hex(af.random_integer);
54 af.dir.deleteFile(&tmp_sub_path) catch {};
55 af.file_exists = false;
56 }
57 if (af.close_dir_on_deinit) {
58 af.dir.close();
59 }
60 af.* = undefined;
61}
62
63pub const FlushError = File.WriteError;
64
65pub fn flush(af: *AtomicFile) FlushError!void {
66 af.file_writer.interface.flush() catch |err| switch (err) {
67 error.WriteFailed => return af.file_writer.err.?,
68 };
69}
70
71pub const RenameIntoPlaceError = posix.RenameError;
72
73/// On Windows, this function introduces a period of time where some file
74/// system operations on the destination file will result in
75/// `error.AccessDenied`, including rename operations (such as the one used in
76/// this function).
77pub fn renameIntoPlace(af: *AtomicFile) RenameIntoPlaceError!void {
78 assert(af.file_exists);
79 if (af.file_open) {
80 af.file_writer.file.close();
81 af.file_open = false;
82 }
83 const tmp_sub_path = std.fmt.hex(af.random_integer);
84 try posix.renameat(af.dir.fd, &tmp_sub_path, af.dir.fd, af.dest_basename);
85 af.file_exists = false;
86}
87
88pub const FinishError = FlushError || RenameIntoPlaceError;
89
90/// Combination of `flush` followed by `renameIntoPlace`.
91pub fn finish(af: *AtomicFile) FinishError!void {
92 try af.flush();
93 try af.renameIntoPlace();
94}
lib/std/fs/Dir.zig deleted-2066
......@@ -1,2066 +0,0 @@
1//! Deprecated in favor of `Io.Dir`.
2const Dir = @This();
3
4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const File = std.fs.File;
10const AtomicFile = std.fs.AtomicFile;
11const base64_encoder = fs.base64_encoder;
12const posix = std.posix;
13const mem = std.mem;
14const path = fs.path;
15const fs = std.fs;
16const Allocator = std.mem.Allocator;
17const assert = std.debug.assert;
18const linux = std.os.linux;
19const windows = std.os.windows;
20const have_flock = @TypeOf(posix.system.flock) != void;
21
22fd: Handle,
23
24pub const Handle = posix.fd_t;
25
26pub const default_mode = 0o755;
27
28pub const Entry = struct {
29 name: []const u8,
30 kind: Kind,
31
32 pub const Kind = File.Kind;
33};
34
35const IteratorError = error{
36 AccessDenied,
37 PermissionDenied,
38 SystemResources,
39} || posix.UnexpectedError;
40
41pub const Iterator = switch (native_os) {
42 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => struct {
43 dir: Dir,
44 seek: i64,
45 buf: [1024]u8 align(@alignOf(posix.system.dirent)),
46 index: usize,
47 end_index: usize,
48 first_iter: bool,
49
50 const Self = @This();
51
52 pub const Error = IteratorError;
53
54 /// Memory such as file names referenced in this returned entry becomes invalid
55 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
56 pub fn next(self: *Self) Error!?Entry {
57 switch (native_os) {
58 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => return self.nextDarwin(),
59 .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
60 .illumos => return self.nextIllumos(),
61 else => @compileError("unimplemented"),
62 }
63 }
64
65 fn nextDarwin(self: *Self) !?Entry {
66 start_over: while (true) {
67 if (self.index >= self.end_index) {
68 if (self.first_iter) {
69 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
70 self.first_iter = false;
71 }
72 const rc = posix.system.getdirentries(
73 self.dir.fd,
74 &self.buf,
75 self.buf.len,
76 &self.seek,
77 );
78 if (rc == 0) return null;
79 if (rc < 0) {
80 switch (posix.errno(rc)) {
81 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
82 .FAULT => unreachable,
83 .NOTDIR => unreachable,
84 .INVAL => unreachable,
85 else => |err| return posix.unexpectedErrno(err),
86 }
87 }
88 self.index = 0;
89 self.end_index = @as(usize, @intCast(rc));
90 }
91 const darwin_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
92 const next_index = self.index + darwin_entry.reclen;
93 self.index = next_index;
94
95 const name = @as([*]u8, @ptrCast(&darwin_entry.name))[0..darwin_entry.namlen];
96
97 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.ino == 0)) {
98 continue :start_over;
99 }
100
101 const entry_kind: Entry.Kind = switch (darwin_entry.type) {
102 posix.DT.BLK => .block_device,
103 posix.DT.CHR => .character_device,
104 posix.DT.DIR => .directory,
105 posix.DT.FIFO => .named_pipe,
106 posix.DT.LNK => .sym_link,
107 posix.DT.REG => .file,
108 posix.DT.SOCK => .unix_domain_socket,
109 posix.DT.WHT => .whiteout,
110 else => .unknown,
111 };
112 return Entry{
113 .name = name,
114 .kind = entry_kind,
115 };
116 }
117 }
118
119 fn nextIllumos(self: *Self) !?Entry {
120 start_over: while (true) {
121 if (self.index >= self.end_index) {
122 if (self.first_iter) {
123 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
124 self.first_iter = false;
125 }
126 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
127 switch (posix.errno(rc)) {
128 .SUCCESS => {},
129 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
130 .FAULT => unreachable,
131 .NOTDIR => unreachable,
132 .INVAL => unreachable,
133 else => |err| return posix.unexpectedErrno(err),
134 }
135 if (rc == 0) return null;
136 self.index = 0;
137 self.end_index = @as(usize, @intCast(rc));
138 }
139 const entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
140 const next_index = self.index + entry.reclen;
141 self.index = next_index;
142
143 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.name)), 0);
144 if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
145 continue :start_over;
146
147 // illumos dirent doesn't expose type, so we have to call stat to get it.
148 const stat_info = posix.fstatat(
149 self.dir.fd,
150 name,
151 posix.AT.SYMLINK_NOFOLLOW,
152 ) catch |err| switch (err) {
153 error.NameTooLong => unreachable,
154 error.SymLinkLoop => unreachable,
155 error.FileNotFound => unreachable, // lost the race
156 else => |e| return e,
157 };
158 const entry_kind: Entry.Kind = switch (stat_info.mode & posix.S.IFMT) {
159 posix.S.IFIFO => .named_pipe,
160 posix.S.IFCHR => .character_device,
161 posix.S.IFDIR => .directory,
162 posix.S.IFBLK => .block_device,
163 posix.S.IFREG => .file,
164 posix.S.IFLNK => .sym_link,
165 posix.S.IFSOCK => .unix_domain_socket,
166 posix.S.IFDOOR => .door,
167 posix.S.IFPORT => .event_port,
168 else => .unknown,
169 };
170 return Entry{
171 .name = name,
172 .kind = entry_kind,
173 };
174 }
175 }
176
177 fn nextBsd(self: *Self) !?Entry {
178 start_over: while (true) {
179 if (self.index >= self.end_index) {
180 if (self.first_iter) {
181 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
182 self.first_iter = false;
183 }
184 const rc = posix.system.getdents(self.dir.fd, &self.buf, self.buf.len);
185 switch (posix.errno(rc)) {
186 .SUCCESS => {},
187 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
188 .FAULT => unreachable,
189 .NOTDIR => unreachable,
190 .INVAL => unreachable,
191 // Introduced in freebsd 13.2: directory unlinked but still open.
192 // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
193 .NOENT => return null,
194 else => |err| return posix.unexpectedErrno(err),
195 }
196 if (rc == 0) return null;
197 self.index = 0;
198 self.end_index = @as(usize, @intCast(rc));
199 }
200 const bsd_entry = @as(*align(1) posix.system.dirent, @ptrCast(&self.buf[self.index]));
201 const next_index = self.index +
202 if (@hasField(posix.system.dirent, "reclen")) bsd_entry.reclen else bsd_entry.reclen();
203 self.index = next_index;
204
205 const name = @as([*]u8, @ptrCast(&bsd_entry.name))[0..bsd_entry.namlen];
206
207 const skip_zero_fileno = switch (native_os) {
208 // fileno=0 is used to mark invalid entries or deleted files.
209 .openbsd, .netbsd => true,
210 else => false,
211 };
212 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
213 (skip_zero_fileno and bsd_entry.fileno == 0))
214 {
215 continue :start_over;
216 }
217
218 const entry_kind: Entry.Kind = switch (bsd_entry.type) {
219 posix.DT.BLK => .block_device,
220 posix.DT.CHR => .character_device,
221 posix.DT.DIR => .directory,
222 posix.DT.FIFO => .named_pipe,
223 posix.DT.LNK => .sym_link,
224 posix.DT.REG => .file,
225 posix.DT.SOCK => .unix_domain_socket,
226 posix.DT.WHT => .whiteout,
227 else => .unknown,
228 };
229 return Entry{
230 .name = name,
231 .kind = entry_kind,
232 };
233 }
234 }
235
236 pub fn reset(self: *Self) void {
237 self.index = 0;
238 self.end_index = 0;
239 self.first_iter = true;
240 }
241 },
242 .haiku => struct {
243 dir: Dir,
244 buf: [@sizeOf(DirEnt) + posix.PATH_MAX]u8 align(@alignOf(DirEnt)),
245 offset: usize,
246 index: usize,
247 end_index: usize,
248 first_iter: bool,
249
250 const Self = @This();
251 const DirEnt = posix.system.DirEnt;
252
253 pub const Error = IteratorError;
254
255 /// Memory such as file names referenced in this returned entry becomes invalid
256 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
257 pub fn next(self: *Self) Error!?Entry {
258 while (true) {
259 if (self.index >= self.end_index) {
260 if (self.first_iter) {
261 switch (@as(posix.E, @enumFromInt(posix.system._kern_rewind_dir(self.dir.fd)))) {
262 .SUCCESS => {},
263 .BADF => unreachable, // Dir is invalid
264 .FAULT => unreachable,
265 .NOTDIR => unreachable,
266 .INVAL => unreachable,
267 .ACCES => return error.AccessDenied,
268 .PERM => return error.PermissionDenied,
269 else => |err| return posix.unexpectedErrno(err),
270 }
271 self.first_iter = false;
272 }
273 const rc = posix.system._kern_read_dir(
274 self.dir.fd,
275 &self.buf,
276 self.buf.len,
277 self.buf.len / @sizeOf(DirEnt),
278 );
279 if (rc == 0) return null;
280 if (rc < 0) {
281 switch (@as(posix.E, @enumFromInt(rc))) {
282 .BADF => unreachable, // Dir is invalid
283 .FAULT => unreachable,
284 .NOTDIR => unreachable,
285 .INVAL => unreachable,
286 .OVERFLOW => unreachable,
287 .ACCES => return error.AccessDenied,
288 .PERM => return error.PermissionDenied,
289 else => |err| return posix.unexpectedErrno(err),
290 }
291 }
292 self.offset = 0;
293 self.index = 0;
294 self.end_index = @intCast(rc);
295 }
296 const dirent: *DirEnt = @ptrCast(@alignCast(&self.buf[self.offset]));
297 self.offset += dirent.reclen;
298 self.index += 1;
299 const name = mem.span(dirent.getName());
300 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or dirent.ino == 0) continue;
301
302 var stat_info: posix.Stat = undefined;
303 switch (@as(posix.E, @enumFromInt(posix.system._kern_read_stat(
304 self.dir.fd,
305 name,
306 false,
307 &stat_info,
308 @sizeOf(posix.Stat),
309 )))) {
310 .SUCCESS => {},
311 .INVAL => unreachable,
312 .BADF => unreachable, // Dir is invalid
313 .NOMEM => return error.SystemResources,
314 .ACCES => return error.AccessDenied,
315 .PERM => return error.PermissionDenied,
316 .FAULT => unreachable,
317 .NAMETOOLONG => unreachable,
318 .LOOP => unreachable,
319 .NOENT => continue,
320 else => |err| return posix.unexpectedErrno(err),
321 }
322 const statmode = stat_info.mode & posix.S.IFMT;
323
324 const entry_kind: Entry.Kind = switch (statmode) {
325 posix.S.IFDIR => .directory,
326 posix.S.IFBLK => .block_device,
327 posix.S.IFCHR => .character_device,
328 posix.S.IFLNK => .sym_link,
329 posix.S.IFREG => .file,
330 posix.S.IFIFO => .named_pipe,
331 else => .unknown,
332 };
333
334 return Entry{
335 .name = name,
336 .kind = entry_kind,
337 };
338 }
339 }
340
341 pub fn reset(self: *Self) void {
342 self.index = 0;
343 self.end_index = 0;
344 self.first_iter = true;
345 }
346 },
347 .linux => struct {
348 dir: Dir,
349 buf: [1024]u8 align(@alignOf(linux.dirent64)),
350 index: usize,
351 end_index: usize,
352 first_iter: bool,
353
354 const Self = @This();
355
356 pub const Error = IteratorError;
357
358 /// Memory such as file names referenced in this returned entry becomes invalid
359 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
360 pub fn next(self: *Self) Error!?Entry {
361 return self.nextLinux() catch |err| switch (err) {
362 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
363 // This matches the behavior of non-Linux UNIX platforms.
364 error.DirNotFound => null,
365 else => |e| return e,
366 };
367 }
368
369 pub const ErrorLinux = error{DirNotFound} || IteratorError;
370
371 /// Implementation of `next` that can return `error.DirNotFound` if the directory being
372 /// iterated was deleted during iteration (this error is Linux specific).
373 pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
374 start_over: while (true) {
375 if (self.index >= self.end_index) {
376 if (self.first_iter) {
377 posix.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
378 self.first_iter = false;
379 }
380 const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
381 switch (linux.errno(rc)) {
382 .SUCCESS => {},
383 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
384 .FAULT => unreachable,
385 .NOTDIR => unreachable,
386 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
387 .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
388 .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
389 else => |err| return posix.unexpectedErrno(err),
390 }
391 if (rc == 0) return null;
392 self.index = 0;
393 self.end_index = rc;
394 }
395 const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
396 const next_index = self.index + linux_entry.reclen;
397 self.index = next_index;
398
399 const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.name)), 0);
400
401 // skip . and .. entries
402 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
403 continue :start_over;
404 }
405
406 const entry_kind: Entry.Kind = switch (linux_entry.type) {
407 linux.DT.BLK => .block_device,
408 linux.DT.CHR => .character_device,
409 linux.DT.DIR => .directory,
410 linux.DT.FIFO => .named_pipe,
411 linux.DT.LNK => .sym_link,
412 linux.DT.REG => .file,
413 linux.DT.SOCK => .unix_domain_socket,
414 else => .unknown,
415 };
416 return Entry{
417 .name = name,
418 .kind = entry_kind,
419 };
420 }
421 }
422
423 pub fn reset(self: *Self) void {
424 self.index = 0;
425 self.end_index = 0;
426 self.first_iter = true;
427 }
428 },
429 .windows => struct {
430 dir: Dir,
431 buf: [1024]u8 align(@alignOf(windows.FILE_BOTH_DIR_INFORMATION)),
432 index: usize,
433 end_index: usize,
434 first_iter: bool,
435 name_data: [fs.max_name_bytes]u8,
436
437 const Self = @This();
438
439 pub const Error = IteratorError;
440
441 /// Memory such as file names referenced in this returned entry becomes invalid
442 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
443 pub fn next(self: *Self) Error!?Entry {
444 const w = windows;
445 while (true) {
446 if (self.index >= self.end_index) {
447 var io: w.IO_STATUS_BLOCK = undefined;
448 const rc = w.ntdll.NtQueryDirectoryFile(
449 self.dir.fd,
450 null,
451 null,
452 null,
453 &io,
454 &self.buf,
455 self.buf.len,
456 .BothDirectory,
457 w.FALSE,
458 null,
459 @intFromBool(self.first_iter),
460 );
461 self.first_iter = false;
462 if (io.Information == 0) return null;
463 self.index = 0;
464 self.end_index = io.Information;
465 switch (rc) {
466 .SUCCESS => {},
467 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
468
469 else => return w.unexpectedStatus(rc),
470 }
471 }
472
473 // While the official api docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
474 // this may not always be the case (e.g. due to faulty VM/Sandboxing tools)
475 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
476 if (dir_info.NextEntryOffset != 0) {
477 self.index += dir_info.NextEntryOffset;
478 } else {
479 self.index = self.buf.len;
480 }
481
482 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
483
484 if (mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' }))
485 continue;
486 const name_wtf8_len = std.unicode.wtf16LeToWtf8(self.name_data[0..], name_wtf16le);
487 const name_wtf8 = self.name_data[0..name_wtf8_len];
488 const kind: Entry.Kind = blk: {
489 const attrs = dir_info.FileAttributes;
490 if (attrs.DIRECTORY) break :blk .directory;
491 if (attrs.REPARSE_POINT) break :blk .sym_link;
492 break :blk .file;
493 };
494 return Entry{
495 .name = name_wtf8,
496 .kind = kind,
497 };
498 }
499 }
500
501 pub fn reset(self: *Self) void {
502 self.index = 0;
503 self.end_index = 0;
504 self.first_iter = true;
505 }
506 },
507 .wasi => struct {
508 dir: Dir,
509 buf: [1024]u8 align(@alignOf(std.os.wasi.dirent_t)),
510 cookie: u64,
511 index: usize,
512 end_index: usize,
513
514 const Self = @This();
515
516 pub const Error = IteratorError;
517
518 /// Memory such as file names referenced in this returned entry becomes invalid
519 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
520 pub fn next(self: *Self) Error!?Entry {
521 return self.nextWasi() catch |err| switch (err) {
522 // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
523 // This matches the behavior of non-Linux UNIX platforms.
524 error.DirNotFound => null,
525 else => |e| return e,
526 };
527 }
528
529 pub const ErrorWasi = error{DirNotFound} || IteratorError;
530
531 /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
532 /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
533 /// iterated was deleted during iteration.
534 pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
535 // We intentinally use fd_readdir even when linked with libc,
536 // since its implementation is exactly the same as below,
537 // and we avoid the code complexity here.
538 const w = std.os.wasi;
539 start_over: while (true) {
540 // According to the WASI spec, the last entry might be truncated,
541 // so we need to check if the left buffer contains the whole dirent.
542 if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
543 var bufused: usize = undefined;
544 switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
545 .SUCCESS => {},
546 .BADF => unreachable, // Dir is invalid or was opened without iteration ability
547 .FAULT => unreachable,
548 .NOTDIR => unreachable,
549 .INVAL => unreachable,
550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
551 .NOTCAPABLE => return error.AccessDenied,
552 else => |err| return posix.unexpectedErrno(err),
553 }
554 if (bufused == 0) return null;
555 self.index = 0;
556 self.end_index = bufused;
557 }
558 const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
559 const entry_size = @sizeOf(w.dirent_t);
560 const name_index = self.index + entry_size;
561 if (name_index + entry.namlen > self.end_index) {
562 // This case, the name is truncated, so we need to call readdir to store the entire name.
563 self.end_index = self.index; // Force fd_readdir in the next loop.
564 continue :start_over;
565 }
566 const name = self.buf[name_index .. name_index + entry.namlen];
567
568 const next_index = name_index + entry.namlen;
569 self.index = next_index;
570 self.cookie = entry.next;
571
572 // skip . and .. entries
573 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
574 continue :start_over;
575 }
576
577 const entry_kind: Entry.Kind = switch (entry.type) {
578 .BLOCK_DEVICE => .block_device,
579 .CHARACTER_DEVICE => .character_device,
580 .DIRECTORY => .directory,
581 .SYMBOLIC_LINK => .sym_link,
582 .REGULAR_FILE => .file,
583 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
584 else => .unknown,
585 };
586 return Entry{
587 .name = name,
588 .kind = entry_kind,
589 };
590 }
591 }
592
593 pub fn reset(self: *Self) void {
594 self.index = 0;
595 self.end_index = 0;
596 self.cookie = std.os.wasi.DIRCOOKIE_START;
597 }
598 },
599 else => @compileError("unimplemented"),
600};
601
602pub fn iterate(self: Dir) Iterator {
603 return self.iterateImpl(true);
604}
605
606/// Like `iterate`, but will not reset the directory cursor before the first
607/// iteration. This should only be used in cases where it is known that the
608/// `Dir` has not had its cursor modified yet (e.g. it was just opened).
609pub fn iterateAssumeFirstIteration(self: Dir) Iterator {
610 return self.iterateImpl(false);
611}
612
613fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator {
614 switch (native_os) {
615 .driverkit,
616 .ios,
617 .maccatalyst,
618 .macos,
619 .tvos,
620 .visionos,
621 .watchos,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 .openbsd,
626 .illumos,
627 => return Iterator{
628 .dir = self,
629 .seek = 0,
630 .index = 0,
631 .end_index = 0,
632 .buf = undefined,
633 .first_iter = first_iter_start_value,
634 },
635 .linux => return Iterator{
636 .dir = self,
637 .index = 0,
638 .end_index = 0,
639 .buf = undefined,
640 .first_iter = first_iter_start_value,
641 },
642 .haiku => return Iterator{
643 .dir = self,
644 .offset = 0,
645 .index = 0,
646 .end_index = 0,
647 .buf = undefined,
648 .first_iter = first_iter_start_value,
649 },
650 .windows => return Iterator{
651 .dir = self,
652 .index = 0,
653 .end_index = 0,
654 .first_iter = first_iter_start_value,
655 .buf = undefined,
656 .name_data = undefined,
657 },
658 .wasi => return Iterator{
659 .dir = self,
660 .cookie = std.os.wasi.DIRCOOKIE_START,
661 .index = 0,
662 .end_index = 0,
663 .buf = undefined,
664 },
665 else => @compileError("unimplemented"),
666 }
667}
668
669pub const SelectiveWalker = struct {
670 stack: std.ArrayList(Walker.StackItem),
671 name_buffer: std.ArrayList(u8),
672 allocator: Allocator,
673
674 pub const Error = IteratorError || Allocator.Error;
675
676 /// After each call to this function, and on deinit(), the memory returned
677 /// from this function becomes invalid. A copy must be made in order to keep
678 /// a reference to the path.
679 pub fn next(self: *SelectiveWalker) Error!?Walker.Entry {
680 while (self.stack.items.len > 0) {
681 const top = &self.stack.items[self.stack.items.len - 1];
682 var dirname_len = top.dirname_len;
683 if (top.iter.next() catch |err| {
684 // If we get an error, then we want the user to be able to continue
685 // walking if they want, which means that we need to pop the directory
686 // that errored from the stack. Otherwise, all future `next` calls would
687 // likely just fail with the same error.
688 var item = self.stack.pop().?;
689 if (self.stack.items.len != 0) {
690 item.iter.dir.close();
691 }
692 return err;
693 }) |entry| {
694 self.name_buffer.shrinkRetainingCapacity(dirname_len);
695 if (self.name_buffer.items.len != 0) {
696 try self.name_buffer.append(self.allocator, fs.path.sep);
697 dirname_len += 1;
698 }
699 try self.name_buffer.ensureUnusedCapacity(self.allocator, entry.name.len + 1);
700 self.name_buffer.appendSliceAssumeCapacity(entry.name);
701 self.name_buffer.appendAssumeCapacity(0);
702 const walker_entry: Walker.Entry = .{
703 .dir = top.iter.dir,
704 .basename = self.name_buffer.items[dirname_len .. self.name_buffer.items.len - 1 :0],
705 .path = self.name_buffer.items[0 .. self.name_buffer.items.len - 1 :0],
706 .kind = entry.kind,
707 };
708 return walker_entry;
709 } else {
710 var item = self.stack.pop().?;
711 if (self.stack.items.len != 0) {
712 item.iter.dir.close();
713 }
714 }
715 }
716 return null;
717 }
718
719 /// Traverses into the directory, continuing walking one level down.
720 pub fn enter(self: *SelectiveWalker, entry: Walker.Entry) !void {
721 if (entry.kind != .directory) {
722 @branchHint(.cold);
723 return;
724 }
725
726 var new_dir = entry.dir.openDir(entry.basename, .{ .iterate = true }) catch |err| {
727 switch (err) {
728 error.NameTooLong => unreachable,
729 else => |e| return e,
730 }
731 };
732 errdefer new_dir.close();
733
734 try self.stack.append(self.allocator, .{
735 .iter = new_dir.iterateAssumeFirstIteration(),
736 .dirname_len = self.name_buffer.items.len - 1,
737 });
738 }
739
740 pub fn deinit(self: *SelectiveWalker) void {
741 self.name_buffer.deinit(self.allocator);
742 self.stack.deinit(self.allocator);
743 }
744
745 /// Leaves the current directory, continuing walking one level up.
746 /// If the current entry is a directory entry, then the "current directory"
747 /// will pertain to that entry if `enter` is called before `leave`.
748 pub fn leave(self: *SelectiveWalker) void {
749 var item = self.stack.pop().?;
750 if (self.stack.items.len != 0) {
751 @branchHint(.likely);
752 item.iter.dir.close();
753 }
754 }
755};
756
757/// Recursively iterates over a directory, but requires the user to
758/// opt-in to recursing into each directory entry.
759///
760/// `self` must have been opened with `OpenOptions{.iterate = true}`.
761///
762/// `Walker.deinit` releases allocated memory and directory handles.
763///
764/// The order of returned file system entries is undefined.
765///
766/// `self` will not be closed after walking it.
767///
768/// See also `walk`.
769pub fn walkSelectively(self: Dir, allocator: Allocator) !SelectiveWalker {
770 var stack: std.ArrayList(Walker.StackItem) = .empty;
771
772 try stack.append(allocator, .{
773 .iter = self.iterate(),
774 .dirname_len = 0,
775 });
776
777 return .{
778 .stack = stack,
779 .name_buffer = .{},
780 .allocator = allocator,
781 };
782}
783
784pub const Walker = struct {
785 inner: SelectiveWalker,
786
787 pub const Entry = struct {
788 /// The containing directory. This can be used to operate directly on `basename`
789 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
790 /// The directory remains open until `next` or `deinit` is called.
791 dir: Dir,
792 basename: [:0]const u8,
793 path: [:0]const u8,
794 kind: Dir.Entry.Kind,
795
796 /// Returns the depth of the entry relative to the initial directory.
797 /// Returns 1 for a direct child of the initial directory, 2 for an entry
798 /// within a direct child of the initial directory, etc.
799 pub fn depth(self: Walker.Entry) usize {
800 return mem.countScalar(u8, self.path, fs.path.sep) + 1;
801 }
802 };
803
804 const StackItem = struct {
805 iter: Dir.Iterator,
806 dirname_len: usize,
807 };
808
809 /// After each call to this function, and on deinit(), the memory returned
810 /// from this function becomes invalid. A copy must be made in order to keep
811 /// a reference to the path.
812 pub fn next(self: *Walker) !?Walker.Entry {
813 const entry = try self.inner.next();
814 if (entry != null and entry.?.kind == .directory) {
815 try self.inner.enter(entry.?);
816 }
817 return entry;
818 }
819
820 pub fn deinit(self: *Walker) void {
821 self.inner.deinit();
822 }
823
824 /// Leaves the current directory, continuing walking one level up.
825 /// If the current entry is a directory entry, then the "current directory"
826 /// is the directory pertaining to the current entry.
827 pub fn leave(self: *Walker) void {
828 self.inner.leave();
829 }
830};
831
832/// Recursively iterates over a directory.
833///
834/// `self` must have been opened with `OpenOptions{.iterate = true}`.
835///
836/// `Walker.deinit` releases allocated memory and directory handles.
837///
838/// The order of returned file system entries is undefined.
839///
840/// `self` will not be closed after walking it.
841///
842/// See also `walkSelectively`.
843pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
844 return .{
845 .inner = try walkSelectively(self, allocator),
846 };
847}
848
849pub const OpenError = Io.Dir.OpenError;
850
851pub fn close(self: *Dir) void {
852 posix.close(self.fd);
853 self.* = undefined;
854}
855
856/// Deprecated in favor of `Io.Dir.openFile`.
857pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
858 var threaded: Io.Threaded = .init_single_threaded;
859 const io = threaded.ioBasic();
860 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
861}
862
863/// Deprecated in favor of `Io.Dir.createFile`.
864pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
865 var threaded: Io.Threaded = .init_single_threaded;
866 const io = threaded.ioBasic();
867 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
868 return .adaptFromNewApi(new_file);
869}
870
871/// Deprecated in favor of `Io.Dir.MakeError`.
872pub const MakeError = Io.Dir.MakeError;
873
874/// Deprecated in favor of `Io.Dir.makeDir`.
875pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
876 var threaded: Io.Threaded = .init_single_threaded;
877 const io = threaded.ioBasic();
878 return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path);
879}
880
881/// Deprecated in favor of `Io.Dir.makeDir`.
882pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
883 try posix.mkdiratZ(self.fd, sub_path, default_mode);
884}
885
886/// Deprecated in favor of `Io.Dir.makeDir`.
887pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
888 try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
889}
890
891/// Deprecated in favor of `Io.Dir.makePath`.
892pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
893 _ = try self.makePathStatus(sub_path);
894}
895
896/// Deprecated in favor of `Io.Dir.MakePathStatus`.
897pub const MakePathStatus = Io.Dir.MakePathStatus;
898/// Deprecated in favor of `Io.Dir.MakePathError`.
899pub const MakePathError = Io.Dir.MakePathError;
900
901/// Deprecated in favor of `Io.Dir.makePathStatus`.
902pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
903 var threaded: Io.Threaded = .init_single_threaded;
904 const io = threaded.ioBasic();
905 return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
906}
907
908/// Deprecated in favor of `Io.Dir.makeOpenPath`.
909pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
910 var threaded: Io.Threaded = .init_single_threaded;
911 const io = threaded.ioBasic();
912 return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
913}
914
915pub const RealPathError = posix.RealPathError || error{Canceled};
916
917/// This function returns the canonicalized absolute pathname of
918/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
919/// `Dir` handle and returns the canonicalized absolute pathname of `pathname`
920/// argument.
921/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
922/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
923/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
924/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
925/// This function is not universally supported by all platforms.
926/// Currently supported hosts are: Linux, macOS, and Windows.
927/// See also `Dir.realpathZ`, `Dir.realpathW`, and `Dir.realpathAlloc`.
928pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError![]u8 {
929 if (native_os == .wasi) {
930 @compileError("realpath is not available on WASI");
931 }
932 if (native_os == .windows) {
933 var pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname);
934
935 const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data);
936
937 const len = std.unicode.calcWtf8Len(wide_slice);
938 if (len > out_buffer.len)
939 return error.NameTooLong;
940
941 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
942 return out_buffer[0..end_index];
943 }
944 const pathname_c = try posix.toPosixPath(pathname);
945 return self.realpathZ(&pathname_c, out_buffer);
946}
947
948/// Same as `Dir.realpath` except `pathname` is null-terminated.
949/// See also `Dir.realpath`, `realpathZ`.
950pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathError![]u8 {
951 if (native_os == .windows) {
952 var pathname_w = try windows.cStrToPrefixedFileW(self.fd, pathname);
953
954 const wide_slice = try self.realpathW2(pathname_w.span(), &pathname_w.data);
955
956 const len = std.unicode.calcWtf8Len(wide_slice);
957 if (len > out_buffer.len)
958 return error.NameTooLong;
959
960 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
961 return out_buffer[0..end_index];
962 }
963
964 var flags: posix.O = .{};
965 if (@hasField(posix.O, "NONBLOCK")) flags.NONBLOCK = true;
966 if (@hasField(posix.O, "CLOEXEC")) flags.CLOEXEC = true;
967 if (@hasField(posix.O, "PATH")) flags.PATH = true;
968
969 const fd = posix.openatZ(self.fd, pathname, flags, 0) catch |err| switch (err) {
970 error.FileLocksNotSupported => return error.Unexpected,
971 error.FileBusy => return error.Unexpected,
972 error.WouldBlock => return error.Unexpected,
973 else => |e| return e,
974 };
975 defer posix.close(fd);
976
977 var buffer: [fs.max_path_bytes]u8 = undefined;
978 const out_path = try std.os.getFdPath(fd, &buffer);
979
980 if (out_path.len > out_buffer.len) {
981 return error.NameTooLong;
982 }
983
984 const result = out_buffer[0..out_path.len];
985 @memcpy(result, out_path);
986 return result;
987}
988
989/// Deprecated: use `realpathW2`.
990///
991/// Windows-only. Same as `Dir.realpath` except `pathname` is WTF16 LE encoded.
992/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
993/// See also `Dir.realpath`, `realpathW`.
994pub fn realpathW(self: Dir, pathname: []const u16, out_buffer: []u8) RealPathError![]u8 {
995 var wide_buf: [std.os.windows.PATH_MAX_WIDE]u16 = undefined;
996 const wide_slice = try self.realpathW2(pathname, &wide_buf);
997
998 const len = std.unicode.calcWtf8Len(wide_slice);
999 if (len > out_buffer.len) return error.NameTooLong;
1000
1001 const end_index = std.unicode.wtf16LeToWtf8(&out_buffer, wide_slice);
1002 return out_buffer[0..end_index];
1003}
1004
1005/// Windows-only. Same as `Dir.realpath` except
1006/// * `pathname` and the result are WTF-16 LE encoded
1007/// * `pathname` is relative or has the NT namespace prefix. See `windows.wToPrefixedFileW` for details.
1008///
1009/// Additionally, `pathname` will never be accessed after `out_buffer` has been written to, so it
1010/// is safe to reuse a single buffer for both.
1011///
1012/// See also `Dir.realpath`, `realpathW`.
1013pub fn realpathW2(self: Dir, pathname: []const u16, out_buffer: []u16) RealPathError![]u16 {
1014 const w = windows;
1015
1016 const h_file = blk: {
1017 const res = w.OpenFile(pathname, .{
1018 .dir = self.fd,
1019 .access_mask = .{
1020 .STANDARD = .{ .SYNCHRONIZE = true },
1021 .GENERIC = .{ .READ = true },
1022 },
1023 .creation = .OPEN,
1024 .filter = .any,
1025 }) catch |err| switch (err) {
1026 error.WouldBlock => unreachable,
1027 else => |e| return e,
1028 };
1029 break :blk res;
1030 };
1031 defer w.CloseHandle(h_file);
1032
1033 return w.GetFinalPathNameByHandle(h_file, .{}, out_buffer);
1034}
1035
1036pub const RealPathAllocError = RealPathError || Allocator.Error;
1037
1038/// Same as `Dir.realpath` except caller must free the returned memory.
1039/// See also `Dir.realpath`.
1040pub fn realpathAlloc(self: Dir, allocator: Allocator, pathname: []const u8) RealPathAllocError![]u8 {
1041 // Use of max_path_bytes here is valid as the realpath function does not
1042 // have a variant that takes an arbitrary-size buffer.
1043 // TODO(#4812): Consider reimplementing realpath or using the POSIX.1-2008
1044 // NULL out parameter (GNU's canonicalize_file_name) to handle overelong
1045 // paths. musl supports passing NULL but restricts the output to PATH_MAX
1046 // anyway.
1047 var buf: [fs.max_path_bytes]u8 = undefined;
1048 return allocator.dupe(u8, try self.realpath(pathname, buf[0..]));
1049}
1050
1051/// Changes the current working directory to the open directory handle.
1052/// This modifies global state and can have surprising effects in multi-
1053/// threaded applications. Most applications and especially libraries should
1054/// not call this function as a general rule, however it can have use cases
1055/// in, for example, implementing a shell, or child process execution.
1056/// Not all targets support this. For example, WASI does not have the concept
1057/// of a current working directory.
1058pub fn setAsCwd(self: Dir) !void {
1059 if (native_os == .wasi) {
1060 @compileError("changing cwd is not currently possible in WASI");
1061 }
1062 if (native_os == .windows) {
1063 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
1064 const dir_path = try windows.GetFinalPathNameByHandle(self.fd, .{}, &dir_path_buffer);
1065 if (builtin.link_libc) {
1066 return posix.chdirW(dir_path);
1067 }
1068 return windows.SetCurrentDirectory(dir_path);
1069 }
1070 try posix.fchdir(self.fd);
1071}
1072
1073/// Deprecated in favor of `Io.Dir.OpenOptions`.
1074pub const OpenOptions = Io.Dir.OpenOptions;
1075
1076/// Deprecated in favor of `Io.Dir.openDir`.
1077pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
1078 var threaded: Io.Threaded = .init_single_threaded;
1079 const io = threaded.ioBasic();
1080 return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
1081}
1082
1083pub const DeleteFileError = posix.UnlinkError;
1084
1085/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1086/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1087/// On WASI, `sub_path` should be encoded as valid UTF-8.
1088/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1089/// Asserts that the path parameter has no null bytes.
1090pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
1091 if (native_os == .windows) {
1092 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1093 return self.deleteFileW(sub_path_w.span());
1094 } else if (native_os == .wasi and !builtin.link_libc) {
1095 posix.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) {
1096 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1097 else => |e| return e,
1098 };
1099 } else {
1100 const sub_path_c = try posix.toPosixPath(sub_path);
1101 return self.deleteFileZ(&sub_path_c);
1102 }
1103}
1104
1105/// Same as `deleteFile` except the parameter is null-terminated.
1106pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void {
1107 posix.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) {
1108 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1109 error.AccessDenied, error.PermissionDenied => |e| switch (native_os) {
1110 // non-Linux POSIX systems return permission errors when trying to delete a
1111 // directory, so we need to handle that case specifically and translate the error
1112 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
1113 // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
1114 const fstat = posix.fstatatZ(self.fd, sub_path_c, posix.AT.SYMLINK_NOFOLLOW) catch return e;
1115 const is_dir = fstat.mode & posix.S.IFMT == posix.S.IFDIR;
1116 return if (is_dir) error.IsDir else e;
1117 },
1118 else => return e,
1119 },
1120 else => |e| return e,
1121 };
1122}
1123
1124/// Same as `deleteFile` except the parameter is WTF-16 LE encoded.
1125pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
1126 posix.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
1127 error.DirNotEmpty => unreachable, // not passing AT.REMOVEDIR
1128 else => |e| return e,
1129 };
1130}
1131
1132pub const DeleteDirError = error{
1133 DirNotEmpty,
1134 FileNotFound,
1135 AccessDenied,
1136 PermissionDenied,
1137 FileBusy,
1138 FileSystem,
1139 SymLinkLoop,
1140 NameTooLong,
1141 NotDir,
1142 SystemResources,
1143 ReadOnlyFileSystem,
1144 /// WASI: file paths must be valid UTF-8.
1145 /// Windows: file paths provided by the user must be valid WTF-8.
1146 /// https://wtf-8.codeberg.page/
1147 BadPathName,
1148 /// On Windows, `\\server` or `\\server\share` was not found.
1149 NetworkNotFound,
1150 ProcessNotFound,
1151 Unexpected,
1152};
1153
1154/// Returns `error.DirNotEmpty` if the directory is not empty.
1155/// To delete a directory recursively, see `deleteTree`.
1156/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1157/// On WASI, `sub_path` should be encoded as valid UTF-8.
1158/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1159/// Asserts that the path parameter has no null bytes.
1160pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
1161 if (native_os == .windows) {
1162 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1163 return self.deleteDirW(sub_path_w.span());
1164 } else if (native_os == .wasi and !builtin.link_libc) {
1165 posix.unlinkat(self.fd, sub_path, posix.AT.REMOVEDIR) catch |err| switch (err) {
1166 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1167 else => |e| return e,
1168 };
1169 } else {
1170 const sub_path_c = try posix.toPosixPath(sub_path);
1171 return self.deleteDirZ(&sub_path_c);
1172 }
1173}
1174
1175/// Same as `deleteDir` except the parameter is null-terminated.
1176pub fn deleteDirZ(self: Dir, sub_path_c: [*:0]const u8) DeleteDirError!void {
1177 posix.unlinkatZ(self.fd, sub_path_c, posix.AT.REMOVEDIR) catch |err| switch (err) {
1178 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1179 else => |e| return e,
1180 };
1181}
1182
1183/// Same as `deleteDir` except the parameter is WTF16LE, NT prefixed.
1184/// This function is Windows-only.
1185pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
1186 posix.unlinkatW(self.fd, sub_path_w, posix.AT.REMOVEDIR) catch |err| switch (err) {
1187 error.IsDir => unreachable, // not possible since we pass AT.REMOVEDIR
1188 else => |e| return e,
1189 };
1190}
1191
1192pub const RenameError = posix.RenameError;
1193
1194/// Change the name or location of a file or directory.
1195/// If new_sub_path already exists, it will be replaced.
1196/// Renaming a file over an existing directory or a directory
1197/// over an existing file will fail with `error.IsDir` or `error.NotDir`
1198/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1199/// On WASI, both paths should be encoded as valid UTF-8.
1200/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1201pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
1202 return posix.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
1203}
1204
1205/// Same as `rename` except the parameters are null-terminated.
1206pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
1207 return posix.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
1208}
1209
1210/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
1211/// This function is Windows-only.
1212pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
1213 return posix.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w, windows.TRUE);
1214}
1215
1216/// Use with `Dir.symLink`, `Dir.atomicSymLink`, and `symLinkAbsolute` to
1217/// specify whether the symlink will point to a file or a directory. This value
1218/// is ignored on all hosts except Windows where creating symlinks to different
1219/// resource types, requires different flags. By default, `symLinkAbsolute` is
1220/// assumed to point to a file.
1221pub const SymLinkFlags = struct {
1222 is_directory: bool = false,
1223};
1224
1225/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1226/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1227/// one; the latter case is known as a dangling link.
1228/// If `sym_link_path` exists, it will not be overwritten.
1229/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1230/// On WASI, both paths should be encoded as valid UTF-8.
1231/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1232pub fn symLink(
1233 self: Dir,
1234 target_path: []const u8,
1235 sym_link_path: []const u8,
1236 flags: SymLinkFlags,
1237) !void {
1238 if (native_os == .wasi and !builtin.link_libc) {
1239 return self.symLinkWasi(target_path, sym_link_path, flags);
1240 }
1241 if (native_os == .windows) {
1242 // Target path does not use sliceToPrefixedFileW because certain paths
1243 // are handled differently when creating a symlink than they would be
1244 // when converting to an NT namespaced path. CreateSymbolicLink in
1245 // symLinkW will handle the necessary conversion.
1246 var target_path_w: windows.PathSpace = undefined;
1247 target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
1248 target_path_w.data[target_path_w.len] = 0;
1249 // However, we need to canonicalize any path separators to `\`, since if
1250 // the target path is relative, then it must use `\` as the path separator.
1251 mem.replaceScalar(
1252 u16,
1253 target_path_w.data[0..target_path_w.len],
1254 mem.nativeToLittle(u16, '/'),
1255 mem.nativeToLittle(u16, '\\'),
1256 );
1257
1258 const sym_link_path_w = try windows.sliceToPrefixedFileW(self.fd, sym_link_path);
1259 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1260 }
1261 const target_path_c = try posix.toPosixPath(target_path);
1262 const sym_link_path_c = try posix.toPosixPath(sym_link_path);
1263 return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
1264}
1265
1266/// WASI-only. Same as `symLink` except targeting WASI.
1267pub fn symLinkWasi(
1268 self: Dir,
1269 target_path: []const u8,
1270 sym_link_path: []const u8,
1271 _: SymLinkFlags,
1272) !void {
1273 return posix.symlinkat(target_path, self.fd, sym_link_path);
1274}
1275
1276/// Same as `symLink`, except the pathname parameters are null-terminated.
1277pub fn symLinkZ(
1278 self: Dir,
1279 target_path_c: [*:0]const u8,
1280 sym_link_path_c: [*:0]const u8,
1281 flags: SymLinkFlags,
1282) !void {
1283 if (native_os == .windows) {
1284 const target_path_w = try windows.cStrToPrefixedFileW(self.fd, target_path_c);
1285 const sym_link_path_w = try windows.cStrToPrefixedFileW(self.fd, sym_link_path_c);
1286 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1287 }
1288 return posix.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1289}
1290
1291/// Windows-only. Same as `symLink` except the pathname parameters
1292/// are WTF16 LE encoded.
1293pub fn symLinkW(
1294 self: Dir,
1295 /// WTF-16, does not need to be NT-prefixed. The NT-prefixing
1296 /// of this path is handled by CreateSymbolicLink.
1297 /// Any path separators must be `\`, not `/`.
1298 target_path_w: [:0]const u16,
1299 /// WTF-16, must be NT-prefixed or relative
1300 sym_link_path_w: []const u16,
1301 flags: SymLinkFlags,
1302) !void {
1303 return windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1304}
1305
1306/// Same as `symLink`, except tries to create the symbolic link until it
1307/// succeeds or encounters an error other than `error.PathAlreadyExists`.
1308///
1309/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1310/// * On WASI, both paths should be encoded as valid UTF-8.
1311/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1312pub fn atomicSymLink(
1313 dir: Dir,
1314 target_path: []const u8,
1315 sym_link_path: []const u8,
1316 flags: SymLinkFlags,
1317) !void {
1318 if (dir.symLink(target_path, sym_link_path, flags)) {
1319 return;
1320 } else |err| switch (err) {
1321 error.PathAlreadyExists => {},
1322 else => |e| return e,
1323 }
1324
1325 const dirname = path.dirname(sym_link_path) orelse ".";
1326
1327 const rand_len = @sizeOf(u64) * 2;
1328 const temp_path_len = dirname.len + 1 + rand_len;
1329 var temp_path_buf: [fs.max_path_bytes]u8 = undefined;
1330
1331 if (temp_path_len > temp_path_buf.len) return error.NameTooLong;
1332 @memcpy(temp_path_buf[0..dirname.len], dirname);
1333 temp_path_buf[dirname.len] = path.sep;
1334
1335 const temp_path = temp_path_buf[0..temp_path_len];
1336
1337 while (true) {
1338 const random_integer = std.crypto.random.int(u64);
1339 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
1340
1341 if (dir.symLink(target_path, temp_path, flags)) {
1342 return dir.rename(temp_path, sym_link_path);
1343 } else |err| switch (err) {
1344 error.PathAlreadyExists => continue,
1345 else => |e| return e,
1346 }
1347 }
1348}
1349
1350pub const ReadLinkError = posix.ReadLinkError;
1351
1352/// Read value of a symbolic link.
1353/// The return value is a slice of `buffer`, from index `0`.
1354/// Asserts that the path parameter has no null bytes.
1355/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1356/// On WASI, `sub_path` should be encoded as valid UTF-8.
1357/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1358pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u8 {
1359 if (native_os == .wasi and !builtin.link_libc) {
1360 return self.readLinkWasi(sub_path, buffer);
1361 }
1362 if (native_os == .windows) {
1363 var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1364 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1365
1366 const len = std.unicode.calcWtf8Len(result_w);
1367 if (len > buffer.len) return error.NameTooLong;
1368
1369 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1370 return buffer[0..end_index];
1371 }
1372 const sub_path_c = try posix.toPosixPath(sub_path);
1373 return self.readLinkZ(&sub_path_c, buffer);
1374}
1375
1376/// WASI-only. Same as `readLink` except targeting WASI.
1377pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1378 return posix.readlinkat(self.fd, sub_path, buffer);
1379}
1380
1381/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
1382pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1383 if (native_os == .windows) {
1384 var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1385 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1386
1387 const len = std.unicode.calcWtf8Len(result_w);
1388 if (len > buffer.len) return error.NameTooLong;
1389
1390 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1391 return buffer[0..end_index];
1392 }
1393 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1394}
1395
1396/// Windows-only. Same as `readLink` except the path parameter
1397/// is WTF-16 LE encoded, NT-prefixed.
1398///
1399/// `sub_path_w` will never be accessed after `buffer` has been written to, so it
1400/// is safe to reuse a single buffer for both.
1401pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 {
1402 return windows.ReadLink(self.fd, sub_path_w, buffer);
1403}
1404
1405/// Deprecated in favor of `Io.Dir.readFile`.
1406pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
1407 var threaded: Io.Threaded = .init_single_threaded;
1408 const io = threaded.ioBasic();
1409 return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
1410}
1411
1412pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
1413 /// File size reached or exceeded the provided limit.
1414 StreamTooLong,
1415};
1416
1417/// Reads all the bytes from the named file. On success, caller owns returned
1418/// buffer.
1419///
1420/// If the file size is already known, a better alternative is to initialize a
1421/// `File.Reader`.
1422///
1423/// If the file size cannot be obtained, an error is returned. If
1424/// this is a realistic possibility, a better alternative is to initialize a
1425/// `File.Reader` which handles this seamlessly.
1426pub fn readFileAlloc(
1427 dir: Dir,
1428 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1429 /// On WASI, should be encoded as valid UTF-8.
1430 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1431 sub_path: []const u8,
1432 /// Used to allocate the result.
1433 gpa: Allocator,
1434 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1435 limit: Io.Limit,
1436) ReadFileAllocError![]u8 {
1437 return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
1438}
1439
1440/// Reads all the bytes from the named file. On success, caller owns returned
1441/// buffer.
1442///
1443/// If the file size is already known, a better alternative is to initialize a
1444/// `File.Reader`.
1445///
1446/// TODO move this function to Io.Dir
1447pub fn readFileAllocOptions(
1448 dir: Dir,
1449 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1450 /// On WASI, should be encoded as valid UTF-8.
1451 /// On other platforms, an opaque sequence of bytes with no particular encoding.
1452 sub_path: []const u8,
1453 /// Used to allocate the result.
1454 gpa: Allocator,
1455 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
1456 limit: Io.Limit,
1457 comptime alignment: std.mem.Alignment,
1458 comptime sentinel: ?u8,
1459) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1460 var threaded: Io.Threaded = .init_single_threaded;
1461 const io = threaded.ioBasic();
1462
1463 var file = try dir.openFile(sub_path, .{});
1464 defer file.close();
1465 var file_reader = file.reader(io, &.{});
1466 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
1467 error.ReadFailed => return file_reader.err.?,
1468 error.OutOfMemory, error.StreamTooLong => |e| return e,
1469 };
1470}
1471
1472pub const DeleteTreeError = error{
1473 AccessDenied,
1474 PermissionDenied,
1475 FileTooBig,
1476 SymLinkLoop,
1477 ProcessFdQuotaExceeded,
1478 NameTooLong,
1479 SystemFdQuotaExceeded,
1480 NoDevice,
1481 SystemResources,
1482 ReadOnlyFileSystem,
1483 FileSystem,
1484 FileBusy,
1485 DeviceBusy,
1486 ProcessNotFound,
1487 /// One of the path components was not a directory.
1488 /// This error is unreachable if `sub_path` does not contain a path separator.
1489 NotDir,
1490 /// WASI: file paths must be valid UTF-8.
1491 /// Windows: file paths provided by the user must be valid WTF-8.
1492 /// https://wtf-8.codeberg.page/
1493 /// On Windows, file paths cannot contain these characters:
1494 /// '/', '*', '?', '"', '<', '>', '|'
1495 BadPathName,
1496 /// On Windows, `\\server` or `\\server\share` was not found.
1497 NetworkNotFound,
1498
1499 Canceled,
1500} || posix.UnexpectedError;
1501
1502/// Whether `sub_path` describes a symlink, file, or directory, this function
1503/// removes it. If it cannot be removed because it is a non-empty directory,
1504/// this function recursively removes its entries and then tries again.
1505/// This operation is not atomic on most file systems.
1506/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1507/// On WASI, `sub_path` should be encoded as valid UTF-8.
1508/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1509pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1510 var initial_iterable_dir = (try self.deleteTreeOpenInitialSubpath(sub_path, .file)) orelse return;
1511
1512 const StackItem = struct {
1513 name: []const u8,
1514 parent_dir: Dir,
1515 iter: Dir.Iterator,
1516
1517 fn closeAll(items: []@This()) void {
1518 for (items) |*item| item.iter.dir.close();
1519 }
1520 };
1521
1522 var stack_buffer: [16]StackItem = undefined;
1523 var stack = std.ArrayList(StackItem).initBuffer(&stack_buffer);
1524 defer StackItem.closeAll(stack.items);
1525
1526 stack.appendAssumeCapacity(.{
1527 .name = sub_path,
1528 .parent_dir = self,
1529 .iter = initial_iterable_dir.iterateAssumeFirstIteration(),
1530 });
1531
1532 process_stack: while (stack.items.len != 0) {
1533 var top = &stack.items[stack.items.len - 1];
1534 while (try top.iter.next()) |entry| {
1535 var treat_as_dir = entry.kind == .directory;
1536 handle_entry: while (true) {
1537 if (treat_as_dir) {
1538 if (stack.unusedCapacitySlice().len >= 1) {
1539 var iterable_dir = top.iter.dir.openDir(entry.name, .{
1540 .follow_symlinks = false,
1541 .iterate = true,
1542 }) catch |err| switch (err) {
1543 error.NotDir => {
1544 treat_as_dir = false;
1545 continue :handle_entry;
1546 },
1547 error.FileNotFound => {
1548 // That's fine, we were trying to remove this directory anyway.
1549 break :handle_entry;
1550 },
1551
1552 error.AccessDenied,
1553 error.PermissionDenied,
1554 error.SymLinkLoop,
1555 error.ProcessFdQuotaExceeded,
1556 error.NameTooLong,
1557 error.SystemFdQuotaExceeded,
1558 error.NoDevice,
1559 error.SystemResources,
1560 error.Unexpected,
1561 error.BadPathName,
1562 error.NetworkNotFound,
1563 error.DeviceBusy,
1564 error.Canceled,
1565 => |e| return e,
1566 };
1567 stack.appendAssumeCapacity(.{
1568 .name = entry.name,
1569 .parent_dir = top.iter.dir,
1570 .iter = iterable_dir.iterateAssumeFirstIteration(),
1571 });
1572 continue :process_stack;
1573 } else {
1574 try top.iter.dir.deleteTreeMinStackSizeWithKindHint(entry.name, entry.kind);
1575 break :handle_entry;
1576 }
1577 } else {
1578 if (top.iter.dir.deleteFile(entry.name)) {
1579 break :handle_entry;
1580 } else |err| switch (err) {
1581 error.FileNotFound => break :handle_entry,
1582
1583 // Impossible because we do not pass any path separators.
1584 error.NotDir => unreachable,
1585
1586 error.IsDir => {
1587 treat_as_dir = true;
1588 continue :handle_entry;
1589 },
1590
1591 error.AccessDenied,
1592 error.PermissionDenied,
1593 error.SymLinkLoop,
1594 error.NameTooLong,
1595 error.SystemResources,
1596 error.ReadOnlyFileSystem,
1597 error.FileSystem,
1598 error.FileBusy,
1599 error.BadPathName,
1600 error.NetworkNotFound,
1601 error.Unexpected,
1602 => |e| return e,
1603 }
1604 }
1605 }
1606 }
1607
1608 // On Windows, we can't delete until the dir's handle has been closed, so
1609 // close it before we try to delete.
1610 top.iter.dir.close();
1611
1612 // In order to avoid double-closing the directory when cleaning up
1613 // the stack in the case of an error, we save the relevant portions and
1614 // pop the value from the stack.
1615 const parent_dir = top.parent_dir;
1616 const name = top.name;
1617 stack.items.len -= 1;
1618
1619 var need_to_retry: bool = false;
1620 parent_dir.deleteDir(name) catch |err| switch (err) {
1621 error.FileNotFound => {},
1622 error.DirNotEmpty => need_to_retry = true,
1623 else => |e| return e,
1624 };
1625
1626 if (need_to_retry) {
1627 // Since we closed the handle that the previous iterator used, we
1628 // need to re-open the dir and re-create the iterator.
1629 var iterable_dir = iterable_dir: {
1630 var treat_as_dir = true;
1631 handle_entry: while (true) {
1632 if (treat_as_dir) {
1633 break :iterable_dir parent_dir.openDir(name, .{
1634 .follow_symlinks = false,
1635 .iterate = true,
1636 }) catch |err| switch (err) {
1637 error.NotDir => {
1638 treat_as_dir = false;
1639 continue :handle_entry;
1640 },
1641 error.FileNotFound => {
1642 // That's fine, we were trying to remove this directory anyway.
1643 continue :process_stack;
1644 },
1645
1646 error.AccessDenied,
1647 error.PermissionDenied,
1648 error.SymLinkLoop,
1649 error.ProcessFdQuotaExceeded,
1650 error.NameTooLong,
1651 error.SystemFdQuotaExceeded,
1652 error.NoDevice,
1653 error.SystemResources,
1654 error.Unexpected,
1655 error.BadPathName,
1656 error.NetworkNotFound,
1657 error.DeviceBusy,
1658 error.Canceled,
1659 => |e| return e,
1660 };
1661 } else {
1662 if (parent_dir.deleteFile(name)) {
1663 continue :process_stack;
1664 } else |err| switch (err) {
1665 error.FileNotFound => continue :process_stack,
1666
1667 // Impossible because we do not pass any path separators.
1668 error.NotDir => unreachable,
1669
1670 error.IsDir => {
1671 treat_as_dir = true;
1672 continue :handle_entry;
1673 },
1674
1675 error.AccessDenied,
1676 error.PermissionDenied,
1677 error.SymLinkLoop,
1678 error.NameTooLong,
1679 error.SystemResources,
1680 error.ReadOnlyFileSystem,
1681 error.FileSystem,
1682 error.FileBusy,
1683 error.BadPathName,
1684 error.NetworkNotFound,
1685 error.Unexpected,
1686 => |e| return e,
1687 }
1688 }
1689 }
1690 };
1691 // We know there is room on the stack since we are just re-adding
1692 // the StackItem that we previously popped.
1693 stack.appendAssumeCapacity(.{
1694 .name = name,
1695 .parent_dir = parent_dir,
1696 .iter = iterable_dir.iterateAssumeFirstIteration(),
1697 });
1698 continue :process_stack;
1699 }
1700 }
1701}
1702
1703/// Like `deleteTree`, but only keeps one `Iterator` active at a time to minimize the function's stack size.
1704/// This is slower than `deleteTree` but uses less stack space.
1705/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1706/// On WASI, `sub_path` should be encoded as valid UTF-8.
1707/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1708pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1709 return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file);
1710}
1711
1712fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint: File.Kind) DeleteTreeError!void {
1713 start_over: while (true) {
1714 var dir = (try self.deleteTreeOpenInitialSubpath(sub_path, kind_hint)) orelse return;
1715 var cleanup_dir_parent: ?Dir = null;
1716 defer if (cleanup_dir_parent) |*d| d.close();
1717
1718 var cleanup_dir = true;
1719 defer if (cleanup_dir) dir.close();
1720
1721 // Valid use of max_path_bytes because dir_name_buf will only
1722 // ever store a single path component that was returned from the
1723 // filesystem.
1724 var dir_name_buf: [fs.max_path_bytes]u8 = undefined;
1725 var dir_name: []const u8 = sub_path;
1726
1727 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
1728 // Go through each entry and if it is not a directory, delete it. If it is a directory,
1729 // open it, and close the original directory. Repeat. Then start the entire operation over.
1730
1731 scan_dir: while (true) {
1732 var dir_it = dir.iterateAssumeFirstIteration();
1733 dir_it: while (try dir_it.next()) |entry| {
1734 var treat_as_dir = entry.kind == .directory;
1735 handle_entry: while (true) {
1736 if (treat_as_dir) {
1737 const new_dir = dir.openDir(entry.name, .{
1738 .follow_symlinks = false,
1739 .iterate = true,
1740 }) catch |err| switch (err) {
1741 error.NotDir => {
1742 treat_as_dir = false;
1743 continue :handle_entry;
1744 },
1745 error.FileNotFound => {
1746 // That's fine, we were trying to remove this directory anyway.
1747 continue :dir_it;
1748 },
1749
1750 error.AccessDenied,
1751 error.PermissionDenied,
1752 error.SymLinkLoop,
1753 error.ProcessFdQuotaExceeded,
1754 error.NameTooLong,
1755 error.SystemFdQuotaExceeded,
1756 error.NoDevice,
1757 error.SystemResources,
1758 error.Unexpected,
1759 error.BadPathName,
1760 error.NetworkNotFound,
1761 error.DeviceBusy,
1762 error.Canceled,
1763 => |e| return e,
1764 };
1765 if (cleanup_dir_parent) |*d| d.close();
1766 cleanup_dir_parent = dir;
1767 dir = new_dir;
1768 const result = dir_name_buf[0..entry.name.len];
1769 @memcpy(result, entry.name);
1770 dir_name = result;
1771 continue :scan_dir;
1772 } else {
1773 if (dir.deleteFile(entry.name)) {
1774 continue :dir_it;
1775 } else |err| switch (err) {
1776 error.FileNotFound => continue :dir_it,
1777
1778 // Impossible because we do not pass any path separators.
1779 error.NotDir => unreachable,
1780
1781 error.IsDir => {
1782 treat_as_dir = true;
1783 continue :handle_entry;
1784 },
1785
1786 error.AccessDenied,
1787 error.PermissionDenied,
1788 error.SymLinkLoop,
1789 error.NameTooLong,
1790 error.SystemResources,
1791 error.ReadOnlyFileSystem,
1792 error.FileSystem,
1793 error.FileBusy,
1794 error.BadPathName,
1795 error.NetworkNotFound,
1796 error.Unexpected,
1797 => |e| return e,
1798 }
1799 }
1800 }
1801 }
1802 // Reached the end of the directory entries, which means we successfully deleted all of them.
1803 // Now to remove the directory itself.
1804 dir.close();
1805 cleanup_dir = false;
1806
1807 if (cleanup_dir_parent) |d| {
1808 d.deleteDir(dir_name) catch |err| switch (err) {
1809 // These two things can happen due to file system race conditions.
1810 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1811 else => |e| return e,
1812 };
1813 continue :start_over;
1814 } else {
1815 self.deleteDir(sub_path) catch |err| switch (err) {
1816 error.FileNotFound => return,
1817 error.DirNotEmpty => continue :start_over,
1818 else => |e| return e,
1819 };
1820 return;
1821 }
1822 }
1823 }
1824}
1825
1826/// On successful delete, returns null.
1827fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File.Kind) !?Dir {
1828 return iterable_dir: {
1829 // Treat as a file by default
1830 var treat_as_dir = kind_hint == .directory;
1831
1832 handle_entry: while (true) {
1833 if (treat_as_dir) {
1834 break :iterable_dir self.openDir(sub_path, .{
1835 .follow_symlinks = false,
1836 .iterate = true,
1837 }) catch |err| switch (err) {
1838 error.NotDir => {
1839 treat_as_dir = false;
1840 continue :handle_entry;
1841 },
1842 error.FileNotFound => {
1843 // That's fine, we were trying to remove this directory anyway.
1844 return null;
1845 },
1846
1847 error.AccessDenied,
1848 error.PermissionDenied,
1849 error.SymLinkLoop,
1850 error.ProcessFdQuotaExceeded,
1851 error.NameTooLong,
1852 error.SystemFdQuotaExceeded,
1853 error.NoDevice,
1854 error.SystemResources,
1855 error.Unexpected,
1856 error.BadPathName,
1857 error.DeviceBusy,
1858 error.NetworkNotFound,
1859 error.Canceled,
1860 => |e| return e,
1861 };
1862 } else {
1863 if (self.deleteFile(sub_path)) {
1864 return null;
1865 } else |err| switch (err) {
1866 error.FileNotFound => return null,
1867
1868 error.IsDir => {
1869 treat_as_dir = true;
1870 continue :handle_entry;
1871 },
1872
1873 error.AccessDenied,
1874 error.PermissionDenied,
1875 error.SymLinkLoop,
1876 error.NameTooLong,
1877 error.SystemResources,
1878 error.ReadOnlyFileSystem,
1879 error.NotDir,
1880 error.FileSystem,
1881 error.FileBusy,
1882 error.BadPathName,
1883 error.NetworkNotFound,
1884 error.Unexpected,
1885 => |e| return e,
1886 }
1887 }
1888 }
1889 };
1890}
1891
1892pub const WriteFileError = File.WriteError || File.OpenError;
1893
1894pub const WriteFileOptions = struct {
1895 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1896 /// On WASI, `sub_path` should be encoded as valid UTF-8.
1897 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1898 sub_path: []const u8,
1899 data: []const u8,
1900 flags: File.CreateFlags = .{},
1901};
1902
1903/// Writes content to the file system, using the file creation flags provided.
1904pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
1905 var file = try self.createFile(options.sub_path, options.flags);
1906 defer file.close();
1907 try file.writeAll(options.data);
1908}
1909
1910/// Deprecated in favor of `Io.Dir.AccessError`.
1911pub const AccessError = Io.Dir.AccessError;
1912
1913/// Deprecated in favor of `Io.Dir.access`.
1914pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
1915 var threaded: Io.Threaded = .init_single_threaded;
1916 const io = threaded.ioBasic();
1917 return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
1918}
1919
1920pub const CopyFileOptions = struct {
1921 /// When this is `null` the mode is copied from the source file.
1922 override_mode: ?File.Mode = null,
1923};
1924
1925pub const CopyFileError = File.OpenError || File.StatError ||
1926 AtomicFile.InitError || AtomicFile.FinishError ||
1927 File.ReadError || File.WriteError || error{InvalidFileName};
1928
1929/// Atomically creates a new file at `dest_path` within `dest_dir` with the
1930/// same contents as `source_path` within `source_dir`, overwriting any already
1931/// existing file.
1932///
1933/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and
1934/// readily available, there is a possibility of power loss or application
1935/// termination leaving temporary files present in the same directory as
1936/// dest_path.
1937///
1938/// On Windows, both paths should be encoded as
1939/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
1940/// encoded as valid UTF-8. On other platforms, both paths are an opaque
1941/// sequence of bytes with no particular encoding.
1942///
1943/// TODO move this function to Io.Dir
1944pub fn copyFile(
1945 source_dir: Dir,
1946 source_path: []const u8,
1947 dest_dir: Dir,
1948 dest_path: []const u8,
1949 options: CopyFileOptions,
1950) CopyFileError!void {
1951 var threaded: Io.Threaded = .init_single_threaded;
1952 const io = threaded.ioBasic();
1953
1954 const file = try source_dir.openFile(source_path, .{});
1955 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1956 defer file_reader.file.close(io);
1957
1958 const mode = options.override_mode orelse blk: {
1959 const st = try file_reader.file.stat(io);
1960 file_reader.size = st.size;
1961 break :blk st.mode;
1962 };
1963
1964 var buffer: [1024]u8 = undefined; // Used only when direct fd-to-fd is not available.
1965 var atomic_file = try dest_dir.atomicFile(dest_path, .{
1966 .mode = mode,
1967 .write_buffer = &buffer,
1968 });
1969 defer atomic_file.deinit();
1970
1971 _ = atomic_file.file_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
1972 error.ReadFailed => return file_reader.err.?,
1973 error.WriteFailed => return atomic_file.file_writer.err.?,
1974 };
1975
1976 try atomic_file.finish();
1977}
1978
1979pub const AtomicFileOptions = struct {
1980 mode: File.Mode = File.default_mode,
1981 make_path: bool = false,
1982 write_buffer: []u8,
1983};
1984
1985/// Directly access the `.file` field, and then call `AtomicFile.finish` to
1986/// atomically replace `dest_path` with contents.
1987/// Always call `AtomicFile.deinit` to clean up, regardless of whether
1988/// `AtomicFile.finish` succeeded. `dest_path` must remain valid until
1989/// `AtomicFile.deinit` is called.
1990/// On Windows, `dest_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1991/// On WASI, `dest_path` should be encoded as valid UTF-8.
1992/// On other platforms, `dest_path` is an opaque sequence of bytes with no particular encoding.
1993pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
1994 if (fs.path.dirname(dest_path)) |dirname| {
1995 const dir = if (options.make_path)
1996 try self.makeOpenPath(dirname, .{})
1997 else
1998 try self.openDir(dirname, .{});
1999
2000 return .init(fs.path.basename(dest_path), options.mode, dir, true, options.write_buffer);
2001 } else {
2002 return .init(dest_path, options.mode, self, false, options.write_buffer);
2003 }
2004}
2005
2006pub const Stat = File.Stat;
2007pub const StatError = File.StatError;
2008
2009/// Deprecated in favor of `Io.Dir.stat`.
2010pub fn stat(self: Dir) StatError!Stat {
2011 var threaded: Io.Threaded = .init_single_threaded;
2012 const io = threaded.ioBasic();
2013 return Io.Dir.stat(.{ .handle = self.fd }, io);
2014}
2015
2016pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
2017
2018/// Deprecated in favor of `Io.Dir.statPath`.
2019pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2020 var threaded: Io.Threaded = .init_single_threaded;
2021 const io = threaded.ioBasic();
2022 return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
2023}
2024
2025pub const ChmodError = File.ChmodError;
2026
2027/// Changes the mode of the directory.
2028/// The process must have the correct privileges in order to do this
2029/// successfully, or must have the effective user ID matching the owner
2030/// of the directory. Additionally, the directory must have been opened
2031/// with `OpenOptions{ .iterate = true }`.
2032pub fn chmod(self: Dir, new_mode: File.Mode) ChmodError!void {
2033 const file: File = .{ .handle = self.fd };
2034 try file.chmod(new_mode);
2035}
2036
2037/// Changes the owner and group of the directory.
2038/// The process must have the correct privileges in order to do this
2039/// successfully. The group may be changed by the owner of the directory to
2040/// any group of which the owner is a member. Additionally, the directory
2041/// must have been opened with `OpenOptions{ .iterate = true }`. If the
2042/// owner or group is specified as `null`, the ID is not changed.
2043pub fn chown(self: Dir, owner: ?File.Uid, group: ?File.Gid) ChownError!void {
2044 const file: File = .{ .handle = self.fd };
2045 try file.chown(owner, group);
2046}
2047
2048pub const ChownError = File.ChownError;
2049
2050const Permissions = File.Permissions;
2051pub const SetPermissionsError = File.SetPermissionsError;
2052
2053/// Sets permissions according to the provided `Permissions` struct.
2054/// This method is *NOT* available on WASI
2055pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void {
2056 const file: File = .{ .handle = self.fd };
2057 try file.setPermissions(permissions);
2058}
2059
2060pub fn adaptToNewApi(dir: Dir) Io.Dir {
2061 return .{ .handle = dir.fd };
2062}
2063
2064pub fn adaptFromNewApi(dir: Io.Dir) Dir {
2065 return .{ .fd = dir.handle };
2066}
lib/std/fs/File.zig+9-90
......@@ -22,18 +22,10 @@ handle: Handle,
2222pub const Handle = Io.File.Handle;
2323pub const Mode = Io.File.Mode;
2424pub const INode = Io.File.INode;
25pub const Uid = posix.uid_t;
26pub const Gid = posix.gid_t;
25pub const Uid = Io.File.Uid;
26pub const Gid = Io.File.Gid;
2727pub const Kind = Io.File.Kind;
2828
29/// This is the default mode given to POSIX operating systems for creating
30/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
31/// since most people would expect "-rw-r--r--", for example, when using
32/// the `touch` command, which would correspond to `0o644`. However, POSIX
33/// libc implementations use `0o666` inside `fopen` and then rely on the
34/// process-scoped "umask" setting to adjust this number for file creation.
35pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
36
3729/// Deprecated in favor of `Io.File.OpenError`.
3830pub const OpenError = Io.File.OpenError || error{WouldBlock};
3931/// Deprecated in favor of `Io.File.OpenMode`.
......@@ -43,53 +35,7 @@ pub const Lock = Io.File.Lock;
4335/// Deprecated in favor of `Io.File.OpenFlags`.
4436pub const OpenFlags = Io.File.OpenFlags;
4537
46pub const CreateFlags = struct {
47 /// Whether the file will be created with read access.
48 read: bool = false,
49
50 /// If the file already exists, and is a regular file, and the access
51 /// mode allows writing, it will be truncated to length 0.
52 truncate: bool = true,
53
54 /// Ensures that this open call creates the file, otherwise causes
55 /// `error.PathAlreadyExists` to be returned.
56 exclusive: bool = false,
57
58 /// Open the file with an advisory lock to coordinate with other processes
59 /// accessing it at the same time. An exclusive lock will prevent other
60 /// processes from acquiring a lock. A shared lock will prevent other
61 /// processes from acquiring a exclusive lock, but does not prevent
62 /// other process from getting their own shared locks.
63 ///
64 /// The lock is advisory, except on Linux in very specific circumstances[1].
65 /// This means that a process that does not respect the locking API can still get access
66 /// to the file, despite the lock.
67 ///
68 /// On these operating systems, the lock is acquired atomically with
69 /// opening the file:
70 /// * Darwin
71 /// * DragonFlyBSD
72 /// * FreeBSD
73 /// * Haiku
74 /// * NetBSD
75 /// * OpenBSD
76 /// On these operating systems, the lock is acquired via a separate syscall
77 /// after opening the file:
78 /// * Linux
79 /// * Windows
80 ///
81 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
82 lock: Lock = .none,
83
84 /// Sets whether or not to wait until the file is locked to return. If set to true,
85 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
86 /// is available to proceed.
87 lock_nonblocking: bool = false,
88
89 /// For POSIX systems this is the file system mode the file will
90 /// be created with. On other systems this is always 0.
91 mode: Mode = default_mode,
92};
38pub const CreateFlags = std.Io.File.CreateFlags;
9339
9440pub fn stdout() File {
9541 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 {
259205 try posix.ftruncate(self.handle, length);
260206}
261207
262pub const SeekError = posix.SeekError;
263
264/// Repositions read/write file offset relative to the current offset.
265/// TODO: integrate with async I/O
266pub fn seekBy(self: File, offset: i64) SeekError!void {
267 return posix.lseek_CUR(self.handle, offset);
268}
269
270/// Repositions read/write file offset relative to the end.
271/// TODO: integrate with async I/O
272pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
273 return posix.lseek_END(self.handle, offset);
274}
275
276/// Repositions read/write file offset relative to the beginning.
277/// TODO: integrate with async I/O
278pub fn seekTo(self: File, offset: u64) SeekError!void {
279 return posix.lseek_SET(self.handle, offset);
280}
281
282pub const GetSeekPosError = posix.SeekError || StatError;
283
284/// TODO: integrate with async I/O
285pub fn getPos(self: File) GetSeekPosError!u64 {
286 return posix.lseek_CUR_get(self.handle);
287}
288
289208pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError;
290209
291210/// TODO: integrate with async I/O
......@@ -306,11 +225,13 @@ pub fn mode(self: File) ModeError!Mode {
306225 return (try self.stat()).mode;
307226}
308227
228/// Deprecated in favor of `Io.File.Stat`.
309229pub const Stat = Io.File.Stat;
310230
231/// Deprecated in favor of `Io.File.StatError`.
311232pub const StatError = posix.FStatError;
312233
313/// Returns `Stat` containing basic information about the `File`.
234/// Deprecated in favor of `Io.File.stat`.
314235pub fn stat(self: File) StatError!Stat {
315236 var threaded: Io.Threaded = .init_single_threaded;
316237 const io = threaded.ioBasic();
......@@ -710,7 +631,7 @@ pub const Writer = struct {
710631 Unexpected,
711632 };
712633
713 pub const SeekError = File.SeekError;
634 pub const SeekError = Io.File.SeekError;
714635
715636 /// Number of slices to store on the stack, when trying to send as many byte
716637 /// vectors through the underlying write calls as possible.
......@@ -1268,10 +1189,8 @@ pub fn writerStreaming(file: File, buffer: []u8) Writer {
12681189const range_off: windows.LARGE_INTEGER = 0;
12691190const range_len: windows.LARGE_INTEGER = 1;
12701191
1271pub const LockError = error{
1272 SystemResources,
1273 FileLocksNotSupported,
1274} || posix.UnexpectedError;
1192/// Deprecated
1193pub const LockError = Io.File.LockError;
12751194
12761195/// Blocks when an incompatible lock is held by another process.
12771196/// A process may hold only one type of lock (shared or exclusive) on
lib/std/fs/test.zig-15
......@@ -2065,21 +2065,6 @@ test "chown" {
20652065 try dir.chown(null, null);
20662066}
20672067
2068test "delete a setAsCwd directory on Windows" {
2069 if (native_os != .windows) return error.SkipZigTest;
2070
2071 var tmp = tmpDir(.{});
2072 // Set tmp dir as current working directory.
2073 try tmp.dir.setAsCwd();
2074 tmp.dir.close();
2075 try testing.expectError(error.FileBusy, tmp.parent_dir.deleteTree(&tmp.sub_path));
2076 // Now set the parent dir as the current working dir for clean up.
2077 try tmp.parent_dir.setAsCwd();
2078 try tmp.parent_dir.deleteTree(&tmp.sub_path);
2079 // Close the parent "tmp" so we don't leak the HANDLE.
2080 tmp.parent_dir.close();
2081}
2082
20832068test "invalid UTF-8/WTF-8 paths" {
20842069 const expected_err = switch (native_os) {
20852070 .wasi => error.BadPathName,
lib/std/os.zig-130
......@@ -21,7 +21,6 @@ const mem = std.mem;
2121const elf = std.elf;
2222const fs = std.fs;
2323const dl = @import("dynamic_library.zig");
24const max_path_bytes = std.fs.max_path_bytes;
2524const posix = std.posix;
2625const native_os = builtin.os.tag;
2726
......@@ -56,135 +55,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o
5655 else => undefined,
5756};
5857
59pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
60 return switch (os.tag) {
61 .windows,
62 .driverkit,
63 .ios,
64 .maccatalyst,
65 .macos,
66 .tvos,
67 .visionos,
68 .watchos,
69 .linux,
70 .illumos,
71 .freebsd,
72 .serenity,
73 => true,
74
75 .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
76 .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
77 else => false,
78 };
79}
80
81/// Return canonical path of handle `fd`.
82///
83/// This function is very host-specific and is not universally supported by all hosts.
84/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
85/// unsupported on WASI.
86///
87/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
88/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
89///
90/// Calling this function is usually a bug.
91pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
92 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
93 @compileError("querying for canonical path of a handle is unsupported on this host");
94 }
95 switch (native_os) {
96 .windows => {
97 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
98 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
99
100 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
101 return out_buffer[0..end_index];
102 },
103 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
104 // On macOS, we can use F.GETPATH fcntl command to query the OS for
105 // the path to the file descriptor.
106 @memset(out_buffer[0..max_path_bytes], 0);
107 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
108 .SUCCESS => {},
109 .BADF => return error.FileNotFound,
110 .NOSPC => return error.NameTooLong,
111 .NOENT => return error.FileNotFound,
112 // TODO man pages for fcntl on macOS don't really tell you what
113 // errno values to expect when command is F.GETPATH...
114 else => |err| return posix.unexpectedErrno(err),
115 }
116 const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
117 return out_buffer[0..len];
118 },
119 .linux, .serenity => {
120 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
121 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}, 0) catch unreachable;
122
123 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| {
124 switch (err) {
125 error.NotLink => unreachable,
126 error.BadPathName => unreachable,
127 error.UnsupportedReparsePointType => unreachable, // Windows-only
128 error.NetworkNotFound => unreachable, // Windows-only
129 else => |e| return e,
130 }
131 };
132 return target;
133 },
134 .illumos => {
135 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
136 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/path/{d}", .{fd}, 0) catch unreachable;
137
138 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
139 error.UnsupportedReparsePointType => unreachable,
140 error.NotLink => unreachable,
141 else => |e| return e,
142 };
143 return target;
144 },
145 .freebsd => {
146 var kfile: std.c.kinfo_file = undefined;
147 kfile.structsize = std.c.KINFO_FILE_SIZE;
148 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
149 .SUCCESS => {},
150 .BADF => return error.FileNotFound,
151 else => |err| return posix.unexpectedErrno(err),
152 }
153 const len = mem.findScalar(u8, &kfile.path, 0) orelse max_path_bytes;
154 if (len == 0) return error.NameTooLong;
155 const result = out_buffer[0..len];
156 @memcpy(result, kfile.path[0..len]);
157 return result;
158 },
159 .dragonfly => {
160 @memset(out_buffer[0..max_path_bytes], 0);
161 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
162 .SUCCESS => {},
163 .BADF => return error.FileNotFound,
164 .RANGE => return error.NameTooLong,
165 else => |err| return posix.unexpectedErrno(err),
166 }
167 const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
168 return out_buffer[0..len];
169 },
170 .netbsd => {
171 @memset(out_buffer[0..max_path_bytes], 0);
172 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
173 .SUCCESS => {},
174 .ACCES => return error.AccessDenied,
175 .BADF => return error.FileNotFound,
176 .NOENT => return error.FileNotFound,
177 .NOMEM => return error.SystemResources,
178 .RANGE => return error.NameTooLong,
179 else => |err| return posix.unexpectedErrno(err),
180 }
181 const len = mem.findScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
182 return out_buffer[0..len];
183 },
184 else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
185 }
186}
187
18858pub const FstatError = error{
18959 SystemResources,
19060 AccessDenied,
lib/std/posix.zig+23-1037
......@@ -319,35 +319,6 @@ pub const FChmodError = error{
319319 ReadOnlyFileSystem,
320320} || UnexpectedError;
321321
322/// Changes the mode of the file referred to by the file descriptor.
323///
324/// The process must have the correct privileges in order to do this
325/// successfully, or must have the effective user ID matching the owner
326/// of the file.
327pub fn fchmod(fd: fd_t, mode: mode_t) FChmodError!void {
328 if (!fs.has_executable_bit) @compileError("fchmod unsupported by target OS");
329
330 while (true) {
331 const res = system.fchmod(fd, mode);
332 switch (errno(res)) {
333 .SUCCESS => return,
334 .INTR => continue,
335 .BADF => unreachable,
336 .FAULT => unreachable,
337 .INVAL => unreachable,
338 .ACCES => return error.AccessDenied,
339 .IO => return error.InputOutput,
340 .LOOP => return error.SymLinkLoop,
341 .NOENT => return error.FileNotFound,
342 .NOMEM => return error.SystemResources,
343 .NOTDIR => return error.FileNotFound,
344 .PERM => return error.PermissionDenied,
345 .ROFS => return error.ReadOnlyFileSystem,
346 else => |err| return unexpectedErrno(err),
347 }
348 }
349}
350
351322pub const FChmodAtError = FChmodError || error{
352323 /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded
353324 /// `PATH_MAX`.
......@@ -533,50 +504,6 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
533504 }
534505}
535506
536pub const FChownError = error{
537 AccessDenied,
538 PermissionDenied,
539 InputOutput,
540 SymLinkLoop,
541 FileNotFound,
542 SystemResources,
543 ReadOnlyFileSystem,
544} || UnexpectedError;
545
546/// Changes the owner and group of the file referred to by the file descriptor.
547/// The process must have the correct privileges in order to do this
548/// successfully. The group may be changed by the owner of the directory to
549/// any group of which the owner is a member. If the owner or group is
550/// specified as `null`, the ID is not changed.
551pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void {
552 switch (native_os) {
553 .windows, .wasi => @compileError("Unsupported OS"),
554 else => {},
555 }
556
557 while (true) {
558 const res = system.fchown(fd, owner orelse ~@as(uid_t, 0), group orelse ~@as(gid_t, 0));
559
560 switch (errno(res)) {
561 .SUCCESS => return,
562 .INTR => continue,
563 .BADF => unreachable, // Can be reached if the fd refers to a directory opened without `Dir.OpenOptions{ .iterate = true }`
564
565 .FAULT => unreachable,
566 .INVAL => unreachable,
567 .ACCES => return error.AccessDenied,
568 .IO => return error.InputOutput,
569 .LOOP => return error.SymLinkLoop,
570 .NOENT => return error.FileNotFound,
571 .NOMEM => return error.SystemResources,
572 .NOTDIR => return error.FileNotFound,
573 .PERM => return error.PermissionDenied,
574 .ROFS => return error.ReadOnlyFileSystem,
575 else => |err| return unexpectedErrno(err),
576 }
577 }
578}
579
580507pub const RebootError = error{
581508 PermissionDenied,
582509} || UnexpectedError;
......@@ -1926,150 +1853,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
19261853 }
19271854}
19281855
1929pub const SymLinkError = error{
1930 /// In WASI, this error may occur when the file descriptor does
1931 /// not hold the required rights to create a new symbolic link relative to it.
1932 AccessDenied,
1933 PermissionDenied,
1934 DiskQuota,
1935 PathAlreadyExists,
1936 FileSystem,
1937 SymLinkLoop,
1938 FileNotFound,
1939 SystemResources,
1940 NoSpaceLeft,
1941 ReadOnlyFileSystem,
1942 NotDir,
1943 NameTooLong,
1944 /// WASI: file paths must be valid UTF-8.
1945 /// Windows: file paths provided by the user must be valid WTF-8.
1946 /// https://wtf-8.codeberg.page/
1947 BadPathName,
1948} || UnexpectedError;
1949
1950/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
1951/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1952/// one; the latter case is known as a dangling link.
1953/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1954/// On WASI, both paths should be encoded as valid UTF-8.
1955/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1956/// If `sym_link_path` exists, it will not be overwritten.
1957/// See also `symlinkZ.
1958pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
1959 if (native_os == .windows) {
1960 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
1961 } else if (native_os == .wasi and !builtin.link_libc) {
1962 return symlinkat(target_path, AT.FDCWD, sym_link_path);
1963 }
1964 const target_path_c = try toPosixPath(target_path);
1965 const sym_link_path_c = try toPosixPath(sym_link_path);
1966 return symlinkZ(&target_path_c, &sym_link_path_c);
1967}
1968
1969/// This is the same as `symlink` except the parameters are null-terminated pointers.
1970/// See also `symlink`.
1971pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
1972 if (native_os == .windows) {
1973 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
1974 } else if (native_os == .wasi and !builtin.link_libc) {
1975 return symlinkatZ(target_path, fs.cwd().fd, sym_link_path);
1976 }
1977 switch (errno(system.symlink(target_path, sym_link_path))) {
1978 .SUCCESS => return,
1979 .FAULT => unreachable,
1980 .INVAL => unreachable,
1981 .ACCES => return error.AccessDenied,
1982 .PERM => return error.PermissionDenied,
1983 .DQUOT => return error.DiskQuota,
1984 .EXIST => return error.PathAlreadyExists,
1985 .IO => return error.FileSystem,
1986 .LOOP => return error.SymLinkLoop,
1987 .NAMETOOLONG => return error.NameTooLong,
1988 .NOENT => return error.FileNotFound,
1989 .NOTDIR => return error.NotDir,
1990 .NOMEM => return error.SystemResources,
1991 .NOSPC => return error.NoSpaceLeft,
1992 .ROFS => return error.ReadOnlyFileSystem,
1993 .ILSEQ => return error.BadPathName,
1994 else => |err| return unexpectedErrno(err),
1995 }
1996}
1997
1998/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string
1999/// `target_path` **relative** to `newdirfd` directory handle.
2000/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
2001/// one; the latter case is known as a dangling link.
2002/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2003/// On WASI, both paths should be encoded as valid UTF-8.
2004/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2005/// If `sym_link_path` exists, it will not be overwritten.
2006/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`.
2007pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
2008 if (native_os == .windows) {
2009 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2010 } else if (native_os == .wasi and !builtin.link_libc) {
2011 return symlinkatWasi(target_path, newdirfd, sym_link_path);
2012 }
2013 const target_path_c = try toPosixPath(target_path);
2014 const sym_link_path_c = try toPosixPath(sym_link_path);
2015 return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c);
2016}
2017
2018/// WASI-only. The same as `symlinkat` but targeting WASI.
2019/// See also `symlinkat`.
2020pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
2021 switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) {
2022 .SUCCESS => {},
2023 .FAULT => unreachable,
2024 .INVAL => unreachable,
2025 .BADF => unreachable,
2026 .ACCES => return error.AccessDenied,
2027 .PERM => return error.PermissionDenied,
2028 .DQUOT => return error.DiskQuota,
2029 .EXIST => return error.PathAlreadyExists,
2030 .IO => return error.FileSystem,
2031 .LOOP => return error.SymLinkLoop,
2032 .NAMETOOLONG => return error.NameTooLong,
2033 .NOENT => return error.FileNotFound,
2034 .NOTDIR => return error.NotDir,
2035 .NOMEM => return error.SystemResources,
2036 .NOSPC => return error.NoSpaceLeft,
2037 .ROFS => return error.ReadOnlyFileSystem,
2038 .NOTCAPABLE => return error.AccessDenied,
2039 .ILSEQ => return error.BadPathName,
2040 else => |err| return unexpectedErrno(err),
2041 }
2042}
2043
2044/// The same as `symlinkat` except the parameters are null-terminated pointers.
2045/// See also `symlinkat`.
2046pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
2047 if (native_os == .windows) {
2048 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
2049 } else if (native_os == .wasi and !builtin.link_libc) {
2050 return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0));
2051 }
2052 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
2053 .SUCCESS => return,
2054 .FAULT => unreachable,
2055 .INVAL => unreachable,
2056 .ACCES => return error.AccessDenied,
2057 .PERM => return error.PermissionDenied,
2058 .DQUOT => return error.DiskQuota,
2059 .EXIST => return error.PathAlreadyExists,
2060 .IO => return error.FileSystem,
2061 .LOOP => return error.SymLinkLoop,
2062 .NAMETOOLONG => return error.NameTooLong,
2063 .NOENT => return error.FileNotFound,
2064 .NOTDIR => return error.NotDir,
2065 .NOMEM => return error.SystemResources,
2066 .NOSPC => return error.NoSpaceLeft,
2067 .ROFS => return error.ReadOnlyFileSystem,
2068 .ILSEQ => return error.BadPathName,
2069 else => |err| return unexpectedErrno(err),
2070 }
2071}
2072
20731856pub const LinkError = UnexpectedError || error{
20741857 AccessDenied,
20751858 PermissionDenied,
......@@ -2200,412 +1983,32 @@ pub fn linkat(
22001983 .MLINK => return error.LinkQuotaExceeded,
22011984 .NAMETOOLONG => return error.NameTooLong,
22021985 .NOENT => return error.FileNotFound,
2203 .NOMEM => return error.SystemResources,
2204 .NOSPC => return error.NoSpaceLeft,
2205 .NOTDIR => return error.NotDir,
2206 .PERM => return error.PermissionDenied,
2207 .ROFS => return error.ReadOnlyFileSystem,
2208 .XDEV => return error.NotSameFileSystem,
2209 .INVAL => unreachable,
2210 .ILSEQ => return error.BadPathName,
2211 else => |err| return unexpectedErrno(err),
2212 }
2213 }
2214 const old = try toPosixPath(oldpath);
2215 const new = try toPosixPath(newpath);
2216 return try linkatZ(olddir, &old, newdir, &new, flags);
2217}
2218
2219pub const UnlinkError = error{
2220 FileNotFound,
2221
2222 /// In WASI, this error may occur when the file descriptor does
2223 /// not hold the required rights to unlink a resource by path relative to it.
2224 AccessDenied,
2225 PermissionDenied,
2226 FileBusy,
2227 FileSystem,
2228 IsDir,
2229 SymLinkLoop,
2230 NameTooLong,
2231 NotDir,
2232 SystemResources,
2233 ReadOnlyFileSystem,
2234
2235 /// WASI: file paths must be valid UTF-8.
2236 /// Windows: file paths provided by the user must be valid WTF-8.
2237 /// https://wtf-8.codeberg.page/
2238 /// Windows: file paths cannot contain these characters:
2239 /// '/', '*', '?', '"', '<', '>', '|'
2240 BadPathName,
2241
2242 /// On Windows, `\\server` or `\\server\share` was not found.
2243 NetworkNotFound,
2244} || UnexpectedError;
2245
2246/// Delete a name and possibly the file it refers to.
2247/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2248/// On WASI, `file_path` should be encoded as valid UTF-8.
2249/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2250/// See also `unlinkZ`.
2251pub fn unlink(file_path: []const u8) UnlinkError!void {
2252 if (native_os == .wasi and !builtin.link_libc) {
2253 return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) {
2254 error.DirNotEmpty => unreachable, // only occurs when targeting directories
2255 else => |e| return e,
2256 };
2257 } else if (native_os == .windows) {
2258 const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
2259 return unlinkW(file_path_w.span());
2260 } else {
2261 const file_path_c = try toPosixPath(file_path);
2262 return unlinkZ(&file_path_c);
2263 }
2264}
2265
2266/// Same as `unlink` except the parameter is null terminated.
2267pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2268 if (native_os == .windows) {
2269 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
2270 return unlinkW(file_path_w.span());
2271 } else if (native_os == .wasi and !builtin.link_libc) {
2272 return unlink(mem.sliceTo(file_path, 0));
2273 }
2274 switch (errno(system.unlink(file_path))) {
2275 .SUCCESS => return,
2276 .ACCES => return error.AccessDenied,
2277 .PERM => return error.PermissionDenied,
2278 .BUSY => return error.FileBusy,
2279 .FAULT => unreachable,
2280 .INVAL => unreachable,
2281 .IO => return error.FileSystem,
2282 .ISDIR => return error.IsDir,
2283 .LOOP => return error.SymLinkLoop,
2284 .NAMETOOLONG => return error.NameTooLong,
2285 .NOENT => return error.FileNotFound,
2286 .NOTDIR => return error.NotDir,
2287 .NOMEM => return error.SystemResources,
2288 .ROFS => return error.ReadOnlyFileSystem,
2289 .ILSEQ => return error.BadPathName,
2290 else => |err| return unexpectedErrno(err),
2291 }
2292}
2293
2294/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 LE encoded.
2295pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
2296 windows.DeleteFile(file_path_w, .{ .dir = fs.cwd().fd }) catch |err| switch (err) {
2297 error.DirNotEmpty => unreachable, // we're not passing .remove_dir = true
2298 else => |e| return e,
2299 };
2300}
2301
2302pub const UnlinkatError = UnlinkError || error{
2303 /// When passing `AT.REMOVEDIR`, this error occurs when the named directory is not empty.
2304 DirNotEmpty,
2305};
2306
2307/// Delete a file name and possibly the file it refers to, based on an open directory handle.
2308/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2309/// On WASI, `file_path` should be encoded as valid UTF-8.
2310/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2311/// Asserts that the path parameter has no null bytes.
2312pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
2313 if (native_os == .windows) {
2314 const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
2315 return unlinkatW(dirfd, file_path_w.span(), flags);
2316 } else if (native_os == .wasi and !builtin.link_libc) {
2317 return unlinkatWasi(dirfd, file_path, flags);
2318 } else {
2319 const file_path_c = try toPosixPath(file_path);
2320 return unlinkatZ(dirfd, &file_path_c, flags);
2321 }
2322}
2323
2324/// WASI-only. Same as `unlinkat` but targeting WASI.
2325/// See also `unlinkat`.
2326pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
2327 const remove_dir = (flags & AT.REMOVEDIR) != 0;
2328 const res = if (remove_dir)
2329 wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len)
2330 else
2331 wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len);
2332 switch (res) {
2333 .SUCCESS => return,
2334 .ACCES => return error.AccessDenied,
2335 .PERM => return error.PermissionDenied,
2336 .BUSY => return error.FileBusy,
2337 .FAULT => unreachable,
2338 .IO => return error.FileSystem,
2339 .ISDIR => return error.IsDir,
2340 .LOOP => return error.SymLinkLoop,
2341 .NAMETOOLONG => return error.NameTooLong,
2342 .NOENT => return error.FileNotFound,
2343 .NOTDIR => return error.NotDir,
2344 .NOMEM => return error.SystemResources,
2345 .ROFS => return error.ReadOnlyFileSystem,
2346 .NOTEMPTY => return error.DirNotEmpty,
2347 .NOTCAPABLE => return error.AccessDenied,
2348 .ILSEQ => return error.BadPathName,
2349
2350 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2351 .BADF => unreachable, // always a race condition
2352
2353 else => |err| return unexpectedErrno(err),
2354 }
2355}
2356
2357/// Same as `unlinkat` but `file_path` is a null-terminated string.
2358pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
2359 if (native_os == .windows) {
2360 const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c);
2361 return unlinkatW(dirfd, file_path_w.span(), flags);
2362 } else if (native_os == .wasi and !builtin.link_libc) {
2363 return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags);
2364 }
2365 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
2366 .SUCCESS => return,
2367 .ACCES => return error.AccessDenied,
2368 .PERM => return error.PermissionDenied,
2369 .BUSY => return error.FileBusy,
2370 .FAULT => unreachable,
2371 .IO => return error.FileSystem,
2372 .ISDIR => return error.IsDir,
2373 .LOOP => return error.SymLinkLoop,
2374 .NAMETOOLONG => return error.NameTooLong,
2375 .NOENT => return error.FileNotFound,
2376 .NOTDIR => return error.NotDir,
2377 .NOMEM => return error.SystemResources,
2378 .ROFS => return error.ReadOnlyFileSystem,
2379 .EXIST => return error.DirNotEmpty,
2380 .NOTEMPTY => return error.DirNotEmpty,
2381 .ILSEQ => return error.BadPathName,
2382
2383 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2384 .BADF => unreachable, // always a race condition
2385
2386 else => |err| return unexpectedErrno(err),
2387 }
2388}
2389
2390/// Same as `unlinkat` but `sub_path_w` is WTF16LE, NT prefixed. Windows only.
2391pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
2392 const remove_dir = (flags & AT.REMOVEDIR) != 0;
2393 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
2394}
2395
2396pub const RenameError = error{
2397 /// In WASI, this error may occur when the file descriptor does
2398 /// not hold the required rights to rename a resource by path relative to it.
2399 ///
2400 /// On Windows, this error may be returned instead of PathAlreadyExists when
2401 /// renaming a directory over an existing directory.
2402 AccessDenied,
2403 PermissionDenied,
2404 FileBusy,
2405 DiskQuota,
2406 IsDir,
2407 SymLinkLoop,
2408 LinkQuotaExceeded,
2409 NameTooLong,
2410 FileNotFound,
2411 NotDir,
2412 SystemResources,
2413 NoSpaceLeft,
2414 PathAlreadyExists,
2415 ReadOnlyFileSystem,
2416 RenameAcrossMountPoints,
2417 /// WASI: file paths must be valid UTF-8.
2418 /// Windows: file paths provided by the user must be valid WTF-8.
2419 /// https://wtf-8.codeberg.page/
2420 BadPathName,
2421 NoDevice,
2422 SharingViolation,
2423 PipeBusy,
2424 /// On Windows, `\\server` or `\\server\share` was not found.
2425 NetworkNotFound,
2426 /// On Windows, antivirus software is enabled by default. It can be
2427 /// disabled, but Windows Update sometimes ignores the user's preference
2428 /// and re-enables it. When enabled, antivirus software on Windows
2429 /// intercepts file system operations and makes them significantly slower
2430 /// in addition to possibly failing with this error code.
2431 AntivirusInterference,
2432} || UnexpectedError;
2433
2434/// Change the name or location of a file.
2435/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2436/// On WASI, both paths should be encoded as valid UTF-8.
2437/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2438pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
2439 if (native_os == .wasi and !builtin.link_libc) {
2440 return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path);
2441 } else if (native_os == .windows) {
2442 const old_path_w = try windows.sliceToPrefixedFileW(null, old_path);
2443 const new_path_w = try windows.sliceToPrefixedFileW(null, new_path);
2444 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2445 } else {
2446 const old_path_c = try toPosixPath(old_path);
2447 const new_path_c = try toPosixPath(new_path);
2448 return renameZ(&old_path_c, &new_path_c);
2449 }
2450}
2451
2452/// Same as `rename` except the parameters are null-terminated.
2453pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!void {
2454 if (native_os == .windows) {
2455 const old_path_w = try windows.cStrToPrefixedFileW(null, old_path);
2456 const new_path_w = try windows.cStrToPrefixedFileW(null, new_path);
2457 return renameW(old_path_w.span().ptr, new_path_w.span().ptr);
2458 } else if (native_os == .wasi and !builtin.link_libc) {
2459 return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0));
2460 }
2461 switch (errno(system.rename(old_path, new_path))) {
2462 .SUCCESS => return,
2463 .ACCES => return error.AccessDenied,
2464 .PERM => return error.PermissionDenied,
2465 .BUSY => return error.FileBusy,
2466 .DQUOT => return error.DiskQuota,
2467 .FAULT => unreachable,
2468 .INVAL => unreachable,
2469 .ISDIR => return error.IsDir,
2470 .LOOP => return error.SymLinkLoop,
2471 .MLINK => return error.LinkQuotaExceeded,
2472 .NAMETOOLONG => return error.NameTooLong,
2473 .NOENT => return error.FileNotFound,
2474 .NOTDIR => return error.NotDir,
2475 .NOMEM => return error.SystemResources,
2476 .NOSPC => return error.NoSpaceLeft,
2477 .EXIST => return error.PathAlreadyExists,
2478 .NOTEMPTY => return error.PathAlreadyExists,
2479 .ROFS => return error.ReadOnlyFileSystem,
2480 .XDEV => return error.RenameAcrossMountPoints,
2481 .ILSEQ => return error.BadPathName,
2482 else => |err| return unexpectedErrno(err),
2483 }
2484}
2485
2486/// Same as `rename` except the parameters are null-terminated and WTF16LE encoded.
2487/// Assumes target is Windows.
2488pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!void {
2489 const cwd_handle = std.fs.cwd().fd;
2490 return windows.RenameFile(cwd_handle, mem.span(old_path), cwd_handle, mem.span(new_path), true);
2491}
2492
2493/// Change the name or location of a file based on an open directory handle.
2494/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2495/// On WASI, both paths should be encoded as valid UTF-8.
2496/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2497pub fn renameat(
2498 old_dir_fd: fd_t,
2499 old_path: []const u8,
2500 new_dir_fd: fd_t,
2501 new_path: []const u8,
2502) RenameError!void {
2503 if (native_os == .windows) {
2504 const old_path_w = try windows.sliceToPrefixedFileW(old_dir_fd, old_path);
2505 const new_path_w = try windows.sliceToPrefixedFileW(new_dir_fd, new_path);
2506 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2507 } else if (native_os == .wasi and !builtin.link_libc) {
2508 const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path };
2509 const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path };
2510 return renameatWasi(old, new);
2511 } else {
2512 const old_path_c = try toPosixPath(old_path);
2513 const new_path_c = try toPosixPath(new_path);
2514 return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
2515 }
2516}
2517
2518/// WASI-only. Same as `renameat` expect targeting WASI.
2519/// See also `renameat`.
2520fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
2521 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)) {
2522 .SUCCESS => return,
2523 .ACCES => return error.AccessDenied,
2524 .PERM => return error.PermissionDenied,
2525 .BUSY => return error.FileBusy,
2526 .DQUOT => return error.DiskQuota,
2527 .FAULT => unreachable,
2528 .INVAL => unreachable,
2529 .ISDIR => return error.IsDir,
2530 .LOOP => return error.SymLinkLoop,
2531 .MLINK => return error.LinkQuotaExceeded,
2532 .NAMETOOLONG => return error.NameTooLong,
2533 .NOENT => return error.FileNotFound,
2534 .NOTDIR => return error.NotDir,
2535 .NOMEM => return error.SystemResources,
2536 .NOSPC => return error.NoSpaceLeft,
2537 .EXIST => return error.PathAlreadyExists,
2538 .NOTEMPTY => return error.PathAlreadyExists,
2539 .ROFS => return error.ReadOnlyFileSystem,
2540 .XDEV => return error.RenameAcrossMountPoints,
2541 .NOTCAPABLE => return error.AccessDenied,
2542 .ILSEQ => return error.BadPathName,
2543 else => |err| return unexpectedErrno(err),
2544 }
2545}
2546
2547/// An fd-relative file path
2548///
2549/// This is currently only used for WASI-specific functionality, but the concept
2550/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
2551const RelativePathWasi = struct {
2552 /// Handle to directory
2553 dir_fd: fd_t,
2554 /// Path to resource within `dir_fd`.
2555 relative_path: []const u8,
2556};
2557
2558/// Same as `renameat` except the parameters are null-terminated.
2559pub fn renameatZ(
2560 old_dir_fd: fd_t,
2561 old_path: [*:0]const u8,
2562 new_dir_fd: fd_t,
2563 new_path: [*:0]const u8,
2564) RenameError!void {
2565 if (native_os == .windows) {
2566 const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path);
2567 const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path);
2568 return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE);
2569 } else if (native_os == .wasi and !builtin.link_libc) {
2570 return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0));
2571 }
2572
2573 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
2574 .SUCCESS => return,
2575 .ACCES => return error.AccessDenied,
2576 .PERM => return error.PermissionDenied,
2577 .BUSY => return error.FileBusy,
2578 .DQUOT => return error.DiskQuota,
2579 .FAULT => unreachable,
2580 .INVAL => unreachable,
2581 .ISDIR => return error.IsDir,
2582 .LOOP => return error.SymLinkLoop,
2583 .MLINK => return error.LinkQuotaExceeded,
2584 .NAMETOOLONG => return error.NameTooLong,
2585 .NOENT => return error.FileNotFound,
2586 .NOTDIR => return error.NotDir,
2587 .NOMEM => return error.SystemResources,
2588 .NOSPC => return error.NoSpaceLeft,
2589 .EXIST => return error.PathAlreadyExists,
2590 .NOTEMPTY => return error.PathAlreadyExists,
2591 .ROFS => return error.ReadOnlyFileSystem,
2592 .XDEV => return error.RenameAcrossMountPoints,
2593 .ILSEQ => return error.BadPathName,
2594 else => |err| return unexpectedErrno(err),
1986 .NOMEM => return error.SystemResources,
1987 .NOSPC => return error.NoSpaceLeft,
1988 .NOTDIR => return error.NotDir,
1989 .PERM => return error.PermissionDenied,
1990 .ROFS => return error.ReadOnlyFileSystem,
1991 .XDEV => return error.NotSameFileSystem,
1992 .INVAL => unreachable,
1993 .ILSEQ => return error.BadPathName,
1994 else => |err| return unexpectedErrno(err),
1995 }
25951996 }
1997 const old = try toPosixPath(oldpath);
1998 const new = try toPosixPath(newpath);
1999 return try linkatZ(olddir, &old, newdir, &new, flags);
25962000}
25972001
2598/// Same as `renameat` but Windows-only and the path parameters are
2599/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.
2600pub fn renameatW(
2601 old_dir_fd: fd_t,
2602 old_path_w: []const u16,
2603 new_dir_fd: fd_t,
2604 new_path_w: []const u16,
2605 ReplaceIfExists: windows.BOOLEAN,
2606) RenameError!void {
2607 return windows.RenameFile(old_dir_fd, old_path_w, new_dir_fd, new_path_w, ReplaceIfExists != 0);
2608}
2002/// An fd-relative file path
2003///
2004/// This is currently only used for WASI-specific functionality, but the concept
2005/// is the same as the dirfd/pathname pairs in the `*at(...)` POSIX functions.
2006const RelativePathWasi = struct {
2007 /// Handle to directory
2008 dir_fd: fd_t,
2009 /// Path to resource within `dir_fd`.
2010 relative_path: []const u8,
2011};
26092012
26102013/// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
26112014/// On WASI, `sub_dir_path` should be encoded as valid UTF-8.
......@@ -2723,84 +2126,6 @@ pub fn mkdirW(dir_path_w: []const u16, mode: mode_t) MakeDirError!void {
27232126 windows.CloseHandle(sub_dir_handle);
27242127}
27252128
2726pub const DeleteDirError = error{
2727 AccessDenied,
2728 PermissionDenied,
2729 FileBusy,
2730 SymLinkLoop,
2731 NameTooLong,
2732 FileNotFound,
2733 SystemResources,
2734 NotDir,
2735 DirNotEmpty,
2736 ReadOnlyFileSystem,
2737 /// WASI: file paths must be valid UTF-8.
2738 /// Windows: file paths provided by the user must be valid WTF-8.
2739 /// https://wtf-8.codeberg.page/
2740 BadPathName,
2741 /// On Windows, `\\server` or `\\server\share` was not found.
2742 NetworkNotFound,
2743} || UnexpectedError;
2744
2745/// Deletes an empty directory.
2746/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2747/// On WASI, `dir_path` should be encoded as valid UTF-8.
2748/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2749pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
2750 if (native_os == .wasi and !builtin.link_libc) {
2751 return unlinkat(AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) {
2752 error.FileSystem => unreachable, // only occurs when targeting files
2753 error.IsDir => unreachable, // only occurs when targeting files
2754 else => |e| return e,
2755 };
2756 } else if (native_os == .windows) {
2757 const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path);
2758 return rmdirW(dir_path_w.span());
2759 } else {
2760 const dir_path_c = try toPosixPath(dir_path);
2761 return rmdirZ(&dir_path_c);
2762 }
2763}
2764
2765/// Same as `rmdir` except the parameter is null-terminated.
2766/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2767/// On WASI, `dir_path` should be encoded as valid UTF-8.
2768/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
2769pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
2770 if (native_os == .windows) {
2771 const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path);
2772 return rmdirW(dir_path_w.span());
2773 } else if (native_os == .wasi and !builtin.link_libc) {
2774 return rmdir(mem.sliceTo(dir_path, 0));
2775 }
2776 switch (errno(system.rmdir(dir_path))) {
2777 .SUCCESS => return,
2778 .ACCES => return error.AccessDenied,
2779 .PERM => return error.PermissionDenied,
2780 .BUSY => return error.FileBusy,
2781 .FAULT => unreachable,
2782 .INVAL => return error.BadPathName,
2783 .LOOP => return error.SymLinkLoop,
2784 .NAMETOOLONG => return error.NameTooLong,
2785 .NOENT => return error.FileNotFound,
2786 .NOMEM => return error.SystemResources,
2787 .NOTDIR => return error.NotDir,
2788 .EXIST => return error.DirNotEmpty,
2789 .NOTEMPTY => return error.DirNotEmpty,
2790 .ROFS => return error.ReadOnlyFileSystem,
2791 .ILSEQ => return error.BadPathName,
2792 else => |err| return unexpectedErrno(err),
2793 }
2794}
2795
2796/// Windows-only. Same as `rmdir` except the parameter is WTF-16 LE encoded.
2797pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
2798 return windows.DeleteFile(dir_path_w, .{ .dir = fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
2799 error.IsDir => unreachable,
2800 else => |e| return e,
2801 };
2802}
2803
28042129pub const ChangeCurDirError = error{
28052130 AccessDenied,
28062131 FileSystem,
......@@ -2889,194 +2214,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void {
28892214 }
28902215}
28912216
2892pub const ReadLinkError = error{
2893 /// In WASI, this error may occur when the file descriptor does
2894 /// not hold the required rights to read value of a symbolic link relative to it.
2895 AccessDenied,
2896 PermissionDenied,
2897 FileSystem,
2898 SymLinkLoop,
2899 NameTooLong,
2900 FileNotFound,
2901 SystemResources,
2902 NotLink,
2903 NotDir,
2904 /// WASI: file paths must be valid UTF-8.
2905 /// Windows: file paths provided by the user must be valid WTF-8.
2906 /// https://wtf-8.codeberg.page/
2907 BadPathName,
2908 /// Windows-only. This error may occur if the opened reparse point is
2909 /// of unsupported type.
2910 UnsupportedReparsePointType,
2911 /// On Windows, `\\server` or `\\server\share` was not found.
2912 NetworkNotFound,
2913 /// On Windows, antivirus software is enabled by default. It can be
2914 /// disabled, but Windows Update sometimes ignores the user's preference
2915 /// and re-enables it. When enabled, antivirus software on Windows
2916 /// intercepts file system operations and makes them significantly slower
2917 /// in addition to possibly failing with this error code.
2918 AntivirusInterference,
2919} || UnexpectedError;
2920
2921/// Read value of a symbolic link.
2922/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2923/// On WASI, `file_path` should be encoded as valid UTF-8.
2924/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2925/// The return value is a slice of `out_buffer` from index 0.
2926/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2927/// On WASI, the result is encoded as UTF-8.
2928/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2929pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2930 if (native_os == .wasi and !builtin.link_libc) {
2931 return readlinkat(AT.FDCWD, file_path, out_buffer);
2932 } else if (native_os == .windows) {
2933 var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
2934 const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
2935
2936 const len = std.unicode.calcWtf8Len(result_w);
2937 if (len > out_buffer.len) return error.NameTooLong;
2938
2939 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
2940 return out_buffer[0..end_index];
2941 } else {
2942 const file_path_c = try toPosixPath(file_path);
2943 return readlinkZ(&file_path_c, out_buffer);
2944 }
2945}
2946
2947/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed.
2948/// The result is encoded as WTF-16 LE.
2949///
2950/// `file_path` will never be accessed after `out_buffer` has been written to, so it
2951/// is safe to reuse a single buffer for both.
2952///
2953/// See also `readlinkZ`.
2954pub fn readlinkW(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
2955 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
2956}
2957
2958/// Same as `readlink` except `file_path` is null-terminated.
2959pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
2960 if (native_os == .windows) {
2961 var file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
2962 const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
2963
2964 const len = std.unicode.calcWtf8Len(result_w);
2965 if (len > out_buffer.len) return error.NameTooLong;
2966
2967 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
2968 return out_buffer[0..end_index];
2969 } else if (native_os == .wasi and !builtin.link_libc) {
2970 return readlink(mem.sliceTo(file_path, 0), out_buffer);
2971 }
2972 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
2973 switch (errno(rc)) {
2974 .SUCCESS => return out_buffer[0..@bitCast(rc)],
2975 .ACCES => return error.AccessDenied,
2976 .FAULT => unreachable,
2977 .INVAL => return error.NotLink,
2978 .IO => return error.FileSystem,
2979 .LOOP => return error.SymLinkLoop,
2980 .NAMETOOLONG => return error.NameTooLong,
2981 .NOENT => return error.FileNotFound,
2982 .NOMEM => return error.SystemResources,
2983 .NOTDIR => return error.NotDir,
2984 .ILSEQ => return error.BadPathName,
2985 else => |err| return unexpectedErrno(err),
2986 }
2987}
2988
2989/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle.
2990/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2991/// On WASI, `file_path` should be encoded as valid UTF-8.
2992/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
2993/// The return value is a slice of `out_buffer` from index 0.
2994/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2995/// On WASI, the result is encoded as UTF-8.
2996/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
2997/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`.
2998pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2999 if (native_os == .wasi and !builtin.link_libc) {
3000 return readlinkatWasi(dirfd, file_path, out_buffer);
3001 }
3002 if (native_os == .windows) {
3003 var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
3004 const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
3005
3006 const len = std.unicode.calcWtf8Len(result_w);
3007 if (len > out_buffer.len) return error.NameTooLong;
3008
3009 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3010 return out_buffer[0..end_index];
3011 }
3012 const file_path_c = try toPosixPath(file_path);
3013 return readlinkatZ(dirfd, &file_path_c, out_buffer);
3014}
3015
3016/// WASI-only. Same as `readlinkat` but targets WASI.
3017/// See also `readlinkat`.
3018pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3019 var bufused: usize = undefined;
3020 switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) {
3021 .SUCCESS => return out_buffer[0..bufused],
3022 .ACCES => return error.AccessDenied,
3023 .FAULT => unreachable,
3024 .INVAL => return error.NotLink,
3025 .IO => return error.FileSystem,
3026 .LOOP => return error.SymLinkLoop,
3027 .NAMETOOLONG => return error.NameTooLong,
3028 .NOENT => return error.FileNotFound,
3029 .NOMEM => return error.SystemResources,
3030 .NOTDIR => return error.NotDir,
3031 .NOTCAPABLE => return error.AccessDenied,
3032 .ILSEQ => return error.BadPathName,
3033 else => |err| return unexpectedErrno(err),
3034 }
3035}
3036
3037/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed.
3038/// The result is encoded as WTF-16 LE.
3039///
3040/// `file_path` will never be accessed after `out_buffer` has been written to, so it
3041/// is safe to reuse a single buffer for both.
3042///
3043/// See also `readlinkat`.
3044pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
3045 return windows.ReadLink(dirfd, file_path, out_buffer);
3046}
3047
3048/// Same as `readlinkat` except `file_path` is null-terminated.
3049/// See also `readlinkat`.
3050pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
3051 if (native_os == .windows) {
3052 var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
3053 const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
3054
3055 const len = std.unicode.calcWtf8Len(result_w);
3056 if (len > out_buffer.len) return error.NameTooLong;
3057
3058 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3059 return out_buffer[0..end_index];
3060 } else if (native_os == .wasi and !builtin.link_libc) {
3061 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
3062 }
3063 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
3064 switch (errno(rc)) {
3065 .SUCCESS => return out_buffer[0..@bitCast(rc)],
3066 .ACCES => return error.AccessDenied,
3067 .FAULT => unreachable,
3068 .INVAL => return error.NotLink,
3069 .IO => return error.FileSystem,
3070 .LOOP => return error.SymLinkLoop,
3071 .NAMETOOLONG => return error.NameTooLong,
3072 .NOENT => return error.FileNotFound,
3073 .NOMEM => return error.SystemResources,
3074 .NOTDIR => return error.NotDir,
3075 .ILSEQ => return error.BadPathName,
3076 else => |err| return unexpectedErrno(err),
3077 }
3078}
3079
30802217pub const SetEidError = error{
30812218 InvalidUserId,
30822219 PermissionDenied,
......@@ -4814,157 +3951,6 @@ pub fn flock(fd: fd_t, operation: i32) FlockError!void {
48143951 }
48153952}
48163953
4817pub const RealPathError = error{
4818 FileNotFound,
4819 AccessDenied,
4820 PermissionDenied,
4821 NameTooLong,
4822 NotSupported,
4823 NotDir,
4824 SymLinkLoop,
4825 InputOutput,
4826 FileTooBig,
4827 IsDir,
4828 ProcessFdQuotaExceeded,
4829 SystemFdQuotaExceeded,
4830 NoDevice,
4831 SystemResources,
4832 NoSpaceLeft,
4833 FileSystem,
4834 DeviceBusy,
4835 ProcessNotFound,
4836
4837 SharingViolation,
4838 PipeBusy,
4839
4840 /// Windows: file paths provided by the user must be valid WTF-8.
4841 /// https://wtf-8.codeberg.page/
4842 BadPathName,
4843
4844 /// On Windows, `\\server` or `\\server\share` was not found.
4845 NetworkNotFound,
4846
4847 PathAlreadyExists,
4848
4849 /// On Windows, antivirus software is enabled by default. It can be
4850 /// disabled, but Windows Update sometimes ignores the user's preference
4851 /// and re-enables it. When enabled, antivirus software on Windows
4852 /// intercepts file system operations and makes them significantly slower
4853 /// in addition to possibly failing with this error code.
4854 AntivirusInterference,
4855
4856 /// On Windows, the volume does not contain a recognized file system. File
4857 /// system drivers might not be loaded, or the volume may be corrupt.
4858 UnrecognizedVolume,
4859
4860 Canceled,
4861} || UnexpectedError;
4862
4863/// Return the canonicalized absolute pathname.
4864///
4865/// Expands all symbolic links and resolves references to `.`, `..`, and
4866/// extra `/` characters in `pathname`.
4867///
4868/// On Windows, `pathname` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
4869///
4870/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
4871///
4872/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
4873///
4874/// See also `realpathZ` and `realpathW`.
4875///
4876/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
4877/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
4878///
4879/// Calling this function is usually a bug.
4880pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
4881 if (native_os == .windows) {
4882 var pathname_w = try windows.sliceToPrefixedFileW(null, pathname);
4883
4884 const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data);
4885
4886 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
4887 return out_buffer[0..end_index];
4888 } else if (native_os == .wasi and !builtin.link_libc) {
4889 @compileError("WASI does not support os.realpath");
4890 }
4891 const pathname_c = try toPosixPath(pathname);
4892 return realpathZ(&pathname_c, out_buffer);
4893}
4894
4895/// Same as `realpath` except `pathname` is null-terminated.
4896///
4897/// Calling this function is usually a bug.
4898pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
4899 if (native_os == .windows) {
4900 var pathname_w = try windows.cStrToPrefixedFileW(null, pathname);
4901
4902 const wide_slice = try realpathW2(pathname_w.span(), &pathname_w.data);
4903
4904 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
4905 return out_buffer[0..end_index];
4906 } else if (native_os == .wasi and !builtin.link_libc) {
4907 return realpath(mem.sliceTo(pathname, 0), out_buffer);
4908 }
4909 if (!builtin.link_libc) {
4910 const flags: O = switch (native_os) {
4911 .linux => .{
4912 .NONBLOCK = true,
4913 .CLOEXEC = true,
4914 .PATH = true,
4915 },
4916 else => .{
4917 .NONBLOCK = true,
4918 .CLOEXEC = true,
4919 },
4920 };
4921 const fd = openZ(pathname, flags, 0) catch |err| switch (err) {
4922 error.FileLocksNotSupported => unreachable,
4923 error.WouldBlock => unreachable,
4924 error.FileBusy => unreachable, // not asking for write permissions
4925 else => |e| return e,
4926 };
4927 defer close(fd);
4928
4929 return std.os.getFdPath(fd, out_buffer);
4930 }
4931 const result_path = std.c.realpath(pathname, out_buffer) orelse switch (@as(E, @enumFromInt(std.c._errno().*))) {
4932 .SUCCESS => unreachable,
4933 .INVAL => unreachable,
4934 .BADF => unreachable,
4935 .FAULT => unreachable,
4936 .ACCES => return error.AccessDenied,
4937 .NOENT => return error.FileNotFound,
4938 .OPNOTSUPP => return error.NotSupported,
4939 .NOTDIR => return error.NotDir,
4940 .NAMETOOLONG => return error.NameTooLong,
4941 .LOOP => return error.SymLinkLoop,
4942 .IO => return error.InputOutput,
4943 else => |err| return unexpectedErrno(err),
4944 };
4945 return mem.sliceTo(result_path, 0);
4946}
4947
4948/// Deprecated: use `realpathW2`.
4949///
4950/// Same as `realpath` except `pathname` is WTF16LE-encoded.
4951///
4952/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
4953///
4954/// Calling this function is usually a bug.
4955pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPathError![]u8 {
4956 return fs.cwd().realpathW(pathname, out_buffer);
4957}
4958
4959/// Same as `realpath` except `pathname` is WTF16LE-encoded.
4960///
4961/// The result is encoded as WTF16LE.
4962///
4963/// Calling this function is usually a bug.
4964pub fn realpathW2(pathname: []const u16, out_buffer: *[std.os.windows.PATH_MAX_WIDE]u16) RealPathError![]u16 {
4965 return fs.cwd().realpathW2(pathname, out_buffer);
4966}
4967
49683954/// Spurious wakeups are possible and no precision of timing is guaranteed.
49693955pub fn nanosleep(seconds: u64, nanoseconds: u64) void {
49703956 var req = timespec{
lib/std/zig/system.zig+4-2
......@@ -786,7 +786,9 @@ test glibcVerFromLinkName {
786786}
787787
788788fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
789 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
789 const cwd: Io.Dir = .cwd();
790
791 var dir = cwd.openDir(io, rpath, .{}) catch |err| switch (err) {
790792 error.NameTooLong => return error.Unexpected,
791793 error.BadPathName => return error.Unexpected,
792794 error.DeviceBusy => return error.Unexpected,
......@@ -805,7 +807,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
805807 error.Unexpected => |e| return e,
806808 error.Canceled => |e| return e,
807809 };
808 defer dir.close();
810 defer dir.close(io);
809811
810812 // Now we have a candidate for the path to libc shared object. In
811813 // the past, we used readlink() here because the link name would