| author | |
| committer | |
| log | d1d2c37af26902f953b2b72335b326c4b01e3bb2 |
| tree | ff74335c6b9eda252c6c4970d49f36bd6f904537 |
| parent | 81214278ca14b832e53876feb70fa3c072c14dd6 |
24 files changed, 3226 insertions(+), 3812 deletions(-)
CMakeLists.txt-2| ... | ... | @@ -436,8 +436,6 @@ set(ZIG_STAGE2_SOURCES |
| 436 | 436 | lib/std/fmt.zig |
| 437 | 437 | lib/std/fmt/parse_float.zig |
| 438 | 438 | lib/std/fs.zig |
| 439 | lib/std/fs/AtomicFile.zig | |
| 440 | lib/std/fs/Dir.zig | |
| 441 | 439 | lib/std/fs/File.zig |
| 442 | 440 | lib/std/fs/get_app_data_dir.zig |
| 443 | 441 | lib/std/fs/path.zig |
build.zig+22-13| ... | ... | @@ -1,18 +1,20 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const builtin = std.builtin; |
| 3 | const tests = @import("test/tests.zig"); | |
| 4 | 3 | const BufMap = std.BufMap; |
| 5 | 4 | const mem = std.mem; |
| 6 | const io = std.io; | |
| 7 | 5 | const fs = std.fs; |
| 8 | 6 | const InstallDirectoryOptions = std.Build.InstallDirectoryOptions; |
| 9 | 7 | const assert = std.debug.assert; |
| 8 | const Io = std.Io; | |
| 9 | ||
| 10 | const tests = @import("test/tests.zig"); | |
| 10 | 11 | const DevEnv = @import("src/dev.zig").Env; |
| 11 | const ValueInterpretMode = enum { direct, by_name }; | |
| 12 | 12 | |
| 13 | 13 | const zig_version: std.SemanticVersion = .{ .major = 0, .minor = 16, .patch = 0 }; |
| 14 | 14 | const stack_size = 46 * 1024 * 1024; |
| 15 | 15 | |
| 16 | const ValueInterpretMode = enum { direct, by_name }; | |
| 17 | ||
| 16 | 18 | pub fn build(b: *std.Build) !void { |
| 17 | 19 | const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false; |
| 18 | 20 | const target = b.standardTargetOptions(.{ |
| ... | ... | @@ -306,8 +308,10 @@ pub fn build(b: *std.Build) !void { |
| 306 | 308 | |
| 307 | 309 | if (enable_llvm) { |
| 308 | 310 | const cmake_cfg = if (static_llvm) null else blk: { |
| 311 | const io = b.graph.io; | |
| 312 | const cwd: Io.Dir = .cwd(); | |
| 309 | 313 | 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; | |
| 311 | 315 | break :blk parseConfigH(b, file_contents); |
| 312 | 316 | } else { |
| 313 | 317 | std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{}); |
| ... | ... | @@ -1153,10 +1157,13 @@ const CMakeConfig = struct { |
| 1153 | 1157 | const max_config_h_bytes = 1 * 1024 * 1024; |
| 1154 | 1158 | |
| 1155 | 1159 | fn 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 | ||
| 1156 | 1163 | 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, .{}); | |
| 1158 | 1165 | if (config_h_or_err) |*file| { |
| 1159 | file.close(); | |
| 1166 | file.close(io); | |
| 1160 | 1167 | return path; |
| 1161 | 1168 | } else |_| { |
| 1162 | 1169 | 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 { |
| 1166 | 1173 | |
| 1167 | 1174 | var check_dir = fs.path.dirname(b.graph.zig_exe).?; |
| 1168 | 1175 | 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); | |
| 1171 | 1178 | |
| 1172 | 1179 | // 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", .{}); | |
| 1174 | 1181 | if (config_h_or_err) |*file| { |
| 1175 | file.close(); | |
| 1182 | file.close(io); | |
| 1176 | 1183 | return fs.path.join( |
| 1177 | 1184 | b.allocator, |
| 1178 | 1185 | &[_][]const u8{ check_dir, "config.h" }, |
| ... | ... | @@ -1183,9 +1190,9 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { |
| 1183 | 1190 | } |
| 1184 | 1191 | |
| 1185 | 1192 | // 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", .{}); | |
| 1187 | 1194 | if (git_dir_or_err) |*git_dir| { |
| 1188 | git_dir.close(); | |
| 1195 | git_dir.close(io); | |
| 1189 | 1196 | return null; |
| 1190 | 1197 | } else |_| {} |
| 1191 | 1198 | |
| ... | ... | @@ -1581,6 +1588,8 @@ const llvm_libs_xtensa = [_][]const u8{ |
| 1581 | 1588 | }; |
| 1582 | 1589 | |
| 1583 | 1590 | fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1591 | const io = b.graph.io; | |
| 1592 | ||
| 1584 | 1593 | const doctest_exe = b.addExecutable(.{ |
| 1585 | 1594 | .name = "doctest", |
| 1586 | 1595 | .root_module = b.createModule(.{ |
| ... | ... | @@ -1590,7 +1599,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1590 | 1599 | }), |
| 1591 | 1600 | }); |
| 1592 | 1601 | |
| 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| { | |
| 1594 | 1603 | std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{ |
| 1595 | 1604 | b.build_root, @errorName(err), |
| 1596 | 1605 | }); |
lib/compiler/build_runner.zig+8-6| ... | ... | @@ -53,24 +53,26 @@ pub fn main() !void { |
| 53 | 53 | const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{}); |
| 54 | 54 | const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{}); |
| 55 | 55 | |
| 56 | const cwd: Io.Dir = .cwd(); | |
| 57 | ||
| 56 | 58 | const zig_lib_directory: std.Build.Cache.Directory = .{ |
| 57 | 59 | .path = zig_lib_dir, |
| 58 | .handle = try std.fs.cwd().openDir(zig_lib_dir, .{}), | |
| 60 | .handle = try cwd.openDir(io, zig_lib_dir, .{}), | |
| 59 | 61 | }; |
| 60 | 62 | |
| 61 | 63 | const build_root_directory: std.Build.Cache.Directory = .{ |
| 62 | 64 | .path = build_root, |
| 63 | .handle = try std.fs.cwd().openDir(build_root, .{}), | |
| 65 | .handle = try cwd.openDir(io, build_root, .{}), | |
| 64 | 66 | }; |
| 65 | 67 | |
| 66 | 68 | const local_cache_directory: std.Build.Cache.Directory = .{ |
| 67 | 69 | .path = cache_root, |
| 68 | .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}), | |
| 70 | .handle = try cwd.makeOpenPath(io, cache_root, .{}), | |
| 69 | 71 | }; |
| 70 | 72 | |
| 71 | 73 | const global_cache_directory: std.Build.Cache.Directory = .{ |
| 72 | 74 | .path = global_cache_root, |
| 73 | .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}), | |
| 75 | .handle = try cwd.makeOpenPath(io, global_cache_root, .{}), | |
| 74 | 76 | }; |
| 75 | 77 | |
| 76 | 78 | var graph: std.Build.Graph = .{ |
| ... | ... | @@ -79,7 +81,7 @@ pub fn main() !void { |
| 79 | 81 | .cache = .{ |
| 80 | 82 | .io = io, |
| 81 | 83 | .gpa = arena, |
| 82 | .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}), | |
| 84 | .manifest_dir = try local_cache_directory.handle.makeOpenPath(io, "h", .{}), | |
| 83 | 85 | }, |
| 84 | 86 | .zig_exe = zig_exe, |
| 85 | 87 | .env_map = try process.getEnvMap(arena), |
| ... | ... | @@ -92,7 +94,7 @@ pub fn main() !void { |
| 92 | 94 | .time_report = false, |
| 93 | 95 | }; |
| 94 | 96 | |
| 95 | graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); | |
| 97 | graph.cache.addPrefix(.{ .path = null, .handle = cwd }); | |
| 96 | 98 | graph.cache.addPrefix(build_root_directory); |
| 97 | 99 | graph.cache.addPrefix(local_cache_directory); |
| 98 | 100 | graph.cache.addPrefix(global_cache_directory); |
lib/std/Build.zig+3-4| ... | ... | @@ -1700,9 +1700,8 @@ pub fn addCheckFile( |
| 1700 | 1700 | } |
| 1701 | 1701 | |
| 1702 | 1702 | pub 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}); | |
| 1706 | 1705 | const cwd = fs.cwd(); |
| 1707 | 1706 | var src_file = cwd.createFile(dest_path, .{}) catch |err| switch (err) { |
| 1708 | 1707 | error.FileNotFound => blk: { |
| ... | ... | @@ -1713,7 +1712,7 @@ pub fn truncateFile(b: *Build, dest_path: []const u8) (fs.Dir.MakeError || fs.Di |
| 1713 | 1712 | }, |
| 1714 | 1713 | else => |e| return e, |
| 1715 | 1714 | }; |
| 1716 | src_file.close(); | |
| 1715 | src_file.close(io); | |
| 1717 | 1716 | } |
| 1718 | 1717 | |
| 1719 | 1718 | /// 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"); |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | const Io = std.Io; |
| 10 | 10 | const crypto = std.crypto; |
| 11 | const fs = std.fs; | |
| 12 | 11 | const assert = std.debug.assert; |
| 13 | 12 | const testing = std.testing; |
| 14 | 13 | const mem = std.mem; |
| ... | ... | @@ -18,7 +17,7 @@ const log = std.log.scoped(.cache); |
| 18 | 17 | |
| 19 | 18 | gpa: Allocator, |
| 20 | 19 | io: Io, |
| 21 | manifest_dir: fs.Dir, | |
| 20 | manifest_dir: Io.Dir, | |
| 22 | 21 | hash: HashHelper = .{}, |
| 23 | 22 | /// This value is accessed from multiple threads, protected by mutex. |
| 24 | 23 | recent_problematic_timestamp: Io.Timestamp = .zero, |
| ... | ... | @@ -71,7 +70,7 @@ const PrefixedPath = struct { |
| 71 | 70 | |
| 72 | 71 | fn findPrefix(cache: *const Cache, file_path: []const u8) !PrefixedPath { |
| 73 | 72 | 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}); | |
| 75 | 74 | errdefer gpa.free(resolved_path); |
| 76 | 75 | return findPrefixResolved(cache, resolved_path); |
| 77 | 76 | } |
| ... | ... | @@ -102,9 +101,9 @@ fn findPrefixResolved(cache: *const Cache, resolved_path: []u8) !PrefixedPath { |
| 102 | 101 | } |
| 103 | 102 | |
| 104 | 103 | fn 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); | |
| 106 | 105 | errdefer allocator.free(relative); |
| 107 | var component_iterator = fs.path.NativeComponentIterator.init(relative); | |
| 106 | var component_iterator = std.fs.path.NativeComponentIterator.init(relative); | |
| 108 | 107 | if (component_iterator.root() != null) { |
| 109 | 108 | return error.NotASubPath; |
| 110 | 109 | } |
| ... | ... | @@ -145,17 +144,17 @@ pub const File = struct { |
| 145 | 144 | max_file_size: ?usize, |
| 146 | 145 | /// Populated if the user calls `addOpenedFile`. |
| 147 | 146 | /// The handle is not owned here. |
| 148 | handle: ?fs.File, | |
| 147 | handle: ?Io.File, | |
| 149 | 148 | stat: Stat, |
| 150 | 149 | bin_digest: BinDigest, |
| 151 | 150 | contents: ?[]const u8, |
| 152 | 151 | |
| 153 | 152 | pub const Stat = struct { |
| 154 | inode: fs.File.INode, | |
| 153 | inode: Io.File.INode, | |
| 155 | 154 | size: u64, |
| 156 | 155 | mtime: Io.Timestamp, |
| 157 | 156 | |
| 158 | pub fn fromFs(fs_stat: fs.File.Stat) Stat { | |
| 157 | pub fn fromFs(fs_stat: Io.File.Stat) Stat { | |
| 159 | 158 | return .{ |
| 160 | 159 | .inode = fs_stat.inode, |
| 161 | 160 | .size = fs_stat.size, |
| ... | ... | @@ -178,7 +177,7 @@ pub const File = struct { |
| 178 | 177 | file.max_file_size = if (file.max_file_size) |old| @max(old, new) else new; |
| 179 | 178 | } |
| 180 | 179 | |
| 181 | pub fn updateHandle(file: *File, new_handle: ?fs.File) void { | |
| 180 | pub fn updateHandle(file: *File, new_handle: ?Io.File) void { | |
| 182 | 181 | const handle = new_handle orelse return; |
| 183 | 182 | file.handle = handle; |
| 184 | 183 | } |
| ... | ... | @@ -293,16 +292,16 @@ pub fn binToHex(bin_digest: BinDigest) HexDigest { |
| 293 | 292 | } |
| 294 | 293 | |
| 295 | 294 | pub const Lock = struct { |
| 296 | manifest_file: fs.File, | |
| 295 | manifest_file: Io.File, | |
| 297 | 296 | |
| 298 | pub fn release(lock: *Lock) void { | |
| 297 | pub fn release(lock: *Lock, io: Io) void { | |
| 299 | 298 | if (builtin.os.tag == .windows) { |
| 300 | 299 | // Windows does not guarantee that locks are immediately unlocked when |
| 301 | 300 | // the file handle is closed. See LockFileEx documentation. |
| 302 | 301 | lock.manifest_file.unlock(); |
| 303 | 302 | } |
| 304 | 303 | |
| 305 | lock.manifest_file.close(); | |
| 304 | lock.manifest_file.close(io); | |
| 306 | 305 | lock.* = undefined; |
| 307 | 306 | } |
| 308 | 307 | }; |
| ... | ... | @@ -311,7 +310,7 @@ pub const Manifest = struct { |
| 311 | 310 | cache: *Cache, |
| 312 | 311 | /// Current state for incremental hashing. |
| 313 | 312 | hash: HashHelper, |
| 314 | manifest_file: ?fs.File, | |
| 313 | manifest_file: ?Io.File, | |
| 315 | 314 | manifest_dirty: bool, |
| 316 | 315 | /// Set this flag to true before calling hit() in order to indicate that |
| 317 | 316 | /// upon a cache hit, the code using the cache will not modify the files |
| ... | ... | @@ -332,9 +331,9 @@ pub const Manifest = struct { |
| 332 | 331 | |
| 333 | 332 | pub const Diagnostic = union(enum) { |
| 334 | 333 | 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, | |
| 338 | 337 | file_open: FileOp, |
| 339 | 338 | file_stat: FileOp, |
| 340 | 339 | file_read: FileOp, |
| ... | ... | @@ -393,10 +392,10 @@ pub const Manifest = struct { |
| 393 | 392 | } |
| 394 | 393 | |
| 395 | 394 | /// 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 { | |
| 397 | 396 | const gpa = m.cache.gpa; |
| 398 | 397 | 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, &.{ | |
| 400 | 399 | path.root_dir.path orelse ".", |
| 401 | 400 | path.subPathOrDot(), |
| 402 | 401 | }); |
| ... | ... | @@ -417,7 +416,7 @@ pub const Manifest = struct { |
| 417 | 416 | return addFileInner(self, prefixed_path, null, max_file_size); |
| 418 | 417 | } |
| 419 | 418 | |
| 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 { | |
| 421 | 420 | const gop = self.files.getOrPutAssumeCapacityAdapted(prefixed_path, FilesAdapter{}); |
| 422 | 421 | if (gop.found_existing) { |
| 423 | 422 | self.cache.gpa.free(prefixed_path.sub_path); |
| ... | ... | @@ -460,7 +459,7 @@ pub const Manifest = struct { |
| 460 | 459 | } |
| 461 | 460 | } |
| 462 | 461 | |
| 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 { | |
| 464 | 463 | assert(self.manifest_file == null); |
| 465 | 464 | return self.addDepFileMaybePost(dir, dep_file_sub_path); |
| 466 | 465 | } |
| ... | ... | @@ -702,7 +701,7 @@ pub const Manifest = struct { |
| 702 | 701 | const file_path = iter.rest(); |
| 703 | 702 | |
| 704 | 703 | 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; | |
| 706 | 705 | const stat_mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; |
| 707 | 706 | const file_bin_digest = b: { |
| 708 | 707 | if (digest_str.len != hex_digest_len) return error.InvalidFormat; |
| ... | ... | @@ -772,7 +771,7 @@ pub const Manifest = struct { |
| 772 | 771 | return error.CacheCheckFailed; |
| 773 | 772 | }, |
| 774 | 773 | }; |
| 775 | defer this_file.close(); | |
| 774 | defer this_file.close(io); | |
| 776 | 775 | |
| 777 | 776 | const actual_stat = this_file.stat() catch |err| { |
| 778 | 777 | self.diagnostic = .{ .file_stat = .{ |
| ... | ... | @@ -879,7 +878,7 @@ pub const Manifest = struct { |
| 879 | 878 | error.Canceled => return error.Canceled, |
| 880 | 879 | else => return true, |
| 881 | 880 | }; |
| 882 | defer file.close(); | |
| 881 | defer file.close(io); | |
| 883 | 882 | |
| 884 | 883 | // Save locally and also save globally (we still hold the global lock). |
| 885 | 884 | const stat = file.stat() catch |err| switch (err) { |
| ... | ... | @@ -894,18 +893,20 @@ pub const Manifest = struct { |
| 894 | 893 | } |
| 895 | 894 | |
| 896 | 895 | fn populateFileHash(self: *Manifest, ch_file: *File) !void { |
| 896 | const io = self.cache.io; | |
| 897 | ||
| 897 | 898 | if (ch_file.handle) |handle| { |
| 898 | 899 | return populateFileHashHandle(self, ch_file, handle); |
| 899 | 900 | } else { |
| 900 | 901 | const pp = ch_file.prefixed_path; |
| 901 | 902 | const dir = self.cache.prefixes()[pp.prefix].handle; |
| 902 | 903 | const handle = try dir.openFile(pp.sub_path, .{}); |
| 903 | defer handle.close(); | |
| 904 | defer handle.close(io); | |
| 904 | 905 | return populateFileHashHandle(self, ch_file, handle); |
| 905 | 906 | } |
| 906 | 907 | } |
| 907 | 908 | |
| 908 | fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: fs.File) !void { | |
| 909 | fn populateFileHashHandle(self: *Manifest, ch_file: *File, handle: Io.File) !void { | |
| 909 | 910 | const actual_stat = try handle.stat(); |
| 910 | 911 | ch_file.stat = .{ |
| 911 | 912 | .size = actual_stat.size, |
| ... | ... | @@ -1064,12 +1065,12 @@ pub const Manifest = struct { |
| 1064 | 1065 | self.hash.hasher.update(&new_file.bin_digest); |
| 1065 | 1066 | } |
| 1066 | 1067 | |
| 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 { | |
| 1068 | 1069 | assert(self.manifest_file != null); |
| 1069 | 1070 | return self.addDepFileMaybePost(dir, dep_file_sub_path); |
| 1070 | 1071 | } |
| 1071 | 1072 | |
| 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 { | |
| 1073 | 1074 | const gpa = self.cache.gpa; |
| 1074 | 1075 | const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max)); |
| 1075 | 1076 | defer gpa.free(dep_file_contents); |
| ... | ... | @@ -1148,7 +1149,7 @@ pub const Manifest = struct { |
| 1148 | 1149 | } |
| 1149 | 1150 | } |
| 1150 | 1151 | |
| 1151 | fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void { | |
| 1152 | fn writeDirtyManifestToStream(self: *Manifest, fw: *Io.File.Writer) !void { | |
| 1152 | 1153 | try fw.interface.writeAll(manifest_header ++ "\n"); |
| 1153 | 1154 | for (self.files.keys()) |file| { |
| 1154 | 1155 | try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{ |
| ... | ... | @@ -1214,13 +1215,15 @@ pub const Manifest = struct { |
| 1214 | 1215 | /// `Manifest.hit` must be called first. |
| 1215 | 1216 | /// Don't forget to call `writeManifest` before this! |
| 1216 | 1217 | pub fn deinit(self: *Manifest) void { |
| 1218 | const io = self.cache.io; | |
| 1219 | ||
| 1217 | 1220 | if (self.manifest_file) |file| { |
| 1218 | 1221 | if (builtin.os.tag == .windows) { |
| 1219 | 1222 | // See Lock.release for why this is required on Windows |
| 1220 | 1223 | file.unlock(); |
| 1221 | 1224 | } |
| 1222 | 1225 | |
| 1223 | file.close(); | |
| 1226 | file.close(io); | |
| 1224 | 1227 | } |
| 1225 | 1228 | for (self.files.keys()) |*file| { |
| 1226 | 1229 | file.deinit(self.cache.gpa); |
| ... | ... | @@ -1281,7 +1284,7 @@ pub const Manifest = struct { |
| 1281 | 1284 | /// On operating systems that support symlinks, does a readlink. On other operating systems, |
| 1282 | 1285 | /// uses the file contents. Windows supports symlinks but only with elevated privileges, so |
| 1283 | 1286 | /// it is treated as not supporting symlinks. |
| 1284 | pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { | |
| 1287 | pub fn readSmallFile(dir: Io.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { | |
| 1285 | 1288 | if (builtin.os.tag == .windows) { |
| 1286 | 1289 | return dir.readFile(sub_path, buffer); |
| 1287 | 1290 | } else { |
| ... | ... | @@ -1293,7 +1296,7 @@ pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { |
| 1293 | 1296 | /// uses the file contents. Windows supports symlinks but only with elevated privileges, so |
| 1294 | 1297 | /// it is treated as not supporting symlinks. |
| 1295 | 1298 | /// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer. |
| 1296 | pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void { | |
| 1299 | pub fn writeSmallFile(dir: Io.Dir, sub_path: []const u8, data: []const u8) !void { | |
| 1297 | 1300 | assert(data.len <= 255); |
| 1298 | 1301 | if (builtin.os.tag == .windows) { |
| 1299 | 1302 | 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 |
| 1302 | 1305 | } |
| 1303 | 1306 | } |
| 1304 | 1307 | |
| 1305 | fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadError!void { | |
| 1308 | fn hashFile(file: Io.File, bin_digest: *[Hasher.mac_length]u8) Io.File.PReadError!void { | |
| 1306 | 1309 | var buf: [1024]u8 = undefined; |
| 1307 | 1310 | var hasher = hasher_init; |
| 1308 | 1311 | var off: u64 = 0; |
| ... | ... | @@ -1316,7 +1319,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro |
| 1316 | 1319 | } |
| 1317 | 1320 | |
| 1318 | 1321 | // Create/Write a file, close it, then grab its stat.mtime timestamp. |
| 1319 | fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp { | |
| 1322 | fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { | |
| 1320 | 1323 | const test_out_file = "test-filetimestamp.tmp"; |
| 1321 | 1324 | |
| 1322 | 1325 | var file = try dir.createFile(test_out_file, .{ |
| ... | ... | @@ -1324,7 +1327,7 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp { |
| 1324 | 1327 | .truncate = true, |
| 1325 | 1328 | }); |
| 1326 | 1329 | defer { |
| 1327 | file.close(); | |
| 1330 | file.close(io); | |
| 1328 | 1331 | dir.deleteFile(test_out_file) catch {}; |
| 1329 | 1332 | } |
| 1330 | 1333 | |
| ... | ... | @@ -1343,8 +1346,8 @@ test "cache file and then recall it" { |
| 1343 | 1346 | try tmp.dir.writeFile(.{ .sub_path = temp_file, .data = "Hello, world!\n" }); |
| 1344 | 1347 | |
| 1345 | 1348 | // 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) { | |
| 1348 | 1351 | try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); |
| 1349 | 1352 | } |
| 1350 | 1353 | |
| ... | ... | @@ -1358,7 +1361,7 @@ test "cache file and then recall it" { |
| 1358 | 1361 | .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), |
| 1359 | 1362 | }; |
| 1360 | 1363 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1361 | defer cache.manifest_dir.close(); | |
| 1364 | defer cache.manifest_dir.close(io); | |
| 1362 | 1365 | |
| 1363 | 1366 | { |
| 1364 | 1367 | var ch = cache.obtain(); |
| ... | ... | @@ -1424,7 +1427,7 @@ test "check that changing a file makes cache fail" { |
| 1424 | 1427 | .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), |
| 1425 | 1428 | }; |
| 1426 | 1429 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1427 | defer cache.manifest_dir.close(); | |
| 1430 | defer cache.manifest_dir.close(io); | |
| 1428 | 1431 | |
| 1429 | 1432 | { |
| 1430 | 1433 | var ch = cache.obtain(); |
| ... | ... | @@ -1484,7 +1487,7 @@ test "no file inputs" { |
| 1484 | 1487 | .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), |
| 1485 | 1488 | }; |
| 1486 | 1489 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1487 | defer cache.manifest_dir.close(); | |
| 1490 | defer cache.manifest_dir.close(io); | |
| 1488 | 1491 | |
| 1489 | 1492 | { |
| 1490 | 1493 | var man = cache.obtain(); |
| ... | ... | @@ -1543,7 +1546,7 @@ test "Manifest with files added after initial hash work" { |
| 1543 | 1546 | .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}), |
| 1544 | 1547 | }; |
| 1545 | 1548 | cache.addPrefix(.{ .path = null, .handle = tmp.dir }); |
| 1546 | defer cache.manifest_dir.close(); | |
| 1549 | defer cache.manifest_dir.close(io); | |
| 1547 | 1550 | |
| 1548 | 1551 | { |
| 1549 | 1552 | var ch = cache.obtain(); |
lib/std/Build/Cache/Directory.zig+6-4| ... | ... | @@ -1,7 +1,9 @@ |
| 1 | 1 | const Directory = @This(); |
| 2 | ||
| 2 | 3 | const std = @import("../../std.zig"); |
| 3 | const assert = std.debug.assert; | |
| 4 | const Io = std.Io; | |
| 4 | 5 | const fs = std.fs; |
| 6 | const assert = std.debug.assert; | |
| 5 | 7 | const fmt = std.fmt; |
| 6 | 8 | const Allocator = std.mem.Allocator; |
| 7 | 9 | |
| ... | ... | @@ -9,7 +11,7 @@ const Allocator = std.mem.Allocator; |
| 9 | 11 | /// directly, but it is needed when passing the directory to a child process. |
| 10 | 12 | /// `null` means cwd. |
| 11 | 13 | path: ?[]const u8, |
| 12 | handle: fs.Dir, | |
| 14 | handle: Io.Dir, | |
| 13 | 15 | |
| 14 | 16 | pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory { |
| 15 | 17 | return .{ |
| ... | ... | @@ -21,7 +23,7 @@ pub fn clone(d: Directory, arena: Allocator) Allocator.Error!Directory { |
| 21 | 23 | pub fn cwd() Directory { |
| 22 | 24 | return .{ |
| 23 | 25 | .path = null, |
| 24 | .handle = fs.cwd(), | |
| 26 | .handle = .cwd(), | |
| 25 | 27 | }; |
| 26 | 28 | } |
| 27 | 29 | |
| ... | ... | @@ -64,5 +66,5 @@ pub fn format(self: Directory, writer: *std.Io.Writer) std.Io.Writer.Error!void |
| 64 | 66 | } |
| 65 | 67 | |
| 66 | 68 | pub fn eql(self: Directory, other: Directory) bool { |
| 67 | return self.handle.fd == other.handle.fd; | |
| 69 | return self.handle.handle == other.handle.handle; | |
| 68 | 70 | } |
lib/std/Build/Cache/Path.zig+12-12| ... | ... | @@ -2,8 +2,8 @@ const Path = @This(); |
| 2 | 2 | |
| 3 | 3 | const std = @import("../../std.zig"); |
| 4 | 4 | const Io = std.Io; |
| 5 | const assert = std.debug.assert; | |
| 6 | 5 | const fs = std.fs; |
| 6 | const assert = std.debug.assert; | |
| 7 | 7 | const Allocator = std.mem.Allocator; |
| 8 | 8 | const Cache = std.Build.Cache; |
| 9 | 9 | |
| ... | ... | @@ -62,8 +62,8 @@ pub fn joinStringZ(p: Path, gpa: Allocator, sub_path: []const u8) Allocator.Erro |
| 62 | 62 | pub fn openFile( |
| 63 | 63 | p: Path, |
| 64 | 64 | sub_path: []const u8, |
| 65 | flags: fs.File.OpenFlags, | |
| 66 | ) !fs.File { | |
| 65 | flags: Io.File.OpenFlags, | |
| 66 | ) !Io.File { | |
| 67 | 67 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 68 | 68 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 69 | 69 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| ... | ... | @@ -76,8 +76,8 @@ pub fn openFile( |
| 76 | 76 | pub fn openDir( |
| 77 | 77 | p: Path, |
| 78 | 78 | 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 { | |
| 81 | 81 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 82 | 82 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 83 | 83 | break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{ |
| ... | ... | @@ -87,7 +87,7 @@ pub fn openDir( |
| 87 | 87 | return p.root_dir.handle.openDir(joined_path, args); |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: fs.Dir.OpenOptions) !fs.Dir { | |
| 90 | pub fn makeOpenPath(p: Path, sub_path: []const u8, opts: Io.Dir.OpenOptions) !Io.Dir { | |
| 91 | 91 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 92 | 92 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 93 | 93 | 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 |
| 97 | 97 | return p.root_dir.handle.makeOpenPath(joined_path, opts); |
| 98 | 98 | } |
| 99 | 99 | |
| 100 | pub fn statFile(p: Path, sub_path: []const u8) !fs.Dir.Stat { | |
| 100 | pub fn statFile(p: Path, sub_path: []const u8) !Io.Dir.Stat { | |
| 101 | 101 | var buf: [fs.max_path_bytes]u8 = undefined; |
| 102 | 102 | const joined_path = if (p.sub_path.len == 0) sub_path else p: { |
| 103 | 103 | 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 { |
| 110 | 110 | pub fn atomicFile( |
| 111 | 111 | p: Path, |
| 112 | 112 | sub_path: []const u8, |
| 113 | options: fs.Dir.AtomicFileOptions, | |
| 113 | options: Io.Dir.AtomicFileOptions, | |
| 114 | 114 | buf: *[fs.max_path_bytes]u8, |
| 115 | 115 | ) !fs.AtomicFile { |
| 116 | 116 | 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 { |
| 180 | 180 | } |
| 181 | 181 | |
| 182 | 182 | pub 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)) { | |
| 184 | 184 | try writer.writeAll(self.sub_path); |
| 185 | 185 | return; |
| 186 | 186 | } |
| ... | ... | @@ -225,9 +225,9 @@ pub const TableAdapter = struct { |
| 225 | 225 | |
| 226 | 226 | pub fn hash(self: TableAdapter, a: Cache.Path) u32 { |
| 227 | 227 | _ = 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)), | |
| 231 | 231 | else => @compileError("unimplemented hash function"), |
| 232 | 232 | }; |
| 233 | 233 | 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 |
| 510 | 510 | const io = b.graph.io; |
| 511 | 511 | const src_path = src_lazy_path.getPath3(b, s); |
| 512 | 512 | 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 }); | |
| 518 | 515 | } |
| 519 | 516 | |
| 520 | 517 | /// Wrapper around `std.fs.Dir.makePathStatus` that handles verbose and error output. |
| 521 | 518 | pub fn installDir(s: *Step, dest_path: []const u8) !std.fs.Dir.MakePathStatus { |
| 522 | 519 | const b = s.owner; |
| 523 | 520 | 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| | |
| 525 | 522 | return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); |
| 526 | }; | |
| 527 | 523 | } |
| 528 | 524 | |
| 529 | 525 | fn 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 @@ |
| 1 | const Compile = @This(); | |
| 1 | 2 | const builtin = @import("builtin"); |
| 3 | ||
| 2 | 4 | const std = @import("std"); |
| 5 | const Io = std.Io; | |
| 3 | 6 | const mem = std.mem; |
| 4 | 7 | const fs = std.fs; |
| 5 | 8 | const assert = std.debug.assert; |
| 6 | 9 | const panic = std.debug.panic; |
| 7 | 10 | const StringHashMap = std.StringHashMap; |
| 8 | 11 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 9 | const Allocator = mem.Allocator; | |
| 12 | const Allocator = std.mem.Allocator; | |
| 10 | 13 | const Step = std.Build.Step; |
| 11 | 14 | const LazyPath = std.Build.LazyPath; |
| 12 | 15 | const PkgConfigPkg = std.Build.PkgConfigPkg; |
| ... | ... | @@ -15,7 +18,6 @@ const RunError = std.Build.RunError; |
| 15 | 18 | const Module = std.Build.Module; |
| 16 | 19 | const InstallDir = std.Build.InstallDir; |
| 17 | 20 | const GeneratedFile = std.Build.GeneratedFile; |
| 18 | const Compile = @This(); | |
| 19 | 21 | const Path = std.Build.Cache.Path; |
| 20 | 22 | |
| 21 | 23 | pub const base_id: Step.Id = .compile; |
| ... | ... | @@ -1561,19 +1563,22 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1561 | 1563 | } |
| 1562 | 1564 | |
| 1563 | 1565 | // -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 | ||
| 1564 | 1569 | 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| { | |
| 1566 | 1571 | return step.fail("unable to open prefix directory '{s}': {s}", .{ |
| 1567 | 1572 | search_prefix, @errorName(err), |
| 1568 | 1573 | }); |
| 1569 | 1574 | }; |
| 1570 | defer prefix_dir.close(); | |
| 1575 | defer prefix_dir.close(io); | |
| 1571 | 1576 | |
| 1572 | 1577 | // Avoid passing -L and -I flags for nonexistent directories. |
| 1573 | 1578 | // This prevents a warning, that should probably be upgraded to an error in Zig's |
| 1574 | 1579 | // CLI parsing code, when the linker sees an -L directory that does not exist. |
| 1575 | 1580 | |
| 1576 | if (prefix_dir.access("lib", .{})) |_| { | |
| 1581 | if (prefix_dir.access(io, "lib", .{})) |_| { | |
| 1577 | 1582 | try zig_args.appendSlice(&.{ |
| 1578 | 1583 | "-L", b.pathJoin(&.{ search_prefix, "lib" }), |
| 1579 | 1584 | }); |
| ... | ... | @@ -1584,7 +1589,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1584 | 1589 | }), |
| 1585 | 1590 | } |
| 1586 | 1591 | |
| 1587 | if (prefix_dir.access("include", .{})) |_| { | |
| 1592 | if (prefix_dir.access(io, "include", .{})) |_| { | |
| 1588 | 1593 | try zig_args.appendSlice(&.{ |
| 1589 | 1594 | "-I", b.pathJoin(&.{ search_prefix, "include" }), |
| 1590 | 1595 | }); |
| ... | ... | @@ -1660,7 +1665,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { |
| 1660 | 1665 | args_length += arg.len + 1; // +1 to account for null terminator |
| 1661 | 1666 | } |
| 1662 | 1667 | if (args_length >= 30 * 1024) { |
| 1663 | try b.cache_root.handle.makePath("args"); | |
| 1668 | try b.cache_root.handle.makePath(io, "args"); | |
| 1664 | 1669 | |
| 1665 | 1670 | const args_to_escape = zig_args.items[2..]; |
| 1666 | 1671 | 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 { |
| 1693 | 1698 | _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}); |
| 1694 | 1699 | |
| 1695 | 1700 | 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, .{})) |_| { | |
| 1697 | 1702 | // The args file is already present from a previous run. |
| 1698 | 1703 | } else |err| switch (err) { |
| 1699 | 1704 | error.FileNotFound => { |
| 1700 | try b.cache_root.handle.makePath("tmp"); | |
| 1705 | try b.cache_root.handle.makePath(io, "tmp"); | |
| 1701 | 1706 | const rand_int = std.crypto.random.int(u64); |
| 1702 | 1707 | 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 { | |
| 1705 | 1710 | // It's fine if the temporary file can't be cleaned up. |
| 1706 | 1711 | }; |
| 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) { | |
| 1708 | 1713 | error.PathAlreadyExists => { |
| 1709 | 1714 | // The args file was created by another concurrent build process. |
| 1710 | 1715 | }, |
| ... | ... | @@ -1816,18 +1821,20 @@ pub fn doAtomicSymLinks( |
| 1816 | 1821 | filename_name_only: []const u8, |
| 1817 | 1822 | ) !void { |
| 1818 | 1823 | const b = step.owner; |
| 1824 | const io = b.graph.io; | |
| 1819 | 1825 | const out_dir = fs.path.dirname(output_path) orelse "."; |
| 1820 | 1826 | const out_basename = fs.path.basename(output_path); |
| 1821 | 1827 | // sym link for libfoo.so.1 to libfoo.so.1.2.3 |
| 1822 | 1828 | 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| { | |
| 1824 | 1831 | return step.fail("unable to symlink {s} -> {s}: {s}", .{ |
| 1825 | 1832 | major_only_path, out_basename, @errorName(err), |
| 1826 | 1833 | }); |
| 1827 | 1834 | }; |
| 1828 | 1835 | // sym link for libfoo.so to libfoo.so.1 |
| 1829 | 1836 | 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| { | |
| 1831 | 1838 | return step.fail("Unable to symlink {s} -> {s}: {s}", .{ |
| 1832 | 1839 | name_only_path, filename_major_only, @errorName(err), |
| 1833 | 1840 | }); |
lib/std/Build/Step/InstallDir.zig+3-4| ... | ... | @@ -58,16 +58,15 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir { |
| 58 | 58 | fn make(step: *Step, options: Step.MakeOptions) !void { |
| 59 | 59 | _ = options; |
| 60 | 60 | const b = step.owner; |
| 61 | const io = b.graph.io; | |
| 61 | 62 | const install_dir: *InstallDir = @fieldParentPtr("step", step); |
| 62 | 63 | step.clearWatchInputs(); |
| 63 | 64 | const arena = b.allocator; |
| 64 | 65 | const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir); |
| 65 | 66 | const src_dir_path = install_dir.options.source_dir.getPath3(b, step); |
| 66 | 67 | 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 }); | |
| 71 | 70 | }; |
| 72 | 71 | defer src_dir.close(); |
| 73 | 72 | 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 { |
| 441 | 441 | _ = make_options; |
| 442 | 442 | |
| 443 | 443 | const b = step.owner; |
| 444 | const io = b.graph.io; | |
| 444 | 445 | const options: *Options = @fieldParentPtr("step", step); |
| 445 | 446 | |
| 446 | 447 | for (options.args.items) |item| { |
| ... | ... | @@ -468,18 +469,15 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { |
| 468 | 469 | |
| 469 | 470 | // Optimize for the hot path. Stat the file, and if it already exists, |
| 470 | 471 | // cache hit. |
| 471 | if (b.cache_root.handle.access(sub_path, .{})) |_| { | |
| 472 | if (b.cache_root.handle.access(io, sub_path, .{})) |_| { | |
| 472 | 473 | // This is the hot path, success. |
| 473 | 474 | step.result_cached = true; |
| 474 | 475 | return; |
| 475 | 476 | } else |outer_err| switch (outer_err) { |
| 476 | 477 | error.FileNotFound => { |
| 477 | 478 | 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 }); | |
| 483 | 481 | |
| 484 | 482 | const rand_int = std.crypto.random.int(u64); |
| 485 | 483 | const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ |
| ... | ... | @@ -487,40 +485,40 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { |
| 487 | 485 | basename; |
| 488 | 486 | const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; |
| 489 | 487 | |
| 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, | |
| 493 | 491 | }); |
| 494 | 492 | }; |
| 495 | 493 | |
| 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, | |
| 499 | 497 | }); |
| 500 | 498 | }; |
| 501 | 499 | |
| 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) { | |
| 503 | 501 | error.PathAlreadyExists => { |
| 504 | 502 | // 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, | |
| 508 | 506 | }); |
| 509 | 507 | }; |
| 510 | 508 | step.result_cached = true; |
| 511 | 509 | return; |
| 512 | 510 | }, |
| 513 | 511 | 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, | |
| 518 | 516 | }); |
| 519 | 517 | }, |
| 520 | 518 | }; |
| 521 | 519 | }, |
| 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, | |
| 524 | 522 | }), |
| 525 | 523 | } |
| 526 | 524 | } |
lib/std/Io.zig+19-8| ... | ... | @@ -662,16 +662,27 @@ pub const VTable = struct { |
| 662 | 662 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 663 | 663 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 664 | 664 | |
| 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, | |
| 668 | 668 | 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, | |
| 674 | 674 | 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 | ||
| 675 | 686 | fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, |
| 676 | 687 | fileClose: *const fn (?*anyopaque, File) void, |
| 677 | 688 | 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; |
| 6 | 6 | const std = @import("../std.zig"); |
| 7 | 7 | const Io = std.Io; |
| 8 | 8 | const File = Io.File; |
| 9 | const assert = std.debug.assert; | |
| 10 | const Allocator = std.mem.Allocator; | |
| 9 | 11 | |
| 10 | 12 | handle: Handle, |
| 11 | 13 | |
| 12 | 14 | pub const Mode = Io.File.Mode; |
| 13 | 15 | pub const default_mode: Mode = 0o755; |
| 14 | 16 | |
| 17 | pub const Entry = struct { | |
| 18 | name: []const u8, | |
| 19 | kind: File.Kind, | |
| 20 | inode: File.INode, | |
| 21 | }; | |
| 22 | ||
| 15 | 23 | /// Returns a handle to the current working directory. |
| 16 | 24 | /// |
| 17 | 25 | /// It is not opened with iteration capability. Iterating over the result is |
| ... | ... | @@ -20,6 +28,8 @@ pub const default_mode: Mode = 0o755; |
| 20 | 28 | /// Closing the returned `Dir` is checked illegal behavior. |
| 21 | 29 | /// |
| 22 | 30 | /// On POSIX targets, this function is comptime-callable. |
| 31 | /// | |
| 32 | /// On WASI, the value this returns is application-configurable. | |
| 23 | 33 | pub fn cwd() Dir { |
| 24 | 34 | return switch (native_os) { |
| 25 | 35 | .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle }, |
| ... | ... | @@ -28,6 +38,270 @@ pub fn cwd() Dir { |
| 28 | 38 | }; |
| 29 | 39 | } |
| 30 | 40 | |
| 41 | pub 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 | ||
| 76 | pub 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 | ||
| 116 | pub 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). | |
| 123 | pub fn iterateAssumeFirstIteration(dir: Dir) Iterator { | |
| 124 | return .init(dir, .reading); | |
| 125 | } | |
| 126 | ||
| 127 | pub 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`. | |
| 227 | pub 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 | ||
| 242 | pub 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`. | |
| 301 | pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { | |
| 302 | return .{ .inner = try walkSelectively(dir, allocator) }; | |
| 303 | } | |
| 304 | ||
| 31 | 305 | pub const Handle = std.posix.fd_t; |
| 32 | 306 | |
| 33 | 307 | pub const PathNameError = error{ |
| ... | ... | @@ -145,7 +419,7 @@ pub const WriteFileOptions = struct { |
| 145 | 419 | flags: File.CreateFlags = .{}, |
| 146 | 420 | }; |
| 147 | 421 | |
| 148 | pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable; | |
| 422 | pub const WriteFileError = File.WriteError || File.OpenError; | |
| 149 | 423 | |
| 150 | 424 | /// Writes content to the file system, using the file creation flags provided. |
| 151 | 425 | pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void { |
| ... | ... | @@ -179,7 +453,7 @@ pub fn updateFile( |
| 179 | 453 | dest_dir: Dir, |
| 180 | 454 | /// If directories in this path do not exist, they are created. |
| 181 | 455 | dest_path: []const u8, |
| 182 | options: std.fs.Dir.CopyFileOptions, | |
| 456 | options: CopyFileOptions, | |
| 183 | 457 | ) !PrevStatus { |
| 184 | 458 | var src_file = try source_dir.openFile(io, source_path, .{}); |
| 185 | 459 | defer src_file.close(io); |
| ... | ... | @@ -367,3 +641,929 @@ pub const StatPathOptions = struct { |
| 367 | 641 | pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat { |
| 368 | 642 | return io.vtable.dirStatPath(io.userdata, dir, sub_path, options); |
| 369 | 643 | } |
| 644 | ||
| 645 | pub 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`. | |
| 698 | pub 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 | ||
| 702 | pub const RealPathAllocError = RealPathError || Allocator.Error; | |
| 703 | ||
| 704 | /// Same as `Dir.realpath` except caller must free the returned memory. | |
| 705 | /// See also `Dir.realpath`. | |
| 706 | pub 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 | ||
| 717 | pub 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. | |
| 748 | pub fn deleteFile(dir: Dir, io: Io, sub_path: []const u8) DeleteFileError!void { | |
| 749 | return io.vtable.dirDeleteFile(io.userdata, dir, sub_path); | |
| 750 | } | |
| 751 | ||
| 752 | pub 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. | |
| 779 | pub fn deleteDir(dir: Dir, io: Io, sub_path: []const u8) DeleteDirError!void { | |
| 780 | return io.vtable.dirDeleteDir(io.userdata, dir, sub_path); | |
| 781 | } | |
| 782 | ||
| 783 | pub 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. | |
| 831 | pub 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. | |
| 846 | pub const SymLinkFlags = struct { | |
| 847 | is_directory: bool = false, | |
| 848 | }; | |
| 849 | ||
| 850 | pub 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. | |
| 881 | pub 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. | |
| 897 | pub 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 | ||
| 936 | pub 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. | |
| 974 | pub 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 | ||
| 978 | pub 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. | |
| 992 | pub 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`. | |
| 1012 | pub 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 | ||
| 1035 | pub 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. | |
| 1072 | pub 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. | |
| 1271 | pub fn deleteTreeMinStackSize(dir: Dir, io: Io, sub_path: []const u8) DeleteTreeError!void { | |
| 1272 | return dir.deleteTreeMinStackSizeWithKindHint(io, sub_path, .file); | |
| 1273 | } | |
| 1274 | ||
| 1275 | fn 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. | |
| 1390 | fn 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 | ||
| 1455 | pub const CopyFileOptions = struct { | |
| 1456 | /// When this is `null` the mode is copied from the source file. | |
| 1457 | override_mode: ?File.Mode = null, | |
| 1458 | }; | |
| 1459 | ||
| 1460 | pub 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. | |
| 1477 | pub 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 | ||
| 1510 | pub 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. | |
| 1526 | pub 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 | ||
| 1539 | pub 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`. | |
| 1547 | pub fn setMode(dir: Dir, io: Io, new_mode: File.Mode) SetModeError!void { | |
| 1548 | return io.vtable.dirSetMode(io.userdata, dir, new_mode); | |
| 1549 | } | |
| 1550 | ||
| 1551 | pub 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. | |
| 1560 | pub 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 | ||
| 1564 | pub const SetPermissionsError = File.SetPermissionsError; | |
| 1565 | pub const Permissions = File.Permissions; | |
| 1566 | ||
| 1567 | pub 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; |
| 7 | 7 | const std = @import("../std.zig"); |
| 8 | 8 | const Io = std.Io; |
| 9 | 9 | const assert = std.debug.assert; |
| 10 | const Dir = std.Io.Dir; | |
| 10 | 11 | |
| 11 | 12 | handle: Handle, |
| 12 | 13 | |
| 13 | 14 | pub const Handle = std.posix.fd_t; |
| 14 | 15 | pub const Mode = std.posix.mode_t; |
| 15 | 16 | pub const INode = std.posix.ino_t; |
| 17 | pub const Uid = std.posix.uid_t; | |
| 18 | pub 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. | |
| 26 | pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; | |
| 16 | 27 | |
| 17 | 28 | pub const Kind = enum { |
| 18 | 29 | block_device, |
| ... | ... | @@ -92,6 +103,11 @@ pub const Lock = enum { |
| 92 | 103 | exclusive, |
| 93 | 104 | }; |
| 94 | 105 | |
| 106 | pub const LockError = error{ | |
| 107 | SystemResources, | |
| 108 | FileLocksNotSupported, | |
| 109 | } || Io.UnexpectedError; | |
| 110 | ||
| 95 | 111 | pub const OpenFlags = struct { |
| 96 | 112 | mode: OpenMode = .read_only, |
| 97 | 113 | |
| ... | ... | @@ -141,7 +157,53 @@ pub const OpenFlags = struct { |
| 141 | 157 | } |
| 142 | 158 | }; |
| 143 | 159 | |
| 144 | pub const CreateFlags = std.fs.File.CreateFlags; | |
| 160 | pub 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 | }; | |
| 145 | 207 | |
| 146 | 208 | pub const OpenError = error{ |
| 147 | 209 | SharingViolation, |
| ... | ... | @@ -231,6 +293,17 @@ pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) Wr |
| 231 | 293 | return io.vtable.fileWritePositional(io.userdata, file, buffer, offset); |
| 232 | 294 | } |
| 233 | 295 | |
| 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. | |
| 234 | 307 | pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File { |
| 235 | 308 | assert(std.fs.path.isAbsolute(absolute_path)); |
| 236 | 309 | return Io.Dir.cwd().openFile(io, absolute_path, flags); |
| ... | ... | @@ -364,11 +437,6 @@ pub const Reader = struct { |
| 364 | 437 | }; |
| 365 | 438 | } |
| 366 | 439 | |
| 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 | ||
| 372 | 440 | pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader { |
| 373 | 441 | return .{ |
| 374 | 442 | .io = io, |
| ... | ... | @@ -652,3 +720,113 @@ pub const Reader = struct { |
| 652 | 720 | return size - logicalPos(r) == 0; |
| 653 | 721 | } |
| 654 | 722 | }; |
| 723 | ||
| 724 | pub 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 | ||
| 814 | pub const SetModeError = error{ | |
| 815 | AccessDenied, | |
| 816 | PermissionDenied, | |
| 817 | InputOutput, | |
| 818 | SymLinkLoop, | |
| 819 | FileNotFound, | |
| 820 | SystemResources, | |
| 821 | ReadOnlyFileSystem, | |
| 822 | } || Io.Cancelable || Io.UnexpectedError; | |
| 823 | ||
| 824 | pub 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 { |
| 657 | 657 | .dirMakeOpenPath = dirMakeOpenPath, |
| 658 | 658 | .dirStat = dirStat, |
| 659 | 659 | .dirStatPath = dirStatPath, |
| 660 | .fileStat = fileStat, | |
| 661 | 660 | .dirAccess = dirAccess, |
| 662 | 661 | .dirCreateFile = dirCreateFile, |
| 663 | 662 | .dirOpenFile = dirOpenFile, |
| 664 | 663 | .dirOpenDir = dirOpenDir, |
| 665 | 664 | .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, | |
| 666 | 676 | .fileClose = fileClose, |
| 667 | 677 | .fileWriteStreaming = fileWriteStreaming, |
| 668 | 678 | .fileWritePositional = fileWritePositional, |
| ... | ... | @@ -753,12 +763,22 @@ pub fn ioBasic(t: *Threaded) Io { |
| 753 | 763 | .dirMakeOpenPath = dirMakeOpenPath, |
| 754 | 764 | .dirStat = dirStat, |
| 755 | 765 | .dirStatPath = dirStatPath, |
| 756 | .fileStat = fileStat, | |
| 757 | 766 | .dirAccess = dirAccess, |
| 758 | 767 | .dirCreateFile = dirCreateFile, |
| 759 | 768 | .dirOpenFile = dirOpenFile, |
| 760 | 769 | .dirOpenDir = dirOpenDir, |
| 761 | 770 | .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, | |
| 762 | 782 | .fileClose = fileClose, |
| 763 | 783 | .fileWriteStreaming = fileWriteStreaming, |
| 764 | 784 | .fileWritePositional = fileWritePositional, |
| ... | ... | @@ -3093,50 +3113,69 @@ fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void { |
| 3093 | 3113 | posix.close(dir.handle); |
| 3094 | 3114 | } |
| 3095 | 3115 | |
| 3096 | fn 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); | |
| 3116 | const dirRealPath = switch (native_os) { | |
| 3117 | .windows => dirRealPathWindows, | |
| 3118 | else => dirRealPathPosix, | |
| 3119 | }; | |
| 3120 | ||
| 3121 | fn dirRealPathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, out_buffer: []u8) Io.Dir.RealPathError!usize { | |
| 3103 | 3122 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3123 | const w = windows; | |
| 3104 | 3124 | const current_thread = Thread.getCurrent(t); |
| 3105 | const wasi = std.os.wasi; | |
| 3106 | 3125 | |
| 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; | |
| 3111 | 3145 | }; |
| 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 | ||
| 3157 | fn 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; | |
| 3129 | 3171 | |
| 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; | |
| 3134 | 3172 | 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)) { | |
| 3137 | 3176 | .SUCCESS => { |
| 3138 | 3177 | current_thread.endSyscall(); |
| 3139 | return .{ .handle = fd }; | |
| 3178 | break @intCast(rc); | |
| 3140 | 3179 | }, |
| 3141 | 3180 | .INTR => { |
| 3142 | 3181 | try current_thread.checkCancel(); |
| ... | ... | @@ -3150,94 +3189,183 @@ fn dirOpenDirWasi( |
| 3150 | 3189 | .INVAL => return error.BadPathName, |
| 3151 | 3190 | .BADF => |err| return errnoBug(err), // File descriptor used after closed. |
| 3152 | 3191 | .ACCES => return error.AccessDenied, |
| 3192 | .FBIG => return error.FileTooBig, | |
| 3193 | .OVERFLOW => return error.FileTooBig, | |
| 3194 | .ISDIR => return error.IsDir, | |
| 3153 | 3195 | .LOOP => return error.SymLinkLoop, |
| 3154 | 3196 | .MFILE => return error.ProcessFdQuotaExceeded, |
| 3155 | 3197 | .NAMETOOLONG => return error.NameTooLong, |
| 3156 | 3198 | .NFILE => return error.SystemFdQuotaExceeded, |
| 3157 | 3199 | .NODEV => return error.NoDevice, |
| 3158 | 3200 | .NOENT => return error.FileNotFound, |
| 3201 | .SRCH => return error.ProcessNotFound, | |
| 3159 | 3202 | .NOMEM => return error.SystemResources, |
| 3203 | .NOSPC => return error.NoSpaceLeft, | |
| 3160 | 3204 | .NOTDIR => return error.NotDir, |
| 3161 | 3205 | .PERM => return error.PermissionDenied, |
| 3206 | .EXIST => return error.PathAlreadyExists, | |
| 3162 | 3207 | .BUSY => return error.DeviceBusy, |
| 3163 | .NOTCAPABLE => return error.AccessDenied, | |
| 3208 | .NXIO => return error.NoDevice, | |
| 3164 | 3209 | .ILSEQ => return error.BadPathName, |
| 3165 | 3210 | else => |err| return posix.unexpectedErrno(err), |
| 3166 | 3211 | } |
| 3167 | 3212 | }, |
| 3168 | 3213 | } |
| 3169 | } | |
| 3170 | } | |
| 3214 | }; | |
| 3215 | errdefer posix.close(fd); | |
| 3171 | 3216 | |
| 3172 | fn 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; | |
| 3176 | 3346 | } |
| 3177 | 3347 | |
| 3178 | const fileReadStreaming = switch (native_os) { | |
| 3179 | .windows => fileReadStreamingWindows, | |
| 3180 | else => fileReadStreamingPosix, | |
| 3348 | const dirDeleteFile = switch (native_os) { | |
| 3349 | .windows => dirDeleteFileWindows, | |
| 3350 | .wasi => dirDeleteFileWasi, | |
| 3351 | else => dirDeleteFilePosix, | |
| 3181 | 3352 | }; |
| 3182 | 3353 | |
| 3183 | fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { | |
| 3354 | fn dirDeleteFileWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { | |
| 3355 | return dirDeleteWindows(userdata, dir, sub_path, false); | |
| 3356 | } | |
| 3357 | ||
| 3358 | fn 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); | |
| 3184 | 3360 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3185 | 3361 | 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 | ||
| 3234 | 3362 | try current_thread.beginSyscall(); |
| 3235 | 3363 | 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) { | |
| 3238 | 3366 | .SUCCESS => { |
| 3239 | 3367 | current_thread.endSyscall(); |
| 3240 | return @intCast(rc); | |
| 3368 | return; | |
| 3241 | 3369 | }, |
| 3242 | 3370 | .INTR => { |
| 3243 | 3371 | try current_thread.checkCancel(); |
| ... | ... | @@ -3247,21 +3375,23 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io |
| 3247 | 3375 | else => |e| { |
| 3248 | 3376 | current_thread.endSyscall(); |
| 3249 | 3377 | switch (e) { |
| 3250 | .INVAL => |err| return errnoBug(err), | |
| 3378 | .ACCES => return error.AccessDenied, | |
| 3379 | .PERM => return error.PermissionDenied, | |
| 3380 | .BUSY => return error.FileBusy, | |
| 3251 | 3381 | .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, | |
| 3259 | 3383 | .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, | |
| 3261 | 3388 | .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. | |
| 3265 | 3395 | else => |err| return posix.unexpectedErrno(err), |
| 3266 | 3396 | } |
| 3267 | 3397 | }, |
| ... | ... | @@ -3269,40 +3399,1259 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io |
| 3269 | 3399 | } |
| 3270 | 3400 | } |
| 3271 | 3401 | |
| 3272 | fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { | |
| 3402 | fn dirDeleteFilePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteFileError!void { | |
| 3273 | 3403 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 3274 | 3404 | const current_thread = Thread.getCurrent(t); |
| 3275 | 3405 | |
| 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); | |
| 3281 | 3408 | |
| 3409 | try current_thread.beginSyscall(); | |
| 3282 | 3410 | 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 | ||
| 3301 | fn 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 | ||
| 3481 | const dirDeleteDir = switch (native_os) { | |
| 3482 | .windows => dirDeleteDirWindows, | |
| 3483 | .wasi => dirDeleteDirWasi, | |
| 3484 | else => dirDeleteDirPosix, | |
| 3485 | }; | |
| 3486 | ||
| 3487 | fn dirDeleteDirWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.DeleteDirError!void { | |
| 3488 | return dirDeleteWindows(userdata, dir, sub_path, true); | |
| 3489 | } | |
| 3490 | ||
| 3491 | fn 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 | ||
| 3632 | fn 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 | ||
| 3678 | fn 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 | ||
| 3724 | const dirRename = switch (native_os) { | |
| 3725 | .windows => dirRenameWindows, | |
| 3726 | .wasi => dirRenameWasi, | |
| 3727 | else => dirRenamePosix, | |
| 3728 | }; | |
| 3729 | ||
| 3730 | fn 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 | ||
| 3853 | fn 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 | ||
| 3904 | fn 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 | ||
| 3958 | const dirSymLink = switch (native_os) { | |
| 3959 | .windows => dirSymLinkWindows, | |
| 3960 | .wasi => dirSymLinkWasi, | |
| 3961 | else => dirSymLinkPosix, | |
| 3962 | }; | |
| 3963 | ||
| 3964 | fn 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 | ||
| 4079 | fn 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 | ||
| 4127 | fn 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 | ||
| 4178 | const dirReadLink = switch (native_os) { | |
| 4179 | .windows => dirReadLinkWindows, | |
| 4180 | .wasi => dirReadLinkWasi, | |
| 4181 | else => dirReadLinkPosix, | |
| 4182 | }; | |
| 4183 | ||
| 4184 | fn 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 | ||
| 4242 | fn 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 | ||
| 4282 | fn 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 | ||
| 4323 | const dirSetMode = switch (native_os) { | |
| 4324 | .windows => dirSetModeUnsupported, | |
| 4325 | else => dirSetModePosix, | |
| 4326 | }; | |
| 4327 | ||
| 4328 | fn 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 | ||
| 4335 | fn 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 | ||
| 4369 | const dirSetOwner = switch (native_os) { | |
| 4370 | .windows => dirSetOwnerUnsupported, | |
| 4371 | else => dirSetOwnerPosix, | |
| 4372 | }; | |
| 4373 | ||
| 4374 | fn 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 | ||
| 4382 | fn 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 | ||
| 4418 | const dirSetPermissions = switch (native_os) { | |
| 4419 | .windows => dirSetPermissionsWindows, | |
| 4420 | else => dirSetPermissionsPosix, | |
| 4421 | }; | |
| 4422 | ||
| 4423 | fn 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 | ||
| 4434 | fn 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 | ||
| 4445 | fn 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 | ||
| 4521 | fn fileClose(userdata: ?*anyopaque, file: Io.File) void { | |
| 4522 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 4523 | _ = t; | |
| 4524 | posix.close(file.handle); | |
| 4525 | } | |
| 4526 | ||
| 4527 | const fileReadStreaming = switch (native_os) { | |
| 4528 | .windows => fileReadStreamingWindows, | |
| 4529 | else => fileReadStreamingPosix, | |
| 4530 | }; | |
| 4531 | ||
| 4532 | fn 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 | ||
| 4621 | fn 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 | ||
| 4650 | fn 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"); | |
| 3306 | 4655 | |
| 3307 | 4656 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 3308 | 4657 | var i: usize = 0; |
| ... | ... | @@ -3445,10 +4794,100 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, |
| 3445 | 4794 | |
| 3446 | 4795 | fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void { |
| 3447 | 4796 | 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 | } | |
| 3452 | 4891 | } |
| 3453 | 4892 | |
| 3454 | 4893 | fn 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 { |
| 1104 | 1104 | return ptr; |
| 1105 | 1105 | } |
| 1106 | 1106 | |
| 1107 | fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void { | |
| 1107 | fn 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 { | |
| 1108 | 1115 | const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { |
| 1109 | 1116 | error.MissingDebugInfo, |
| 1110 | 1117 | error.UnsupportedDebugInfo, |
| ... | ... | @@ -1125,6 +1132,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: * |
| 1125 | 1132 | }; |
| 1126 | 1133 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); |
| 1127 | 1134 | return printLineInfo( |
| 1135 | io, | |
| 1128 | 1136 | writer, |
| 1129 | 1137 | symbol.source_location, |
| 1130 | 1138 | address, |
| ... | ... | @@ -1134,6 +1142,7 @@ fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: * |
| 1134 | 1142 | ); |
| 1135 | 1143 | } |
| 1136 | 1144 | fn printLineInfo( |
| 1145 | io: Io, | |
| 1137 | 1146 | writer: *Writer, |
| 1138 | 1147 | source_location: ?SourceLocation, |
| 1139 | 1148 | address: usize, |
| ... | ... | @@ -1159,7 +1168,7 @@ fn printLineInfo( |
| 1159 | 1168 | |
| 1160 | 1169 | // Show the matching source code line if possible |
| 1161 | 1170 | if (source_location) |sl| { |
| 1162 | if (printLineFromFile(writer, sl)) { | |
| 1171 | if (printLineFromFile(io, writer, sl)) { | |
| 1163 | 1172 | if (sl.column > 0) { |
| 1164 | 1173 | // The caret already takes one char |
| 1165 | 1174 | const space_needed = @as(usize, @intCast(sl.column - 1)); |
| ... | ... | @@ -1177,16 +1186,17 @@ fn printLineInfo( |
| 1177 | 1186 | } |
| 1178 | 1187 | } |
| 1179 | 1188 | } |
| 1180 | fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { | |
| 1189 | fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void { | |
| 1181 | 1190 | // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`. |
| 1182 | 1191 | 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); | |
| 1184 | 1193 | } |
| 1185 | 1194 | |
| 1186 | 1195 | // Need this to always block even in async I/O mode, because this could potentially |
| 1187 | 1196 | // 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); | |
| 1190 | 1200 | // TODO fstat and make sure that the file has the correct size |
| 1191 | 1201 | |
| 1192 | 1202 | var buf: [4096]u8 = undefined; |
| ... | ... | @@ -1237,11 +1247,13 @@ fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void { |
| 1237 | 1247 | } |
| 1238 | 1248 | |
| 1239 | 1249 | test 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); | |
| 1241 | 1254 | defer aw.deinit(); |
| 1242 | 1255 | const output_stream = &aw.writer; |
| 1243 | 1256 | |
| 1244 | const allocator = std.testing.allocator; | |
| 1245 | 1257 | const join = std.fs.path.join; |
| 1246 | 1258 | const expectError = std.testing.expectError; |
| 1247 | 1259 | const expectEqualStrings = std.testing.expectEqualStrings; |
| ... | ... | @@ -1249,24 +1261,24 @@ test printLineFromFile { |
| 1249 | 1261 | var test_dir = std.testing.tmpDir(.{}); |
| 1250 | 1262 | defer test_dir.cleanup(); |
| 1251 | 1263 | // 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); | |
| 1254 | 1266 | |
| 1255 | 1267 | // Cases |
| 1256 | 1268 | { |
| 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); | |
| 1259 | 1271 | try test_dir.dir.writeFile(.{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" }); |
| 1260 | 1272 | |
| 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 })); | |
| 1262 | 1274 | |
| 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 }); | |
| 1264 | 1276 | try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written()); |
| 1265 | 1277 | aw.clearRetainingCapacity(); |
| 1266 | 1278 | } |
| 1267 | 1279 | { |
| 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); | |
| 1270 | 1282 | try test_dir.dir.writeFile(.{ |
| 1271 | 1283 | .sub_path = "three_lines.zig", |
| 1272 | 1284 | .data = |
| ... | ... | @@ -1276,19 +1288,19 @@ test printLineFromFile { |
| 1276 | 1288 | , |
| 1277 | 1289 | }); |
| 1278 | 1290 | |
| 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 }); | |
| 1280 | 1292 | try expectEqualStrings("1\n", aw.written()); |
| 1281 | 1293 | aw.clearRetainingCapacity(); |
| 1282 | 1294 | |
| 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 }); | |
| 1284 | 1296 | try expectEqualStrings("3\n", aw.written()); |
| 1285 | 1297 | aw.clearRetainingCapacity(); |
| 1286 | 1298 | } |
| 1287 | 1299 | { |
| 1288 | 1300 | const file = try test_dir.dir.createFile("line_overlaps_page_boundary.zig", .{}); |
| 1289 | 1301 | 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); | |
| 1292 | 1304 | |
| 1293 | 1305 | const overlap = 10; |
| 1294 | 1306 | var buf: [16]u8 = undefined; |
| ... | ... | @@ -1299,55 +1311,55 @@ test printLineFromFile { |
| 1299 | 1311 | try writer.splatByteAll('a', overlap); |
| 1300 | 1312 | try writer.flush(); |
| 1301 | 1313 | |
| 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 }); | |
| 1303 | 1315 | try expectEqualStrings(("a" ** overlap) ++ "\n", aw.written()); |
| 1304 | 1316 | aw.clearRetainingCapacity(); |
| 1305 | 1317 | } |
| 1306 | 1318 | { |
| 1307 | 1319 | const file = try test_dir.dir.createFile("file_ends_on_page_boundary.zig", .{}); |
| 1308 | 1320 | 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); | |
| 1311 | 1323 | |
| 1312 | 1324 | var file_writer = file.writer(&.{}); |
| 1313 | 1325 | const writer = &file_writer.interface; |
| 1314 | 1326 | try writer.splatByteAll('a', std.heap.page_size_max); |
| 1315 | 1327 | |
| 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 }); | |
| 1317 | 1329 | try expectEqualStrings(("a" ** std.heap.page_size_max) ++ "\n", aw.written()); |
| 1318 | 1330 | aw.clearRetainingCapacity(); |
| 1319 | 1331 | } |
| 1320 | 1332 | { |
| 1321 | 1333 | const file = try test_dir.dir.createFile("very_long_first_line_spanning_multiple_pages.zig", .{}); |
| 1322 | 1334 | 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); | |
| 1325 | 1337 | |
| 1326 | 1338 | var file_writer = file.writer(&.{}); |
| 1327 | 1339 | const writer = &file_writer.interface; |
| 1328 | 1340 | try writer.splatByteAll('a', 3 * std.heap.page_size_max); |
| 1329 | 1341 | |
| 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 })); | |
| 1331 | 1343 | |
| 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 }); | |
| 1333 | 1345 | try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "\n", aw.written()); |
| 1334 | 1346 | aw.clearRetainingCapacity(); |
| 1335 | 1347 | |
| 1336 | 1348 | try writer.writeAll("a\na"); |
| 1337 | 1349 | |
| 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 }); | |
| 1339 | 1351 | try expectEqualStrings(("a" ** (3 * std.heap.page_size_max)) ++ "a\n", aw.written()); |
| 1340 | 1352 | aw.clearRetainingCapacity(); |
| 1341 | 1353 | |
| 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 }); | |
| 1343 | 1355 | try expectEqualStrings("a\n", aw.written()); |
| 1344 | 1356 | aw.clearRetainingCapacity(); |
| 1345 | 1357 | } |
| 1346 | 1358 | { |
| 1347 | 1359 | const file = try test_dir.dir.createFile("file_of_newlines.zig", .{}); |
| 1348 | 1360 | 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); | |
| 1351 | 1363 | |
| 1352 | 1364 | var file_writer = file.writer(&.{}); |
| 1353 | 1365 | const writer = &file_writer.interface; |
| ... | ... | @@ -1355,11 +1367,11 @@ test printLineFromFile { |
| 1355 | 1367 | try writer.splatByteAll('\n', real_file_start); |
| 1356 | 1368 | try writer.writeAll("abc\ndef"); |
| 1357 | 1369 | |
| 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 }); | |
| 1359 | 1371 | try expectEqualStrings("abc\n", aw.written()); |
| 1360 | 1372 | aw.clearRetainingCapacity(); |
| 1361 | 1373 | |
| 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 }); | |
| 1363 | 1375 | try expectEqualStrings("def\n", aw.written()); |
| 1364 | 1376 | aw.clearRetainingCapacity(); |
| 1365 | 1377 | } |
lib/std/fs.zig+14-55| ... | ... | @@ -15,9 +15,13 @@ const windows = std.os.windows; |
| 15 | 15 | |
| 16 | 16 | const is_darwin = native_os.isDarwin(); |
| 17 | 17 | |
| 18 | pub const AtomicFile = @import("fs/AtomicFile.zig"); | |
| 19 | pub const Dir = @import("fs/Dir.zig"); | |
| 20 | pub const File = @import("fs/File.zig"); | |
| 18 | /// Deprecated. | |
| 19 | pub const AtomicFile = std.Io.File.Atomic; | |
| 20 | /// Deprecated. | |
| 21 | pub const Dir = std.Io.Dir; | |
| 22 | /// Deprecated. | |
| 23 | pub const File = std.Io.File; | |
| 24 | ||
| 21 | 25 | pub const path = @import("fs/path.zig"); |
| 22 | 26 | |
| 23 | 27 | pub const has_executable_bit = switch (native_os) { |
| ... | ... | @@ -153,42 +157,9 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void { |
| 153 | 157 | return posix.rmdirZ(dir_path); |
| 154 | 158 | } |
| 155 | 159 | |
| 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. | |
| 160 | pub 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. | |
| 167 | pub 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` | |
| 174 | pub 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. | |
| 179 | pub 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 | ||
| 183 | 160 | /// Deprecated in favor of `Io.Dir.cwd`. |
| 184 | pub 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 | } | |
| 161 | pub fn cwd() Io.Dir { | |
| 162 | return .cwd(); | |
| 192 | 163 | } |
| 193 | 164 | |
| 194 | 165 | pub fn defaultWasiCwd() std.os.wasi.fd_t { |
| ... | ... | @@ -209,23 +180,11 @@ pub fn openDirAbsolute(absolute_path: []const u8, flags: Dir.OpenOptions) File.O |
| 209 | 180 | return cwd().openDir(absolute_path, flags); |
| 210 | 181 | } |
| 211 | 182 | |
| 212 | /// Same as `openDirAbsolute` but the path parameter is null-terminated. | |
| 213 | pub 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. | |
| 226 | pub 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`. | |
| 184 | pub 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); | |
| 229 | 188 | } |
| 230 | 189 | |
| 231 | 190 | /// Test accessing `path`. |
lib/std/fs/AtomicFile.zig deleted-94| ... | ... | @@ -1,94 +0,0 @@ |
| 1 | const AtomicFile = @This(); | |
| 2 | const std = @import("../std.zig"); | |
| 3 | const File = std.fs.File; | |
| 4 | const Dir = std.fs.Dir; | |
| 5 | const fs = std.fs; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const posix = std.posix; | |
| 8 | ||
| 9 | file_writer: File.Writer, | |
| 10 | random_integer: u64, | |
| 11 | dest_basename: []const u8, | |
| 12 | file_open: bool, | |
| 13 | file_exists: bool, | |
| 14 | close_dir_on_deinit: bool, | |
| 15 | dir: Dir, | |
| 16 | ||
| 17 | pub const InitError = File.OpenError; | |
| 18 | ||
| 19 | /// Note that the `Dir.atomicFile` API may be more handy than this lower-level function. | |
| 20 | pub 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(). | |
| 47 | pub 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 | ||
| 63 | pub const FlushError = File.WriteError; | |
| 64 | ||
| 65 | pub 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 | ||
| 71 | pub 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). | |
| 77 | pub 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 | ||
| 88 | pub const FinishError = FlushError || RenameIntoPlaceError; | |
| 89 | ||
| 90 | /// Combination of `flush` followed by `renameIntoPlace`. | |
| 91 | pub 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`. | |
| 2 | const Dir = @This(); | |
| 3 | ||
| 4 | const builtin = @import("builtin"); | |
| 5 | const native_os = builtin.os.tag; | |
| 6 | ||
| 7 | const std = @import("../std.zig"); | |
| 8 | const Io = std.Io; | |
| 9 | const File = std.fs.File; | |
| 10 | const AtomicFile = std.fs.AtomicFile; | |
| 11 | const base64_encoder = fs.base64_encoder; | |
| 12 | const posix = std.posix; | |
| 13 | const mem = std.mem; | |
| 14 | const path = fs.path; | |
| 15 | const fs = std.fs; | |
| 16 | const Allocator = std.mem.Allocator; | |
| 17 | const assert = std.debug.assert; | |
| 18 | const linux = std.os.linux; | |
| 19 | const windows = std.os.windows; | |
| 20 | const have_flock = @TypeOf(posix.system.flock) != void; | |
| 21 | ||
| 22 | fd: Handle, | |
| 23 | ||
| 24 | pub const Handle = posix.fd_t; | |
| 25 | ||
| 26 | pub const default_mode = 0o755; | |
| 27 | ||
| 28 | pub const Entry = struct { | |
| 29 | name: []const u8, | |
| 30 | kind: Kind, | |
| 31 | ||
| 32 | pub const Kind = File.Kind; | |
| 33 | }; | |
| 34 | ||
| 35 | const IteratorError = error{ | |
| 36 | AccessDenied, | |
| 37 | PermissionDenied, | |
| 38 | SystemResources, | |
| 39 | } || posix.UnexpectedError; | |
| 40 | ||
| 41 | pub 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 | ||
| 602 | pub 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). | |
| 609 | pub fn iterateAssumeFirstIteration(self: Dir) Iterator { | |
| 610 | return self.iterateImpl(false); | |
| 611 | } | |
| 612 | ||
| 613 | fn 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 | ||
| 669 | pub 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`. | |
| 769 | pub 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 | ||
| 784 | pub 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`. | |
| 843 | pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker { | |
| 844 | return .{ | |
| 845 | .inner = try walkSelectively(self, allocator), | |
| 846 | }; | |
| 847 | } | |
| 848 | ||
| 849 | pub const OpenError = Io.Dir.OpenError; | |
| 850 | ||
| 851 | pub fn close(self: *Dir) void { | |
| 852 | posix.close(self.fd); | |
| 853 | self.* = undefined; | |
| 854 | } | |
| 855 | ||
| 856 | /// Deprecated in favor of `Io.Dir.openFile`. | |
| 857 | pub 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`. | |
| 864 | pub 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`. | |
| 872 | pub const MakeError = Io.Dir.MakeError; | |
| 873 | ||
| 874 | /// Deprecated in favor of `Io.Dir.makeDir`. | |
| 875 | pub 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`. | |
| 882 | pub 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`. | |
| 887 | pub 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`. | |
| 892 | pub 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`. | |
| 897 | pub const MakePathStatus = Io.Dir.MakePathStatus; | |
| 898 | /// Deprecated in favor of `Io.Dir.MakePathError`. | |
| 899 | pub const MakePathError = Io.Dir.MakePathError; | |
| 900 | ||
| 901 | /// Deprecated in favor of `Io.Dir.makePathStatus`. | |
| 902 | pub 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`. | |
| 909 | pub 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 | ||
| 915 | pub 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`. | |
| 928 | pub 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`. | |
| 950 | pub 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`. | |
| 994 | pub 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`. | |
| 1013 | pub 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 | ||
| 1036 | pub const RealPathAllocError = RealPathError || Allocator.Error; | |
| 1037 | ||
| 1038 | /// Same as `Dir.realpath` except caller must free the returned memory. | |
| 1039 | /// See also `Dir.realpath`. | |
| 1040 | pub 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. | |
| 1058 | pub 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`. | |
| 1074 | pub const OpenOptions = Io.Dir.OpenOptions; | |
| 1075 | ||
| 1076 | /// Deprecated in favor of `Io.Dir.openDir`. | |
| 1077 | pub 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 | ||
| 1083 | pub 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. | |
| 1090 | pub 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. | |
| 1106 | pub 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. | |
| 1125 | pub 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 | ||
| 1132 | pub 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. | |
| 1160 | pub 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. | |
| 1176 | pub 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. | |
| 1185 | pub 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 | ||
| 1192 | pub 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. | |
| 1201 | pub 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. | |
| 1206 | pub 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. | |
| 1212 | pub 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. | |
| 1221 | pub 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. | |
| 1232 | pub 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. | |
| 1267 | pub 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. | |
| 1277 | pub 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. | |
| 1293 | pub 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. | |
| 1312 | pub 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 | ||
| 1350 | pub 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. | |
| 1358 | pub 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. | |
| 1377 | pub 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. | |
| 1382 | pub 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. | |
| 1401 | pub 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`. | |
| 1406 | pub 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 | ||
| 1412 | pub 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. | |
| 1426 | pub 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 | |
| 1447 | pub 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 | ||
| 1472 | pub 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. | |
| 1509 | pub 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. | |
| 1708 | pub fn deleteTreeMinStackSize(self: Dir, sub_path: []const u8) DeleteTreeError!void { | |
| 1709 | return self.deleteTreeMinStackSizeWithKindHint(sub_path, .file); | |
| 1710 | } | |
| 1711 | ||
| 1712 | fn 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. | |
| 1827 | fn 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 | ||
| 1892 | pub const WriteFileError = File.WriteError || File.OpenError; | |
| 1893 | ||
| 1894 | pub 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. | |
| 1904 | pub 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`. | |
| 1911 | pub const AccessError = Io.Dir.AccessError; | |
| 1912 | ||
| 1913 | /// Deprecated in favor of `Io.Dir.access`. | |
| 1914 | pub 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 | ||
| 1920 | pub const CopyFileOptions = struct { | |
| 1921 | /// When this is `null` the mode is copied from the source file. | |
| 1922 | override_mode: ?File.Mode = null, | |
| 1923 | }; | |
| 1924 | ||
| 1925 | pub 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 | |
| 1944 | pub 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 | ||
| 1979 | pub 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. | |
| 1993 | pub 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 | ||
| 2006 | pub const Stat = File.Stat; | |
| 2007 | pub const StatError = File.StatError; | |
| 2008 | ||
| 2009 | /// Deprecated in favor of `Io.Dir.stat`. | |
| 2010 | pub 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 | ||
| 2016 | pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError; | |
| 2017 | ||
| 2018 | /// Deprecated in favor of `Io.Dir.statPath`. | |
| 2019 | pub 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 | ||
| 2025 | pub 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 }`. | |
| 2032 | pub 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. | |
| 2043 | pub 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 | ||
| 2048 | pub const ChownError = File.ChownError; | |
| 2049 | ||
| 2050 | const Permissions = File.Permissions; | |
| 2051 | pub const SetPermissionsError = File.SetPermissionsError; | |
| 2052 | ||
| 2053 | /// Sets permissions according to the provided `Permissions` struct. | |
| 2054 | /// This method is *NOT* available on WASI | |
| 2055 | pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!void { | |
| 2056 | const file: File = .{ .handle = self.fd }; | |
| 2057 | try file.setPermissions(permissions); | |
| 2058 | } | |
| 2059 | ||
| 2060 | pub fn adaptToNewApi(dir: Dir) Io.Dir { | |
| 2061 | return .{ .handle = dir.fd }; | |
| 2062 | } | |
| 2063 | ||
| 2064 | pub 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, |
| 22 | 22 | pub const Handle = Io.File.Handle; |
| 23 | 23 | pub const Mode = Io.File.Mode; |
| 24 | 24 | pub const INode = Io.File.INode; |
| 25 | pub const Uid = posix.uid_t; | |
| 26 | pub const Gid = posix.gid_t; | |
| 25 | pub const Uid = Io.File.Uid; | |
| 26 | pub const Gid = Io.File.Gid; | |
| 27 | 27 | pub const Kind = Io.File.Kind; |
| 28 | 28 | |
| 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. | |
| 35 | pub const default_mode: Mode = if (Mode == u0) 0 else 0o666; | |
| 36 | ||
| 37 | 29 | /// Deprecated in favor of `Io.File.OpenError`. |
| 38 | 30 | pub const OpenError = Io.File.OpenError || error{WouldBlock}; |
| 39 | 31 | /// Deprecated in favor of `Io.File.OpenMode`. |
| ... | ... | @@ -43,53 +35,7 @@ pub const Lock = Io.File.Lock; |
| 43 | 35 | /// Deprecated in favor of `Io.File.OpenFlags`. |
| 44 | 36 | pub const OpenFlags = Io.File.OpenFlags; |
| 45 | 37 | |
| 46 | pub 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 | }; | |
| 38 | pub const CreateFlags = std.Io.File.CreateFlags; | |
| 93 | 39 | |
| 94 | 40 | pub fn stdout() File { |
| 95 | 41 | 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 { |
| 259 | 205 | try posix.ftruncate(self.handle, length); |
| 260 | 206 | } |
| 261 | 207 | |
| 262 | pub const SeekError = posix.SeekError; | |
| 263 | ||
| 264 | /// Repositions read/write file offset relative to the current offset. | |
| 265 | /// TODO: integrate with async I/O | |
| 266 | pub 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 | |
| 272 | pub 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 | |
| 278 | pub fn seekTo(self: File, offset: u64) SeekError!void { | |
| 279 | return posix.lseek_SET(self.handle, offset); | |
| 280 | } | |
| 281 | ||
| 282 | pub const GetSeekPosError = posix.SeekError || StatError; | |
| 283 | ||
| 284 | /// TODO: integrate with async I/O | |
| 285 | pub fn getPos(self: File) GetSeekPosError!u64 { | |
| 286 | return posix.lseek_CUR_get(self.handle); | |
| 287 | } | |
| 288 | ||
| 289 | 208 | pub const GetEndPosError = std.os.windows.GetFileSizeError || StatError; |
| 290 | 209 | |
| 291 | 210 | /// TODO: integrate with async I/O |
| ... | ... | @@ -306,11 +225,13 @@ pub fn mode(self: File) ModeError!Mode { |
| 306 | 225 | return (try self.stat()).mode; |
| 307 | 226 | } |
| 308 | 227 | |
| 228 | /// Deprecated in favor of `Io.File.Stat`. | |
| 309 | 229 | pub const Stat = Io.File.Stat; |
| 310 | 230 | |
| 231 | /// Deprecated in favor of `Io.File.StatError`. | |
| 311 | 232 | pub const StatError = posix.FStatError; |
| 312 | 233 | |
| 313 | /// Returns `Stat` containing basic information about the `File`. | |
| 234 | /// Deprecated in favor of `Io.File.stat`. | |
| 314 | 235 | pub fn stat(self: File) StatError!Stat { |
| 315 | 236 | var threaded: Io.Threaded = .init_single_threaded; |
| 316 | 237 | const io = threaded.ioBasic(); |
| ... | ... | @@ -710,7 +631,7 @@ pub const Writer = struct { |
| 710 | 631 | Unexpected, |
| 711 | 632 | }; |
| 712 | 633 | |
| 713 | pub const SeekError = File.SeekError; | |
| 634 | pub const SeekError = Io.File.SeekError; | |
| 714 | 635 | |
| 715 | 636 | /// Number of slices to store on the stack, when trying to send as many byte |
| 716 | 637 | /// vectors through the underlying write calls as possible. |
| ... | ... | @@ -1268,10 +1189,8 @@ pub fn writerStreaming(file: File, buffer: []u8) Writer { |
| 1268 | 1189 | const range_off: windows.LARGE_INTEGER = 0; |
| 1269 | 1190 | const range_len: windows.LARGE_INTEGER = 1; |
| 1270 | 1191 | |
| 1271 | pub const LockError = error{ | |
| 1272 | SystemResources, | |
| 1273 | FileLocksNotSupported, | |
| 1274 | } || posix.UnexpectedError; | |
| 1192 | /// Deprecated | |
| 1193 | pub const LockError = Io.File.LockError; | |
| 1275 | 1194 | |
| 1276 | 1195 | /// Blocks when an incompatible lock is held by another process. |
| 1277 | 1196 | /// 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" { |
| 2065 | 2065 | try dir.chown(null, null); |
| 2066 | 2066 | } |
| 2067 | 2067 | |
| 2068 | test "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 | ||
| 2083 | 2068 | test "invalid UTF-8/WTF-8 paths" { |
| 2084 | 2069 | const expected_err = switch (native_os) { |
| 2085 | 2070 | .wasi => error.BadPathName, |
lib/std/os.zig-130| ... | ... | @@ -21,7 +21,6 @@ const mem = std.mem; |
| 21 | 21 | const elf = std.elf; |
| 22 | 22 | const fs = std.fs; |
| 23 | 23 | const dl = @import("dynamic_library.zig"); |
| 24 | const max_path_bytes = std.fs.max_path_bytes; | |
| 25 | 24 | const posix = std.posix; |
| 26 | 25 | const native_os = builtin.os.tag; |
| 27 | 26 | |
| ... | ... | @@ -56,135 +55,6 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o |
| 56 | 55 | else => undefined, |
| 57 | 56 | }; |
| 58 | 57 | |
| 59 | pub 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. | |
| 91 | pub 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 | ||
| 188 | 58 | pub const FstatError = error{ |
| 189 | 59 | SystemResources, |
| 190 | 60 | AccessDenied, |
lib/std/posix.zig+23-1037| ... | ... | @@ -319,35 +319,6 @@ pub const FChmodError = error{ |
| 319 | 319 | ReadOnlyFileSystem, |
| 320 | 320 | } || UnexpectedError; |
| 321 | 321 | |
| 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. | |
| 327 | pub 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 | ||
| 351 | 322 | pub const FChmodAtError = FChmodError || error{ |
| 352 | 323 | /// A component of `path` exceeded `NAME_MAX`, or the entire path exceeded |
| 353 | 324 | /// `PATH_MAX`. |
| ... | ... | @@ -533,50 +504,6 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr |
| 533 | 504 | } |
| 534 | 505 | } |
| 535 | 506 | |
| 536 | pub 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. | |
| 551 | pub 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 | ||
| 580 | 507 | pub const RebootError = error{ |
| 581 | 508 | PermissionDenied, |
| 582 | 509 | } || UnexpectedError; |
| ... | ... | @@ -1926,150 +1853,6 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { |
| 1926 | 1853 | } |
| 1927 | 1854 | } |
| 1928 | 1855 | |
| 1929 | pub 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. | |
| 1958 | pub 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`. | |
| 1971 | pub 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`. | |
| 2007 | pub 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`. | |
| 2020 | pub 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`. | |
| 2046 | pub 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 | ||
| 2073 | 1856 | pub const LinkError = UnexpectedError || error{ |
| 2074 | 1857 | AccessDenied, |
| 2075 | 1858 | PermissionDenied, |
| ... | ... | @@ -2200,412 +1983,32 @@ pub fn linkat( |
| 2200 | 1983 | .MLINK => return error.LinkQuotaExceeded, |
| 2201 | 1984 | .NAMETOOLONG => return error.NameTooLong, |
| 2202 | 1985 | .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 | ||
| 2219 | pub 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`. | |
| 2251 | pub 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. | |
| 2267 | pub 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. | |
| 2295 | pub 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 | ||
| 2302 | pub 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. | |
| 2312 | pub 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`. | |
| 2326 | pub 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. | |
| 2358 | pub 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. | |
| 2391 | pub 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 | ||
| 2396 | pub 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. | |
| 2438 | pub 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. | |
| 2453 | pub 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. | |
| 2488 | pub 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. | |
| 2497 | pub 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`. | |
| 2520 | fn 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. | |
| 2551 | const 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. | |
| 2559 | pub 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 | } | |
| 2595 | 1996 | } |
| 1997 | const old = try toPosixPath(oldpath); | |
| 1998 | const new = try toPosixPath(newpath); | |
| 1999 | return try linkatZ(olddir, &old, newdir, &new, flags); | |
| 2596 | 2000 | } |
| 2597 | 2001 | |
| 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. | |
| 2600 | pub 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. | |
| 2006 | const RelativePathWasi = struct { | |
| 2007 | /// Handle to directory | |
| 2008 | dir_fd: fd_t, | |
| 2009 | /// Path to resource within `dir_fd`. | |
| 2010 | relative_path: []const u8, | |
| 2011 | }; | |
| 2609 | 2012 | |
| 2610 | 2013 | /// On Windows, `sub_dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 2611 | 2014 | /// 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 { |
| 2723 | 2126 | windows.CloseHandle(sub_dir_handle); |
| 2724 | 2127 | } |
| 2725 | 2128 | |
| 2726 | pub 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. | |
| 2749 | pub 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. | |
| 2769 | pub 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. | |
| 2797 | pub 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 | ||
| 2804 | 2129 | pub const ChangeCurDirError = error{ |
| 2805 | 2130 | AccessDenied, |
| 2806 | 2131 | FileSystem, |
| ... | ... | @@ -2889,194 +2214,6 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void { |
| 2889 | 2214 | } |
| 2890 | 2215 | } |
| 2891 | 2216 | |
| 2892 | pub 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. | |
| 2929 | pub 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`. | |
| 2954 | pub 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. | |
| 2959 | pub 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`. | |
| 2998 | pub 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`. | |
| 3018 | pub 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`. | |
| 3044 | pub 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`. | |
| 3050 | pub 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 | ||
| 3080 | 2217 | pub const SetEidError = error{ |
| 3081 | 2218 | InvalidUserId, |
| 3082 | 2219 | PermissionDenied, |
| ... | ... | @@ -4814,157 +3951,6 @@ pub fn flock(fd: fd_t, operation: i32) FlockError!void { |
| 4814 | 3951 | } |
| 4815 | 3952 | } |
| 4816 | 3953 | |
| 4817 | pub 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. | |
| 4880 | pub 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. | |
| 4898 | pub 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. | |
| 4955 | pub 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. | |
| 4964 | pub 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 | ||
| 4968 | 3954 | /// Spurious wakeups are possible and no precision of timing is guaranteed. |
| 4969 | 3955 | pub fn nanosleep(seconds: u64, nanoseconds: u64) void { |
| 4970 | 3956 | var req = timespec{ |
lib/std/zig/system.zig+4-2| ... | ... | @@ -786,7 +786,9 @@ test glibcVerFromLinkName { |
| 786 | 786 | } |
| 787 | 787 | |
| 788 | 788 | fn 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) { | |
| 790 | 792 | error.NameTooLong => return error.Unexpected, |
| 791 | 793 | error.BadPathName => return error.Unexpected, |
| 792 | 794 | error.DeviceBusy => return error.Unexpected, |
| ... | ... | @@ -805,7 +807,7 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { |
| 805 | 807 | error.Unexpected => |e| return e, |
| 806 | 808 | error.Canceled => |e| return e, |
| 807 | 809 | }; |
| 808 | defer dir.close(); | |
| 810 | defer dir.close(io); | |
| 809 | 811 | |
| 810 | 812 | // Now we have a candidate for the path to libc shared object. In |
| 811 | 813 | // the past, we used readlink() here because the link name would |