diff --git a/lib/std/build.zig b/lib/std/build.zig index 38025b83b..026e889b7 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2416,7 +2416,7 @@ fn findVcpkgRoot(allocator: *Allocator) !?[]const u8 { const path_file = try fs.path.join(allocator, [_][]const u8{ appdata_path, "vcpkg.path.txt" }); defer allocator.free(path_file); - const file = fs.File.openRead(path_file) catch return null; + const file = fs.cwd().openFile(path_file, .{}) catch return null; defer file.close(); const size = @intCast(usize, try file.getEndPos()); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 6324c88d8..3c842f3ac 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1131,7 +1131,7 @@ fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo { } fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { - var f = try File.openRead(line_info.file_name); + var f = try fs.cwd().openFile(line_info.file_name, .{}); defer f.close(); // TODO fstat and make sure that the file has the correct size @@ -2089,7 +2089,7 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u const ofile_path = mem.toSliceConst(u8, @ptrCast([*:0]const u8, di.strings.ptr + ofile.n_strx)); gop.kv.value = MachOFile{ - .bytes = try std.fs.Dir.cwd().readFileAllocAligned( + .bytes = try std.fs.cwd().readFileAllocAligned( di.ofiles.allocator, ofile_path, maxInt(usize), diff --git a/lib/std/fs.zig b/lib/std/fs.zig index ec143056f..0f8da873a 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -13,8 +13,6 @@ pub const File = @import("fs/file.zig").File; pub const symLink = os.symlink; pub const symLinkC = os.symlinkC; -pub const deleteFile = os.unlink; -pub const deleteFileC = os.unlinkC; pub const rename = os.rename; pub const renameC = os.renameC; pub const renameW = os.renameW; @@ -88,13 +86,15 @@ pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus { /// If any of the directories do not exist for dest_path, they are created. /// TODO https://github.com/ziglang/zig/issues/2885 pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus { - var src_file = try File.openRead(source_path); + const my_cwd = cwd(); + + var src_file = try my_cwd.openFile(source_path, .{}); defer src_file.close(); const src_stat = try src_file.stat(); check_dest_stat: { const dest_stat = blk: { - var dest_file = File.openRead(dest_path) catch |err| switch (err) { + var dest_file = my_cwd.openFile(dest_path, .{}) catch |err| switch (err) { error.FileNotFound => break :check_dest_stat, else => |e| return e, }; @@ -157,7 +157,7 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil /// in the same directory as dest_path. /// Destination file will have the same mode as the source file. pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { - var in_file = try File.openRead(source_path); + var in_file = try cwd().openFile(source_path, .{}); defer in_file.close(); const mode = try in_file.mode(); @@ -180,7 +180,7 @@ pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { /// merged and readily available, /// there is a possibility of power loss or application termination leaving temporary files present pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { - var in_file = try File.openRead(source_path); + var in_file = try cwd().openFile(source_path, .{}); defer in_file.close(); var atomic_file = try AtomicFile.init(dest_path, mode); @@ -206,8 +206,6 @@ pub const AtomicFile = struct { /// dest_path must remain valid for the lifetime of AtomicFile /// call finish to atomically replace dest_path with contents - /// TODO once we have null terminated pointers, use the - /// openWriteNoClobberN function pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { const dirname = path.dirname(dest_path); var rand_buf: [12]u8 = undefined; @@ -224,15 +222,19 @@ pub const AtomicFile = struct { tmp_path_buf[tmp_path_len] = 0; + const my_cwd = cwd(); + while (true) { try crypto.randomBytes(rand_buf[0..]); b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf); - const file = File.openWriteNoClobberC(@ptrCast([*:0]u8, &tmp_path_buf), mode) catch |err| switch (err) { + // TODO https://github.com/ziglang/zig/issues/3770 to clean up this @ptrCast + const file = my_cwd.createFileC( + @ptrCast([*:0]u8, &tmp_path_buf), + .{ .mode = mode, .exclusive = true }, + ) catch |err| switch (err) { error.PathAlreadyExists => continue, - // TODO zig should figure out that this error set does not include PathAlreadyExists since - // it is handled in the above switch - else => return err, + else => |e| return e, }; return AtomicFile{ @@ -248,7 +250,7 @@ pub const AtomicFile = struct { pub fn deinit(self: *AtomicFile) void { if (!self.finished) { self.file.close(); - deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {}; + cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {}; self.finished = true; } } @@ -350,12 +352,12 @@ pub fn deleteTree(full_path: []const u8) !void { CannotDeleteRootDirectory, }.CannotDeleteRootDirectory; - var dir = try Dir.cwd().openDirList(dirname); + var dir = try cwd().openDirList(dirname); defer dir.close(); return dir.deleteTree(path.basename(full_path)); } else { - return Dir.cwd().deleteTree(full_path); + return cwd().deleteTree(full_path); } } @@ -657,17 +659,6 @@ pub const Dir = struct { } } - /// Returns an handle to the current working directory that is open for traversal. - /// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. - /// On POSIX targets, this function is comptime-callable. - pub fn cwd() Dir { - if (builtin.os == .windows) { - return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; - } else { - return Dir{ .fd = os.AT_FDCWD }; - } - } - pub const OpenError = error{ FileNotFound, NotDir, @@ -683,12 +674,12 @@ pub const Dir = struct { DeviceBusy, } || os.UnexpectedError; - /// Deprecated; call `Dir.cwd().openDirList` directly. + /// Deprecated; call `cwd().openDirList` directly. pub fn open(dir_path: []const u8) OpenError!Dir { return cwd().openDirList(dir_path); } - /// Deprecated; call `Dir.cwd().openDirListC` directly. + /// Deprecated; call `cwd().openDirListC` directly. pub fn openC(dir_path_c: [*:0]const u8) OpenError!Dir { return cwd().openDirListC(dir_path_c); } @@ -700,7 +691,9 @@ pub const Dir = struct { /// Opens a file for reading or writing, without attempting to create a new file. /// Call `File.close` to release the resource. + /// Asserts that the path parameter has no null bytes. pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openFileW(&path_w, flags); @@ -737,7 +730,9 @@ pub const Dir = struct { /// Creates, opens, or overwrites a file with write access. /// Call `File.close` on the result when done. + /// Asserts that the path parameter has no null bytes. pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.createFileW(&path_w, flags); @@ -865,7 +860,10 @@ pub const Dir = struct { /// list the contents of a directory, open it with `openDirList`. /// /// Call `close` on the result when done. + /// + /// Asserts that the path parameter has no null bytes. pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirTraverseW(&sub_path_w); @@ -880,7 +878,10 @@ pub const Dir = struct { /// same and may be more efficient. /// /// Call `close` on the result when done. + /// + /// Asserts that the path parameter has no null bytes. pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirListW(&sub_path_w); @@ -995,9 +996,12 @@ pub const Dir = struct { pub const DeleteFileError = os.UnlinkError; /// Delete a file name and possibly the file it refers to, based on an open directory handle. + /// Asserts that the path parameter has no null bytes. pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { - const sub_path_c = try os.toPosixPath(sub_path); - return self.deleteFileC(&sub_path_c); + os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + else => |e| return e, + }; } /// Same as `deleteFile` except the parameter is null-terminated. @@ -1008,6 +1012,14 @@ pub const Dir = struct { }; } + /// Same as `deleteFile` except the parameter is WTF-16 encoded. + pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void { + os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + else => |e| return e, + }; + } + pub const DeleteDirError = error{ DirNotEmpty, FileNotFound, @@ -1026,7 +1038,9 @@ pub const Dir = struct { /// Returns `error.DirNotEmpty` if the directory is not empty. /// To delete a directory recursively, see `deleteTree`. + /// Asserts that the path parameter has no null bytes. pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.deleteDirW(&sub_path_w); @@ -1054,7 +1068,9 @@ pub const Dir = struct { /// Read value of a symbolic link. /// The return value is a slice of `buffer`, from index `0`. + /// Asserts that the path parameter has no null bytes. pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { + if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0); const sub_path_c = try os.toPosixPath(sub_path); return self.readLinkC(&sub_path_c, buffer); } @@ -1265,8 +1281,94 @@ pub const Dir = struct { } } } + + /// Writes content to the file system, creating a new file if it does not exist, truncating + /// if it already exists. + pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void { + var file = try self.createFile(sub_path, .{}); + defer file.close(); + try file.write(data); + } }; +/// Returns an handle to the current working directory that is open for traversal. +/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior. +/// On POSIX targets, this function is comptime-callable. +pub fn cwd() Dir { + if (builtin.os == .windows) { + return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; + } else { + return Dir{ .fd = os.AT_FDCWD }; + } +} + +/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path. +/// Call `File.close` to release the resource. +/// Asserts that the path is absolute. See `Dir.openFile` for a function that +/// operates on both absolute and relative paths. +/// Asserts that the path parameter has no null bytes. See `openFileAbsoluteC` for a function +/// that accepts a null-terminated path. +pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsolute(absolute_path)); + return cwd().openFile(absolute_path, flags); +} + +/// Same as `openFileAbsolute` but the path parameter is null-terminated. +pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsoluteC(absolute_path_c)); + return cwd().openFileC(absolute_path_c, flags); +} + +/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded. +pub fn openFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsoluteW(absolute_path_w)); + return cwd().openFileW(absolute_path_w, flags); +} + +/// Creates, opens, or overwrites a file with write access, based on an absolute path. +/// Call `File.close` to release the resource. +/// Asserts that the path is absolute. See `Dir.createFile` for a function that +/// operates on both absolute and relative paths. +/// Asserts that the path parameter has no null bytes. See `createFileAbsoluteC` for a function +/// that accepts a null-terminated path. +pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) File.OpenError!File { + assert(path.isAbsolute(absolute_path)); + return cwd().createFile(absolute_path, flags); +} + +/// Same as `createFileAbsolute` but the path parameter is null-terminated. +pub fn createFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { + assert(path.isAbsoluteC(absolute_path_c)); + return cwd().createFileC(absolute_path_c, flags); +} + +/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded. +pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File { + assert(path.isAbsoluteW(absolute_path_w)); + return cwd().createFileW(absolute_path_w, flags); +} + +/// Delete a file name and possibly the file it refers to, based on an absolute path. +/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that +/// operates on both absolute and relative paths. +/// Asserts that the path parameter has no null bytes. +pub fn deleteFileAbsolute(absolute_path: []const u8) DeleteFileError!void { + assert(path.isAbsolute(absolute_path)); + return cwd().deleteFile(absolute_path); +} + +/// Same as `deleteFileAbsolute` except the parameter is null-terminated. +pub fn deleteFileAbsoluteC(absolute_path_c: [*:0]const u8) DeleteFileError!void { + assert(path.isAbsoluteC(absolute_path_c)); + return cwd().deleteFileC(absolute_path_c); +} + +/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded. +pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) DeleteFileError!void { + assert(path.isAbsoluteW(absolute_path_w)); + return cwd().deleteFileW(absolute_path_w); +} + pub const Walker = struct { stack: std.ArrayList(StackItem), name_buffer: std.Buffer, @@ -1339,7 +1441,7 @@ pub const Walker = struct { pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { assert(!mem.endsWith(u8, dir_path, path.sep_str)); - var dir = try Dir.cwd().openDirList(dir_path); + var dir = try cwd().openDirList(dir_path); errdefer dir.close(); var name_buffer = try std.Buffer.init(allocator, dir_path); @@ -1373,18 +1475,18 @@ pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfE pub fn openSelfExe() OpenSelfExeError!File { if (builtin.os == .linux) { - return File.openReadC("/proc/self/exe"); + return openFileAbsoluteC("/proc/self/exe", .{}); } if (builtin.os == .windows) { const wide_slice = selfExePathW(); const prefixed_path_w = try os.windows.wToPrefixedFileW(wide_slice); - return Dir.cwd().openReadW(&prefixed_path_w); + return cwd().openReadW(&prefixed_path_w); } var buf: [MAX_PATH_BYTES]u8 = undefined; const self_exe_path = try selfExePath(&buf); buf[self_exe_path.len] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 - return File.openReadC(@ptrCast([*:0]u8, self_exe_path.ptr)); + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 + return openFileAbsoluteC(@ptrCast([*:0]u8, self_exe_path.ptr), .{}); } test "openSelfExe" { diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 52e5f3e49..7a12c7b93 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -51,42 +51,42 @@ pub const File = struct { /// Deprecated; call `std.fs.Dir.openFile` directly. pub fn openRead(path: []const u8) OpenError!File { - return std.fs.Dir.cwd().openFile(path, .{}); + return std.fs.cwd().openFile(path, .{}); } /// Deprecated; call `std.fs.Dir.openFileC` directly. pub fn openReadC(path_c: [*:0]const u8) OpenError!File { - return std.fs.Dir.cwd().openFileC(path_c, .{}); + return std.fs.cwd().openFileC(path_c, .{}); } /// Deprecated; call `std.fs.Dir.openFileW` directly. pub fn openReadW(path_w: [*]const u16) OpenError!File { - return std.fs.Dir.cwd().openFileW(path_w, .{}); + return std.fs.cwd().openFileW(path_w, .{}); } /// Deprecated; call `std.fs.Dir.createFile` directly. pub fn openWrite(path: []const u8) OpenError!File { - return std.fs.Dir.cwd().createFile(path, .{}); + return std.fs.cwd().createFile(path, .{}); } /// Deprecated; call `std.fs.Dir.createFile` directly. pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File { - return std.fs.Dir.cwd().createFile(path, .{ .mode = file_mode }); + return std.fs.cwd().createFile(path, .{ .mode = file_mode }); } /// Deprecated; call `std.fs.Dir.createFileC` directly. pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File { - return std.fs.Dir.cwd().createFileC(path_c, .{ .mode = file_mode }); + return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode }); } /// Deprecated; call `std.fs.Dir.createFileW` directly. pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File { - return std.fs.Dir.cwd().createFileW(path_w, .{ .mode = file_mode }); + return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode }); } /// Deprecated; call `std.fs.Dir.createFile` directly. pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File { - return std.fs.Dir.cwd().createFile(path, .{ + return std.fs.cwd().createFile(path, .{ .mode = file_mode, .exclusive = true, }); @@ -94,7 +94,7 @@ pub const File = struct { /// Deprecated; call `std.fs.Dir.createFileC` directly. pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File { - return std.fs.Dir.cwd().createFileC(path_c, .{ + return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode, .exclusive = true, }); @@ -102,7 +102,7 @@ pub const File = struct { /// Deprecated; call `std.fs.Dir.createFileW` directly. pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File { - return std.fs.Dir.cwd().createFileW(path_w, .{ + return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode, .exclusive = true, }); diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index e2f1b5ac6..b1b3ef6af 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -128,6 +128,14 @@ test "join" { testJoinPosix([_][]const u8{ "a/", "/c" }, "a/c"); } +pub fn isAbsoluteC(path_c: [*:0]const u8) bool { + if (builtin.os == .windows) { + return isAbsoluteWindowsC(path_c); + } else { + return isAbsolutePosixC(path_c); + } +} + pub fn isAbsolute(path: []const u8) bool { if (builtin.os == .windows) { return isAbsoluteWindows(path); @@ -136,7 +144,7 @@ pub fn isAbsolute(path: []const u8) bool { } } -pub fn isAbsoluteW(path_w: [*]const u16) bool { +pub fn isAbsoluteW(path_w: [*:0]const u16) bool { if (path_w[0] == '/') return true; @@ -174,10 +182,33 @@ pub fn isAbsoluteWindows(path: []const u8) bool { return false; } +pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool { + if (path_c[0] == '/') + return true; + + if (path_c[0] == '\\') { + return true; + } + if (path_c[0] == 0 or path_c[1] == 0 or path_c[2] == 0) { + return false; + } + if (path_c[1] == ':') { + if (path_c[2] == '/') + return true; + if (path_c[2] == '\\') + return true; + } + return false; +} + pub fn isAbsolutePosix(path: []const u8) bool { return path[0] == sep_posix; } +pub fn isAbsolutePosixC(path_c: [*:0]const u8) bool { + return path_c[0] == sep_posix; +} + test "isAbsoluteWindows" { testIsAbsoluteWindows("/", true); testIsAbsoluteWindows("//", true); diff --git a/lib/std/io.zig b/lib/std/io.zig index 124d3cf25..c36fd195b 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -61,17 +61,14 @@ pub const COutStream = @import("io/c_out_stream.zig").COutStream; pub const InStream = @import("io/in_stream.zig").InStream; pub const OutStream = @import("io/out_stream.zig").OutStream; -/// TODO move this to `std.fs` and add a version to `std.fs.Dir`. +/// Deprecated; use `std.fs.Dir.writeFile`. pub fn writeFile(path: []const u8, data: []const u8) !void { - var file = try File.openWrite(path); - defer file.close(); - try file.write(data); + return fs.cwd().writeFile(path, data); } -/// On success, caller owns returned buffer. -/// This function is deprecated; use `std.fs.Dir.readFileAlloc`. +/// Deprecated; use `std.fs.Dir.readFileAlloc`. pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 { - return fs.Dir.cwd().readFileAlloc(allocator, path, math.maxInt(usize)); + return fs.cwd().readFileAlloc(allocator, path, math.maxInt(usize)); } pub fn BufferedInStream(comptime Error: type) type { diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index d2374f0a3..2857252bb 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -14,12 +14,14 @@ test "write a file, read it, then delete it" { var raw_bytes: [200 * 1024]u8 = undefined; var allocator = &std.heap.FixedBufferAllocator.init(raw_bytes[0..]).allocator; + const cwd = fs.cwd(); + var data: [1024]u8 = undefined; var prng = DefaultPrng.init(1234); prng.random.bytes(data[0..]); const tmp_file_name = "temp_test_file.txt"; { - var file = try File.openWrite(tmp_file_name); + var file = try cwd.createFile(tmp_file_name, .{}); defer file.close(); var file_out_stream = file.outStream(); @@ -32,8 +34,8 @@ test "write a file, read it, then delete it" { } { - // make sure openWriteNoClobber doesn't harm the file - if (File.openWriteNoClobber(tmp_file_name, File.default_mode)) |file| { + // Make sure the exclusive flag is honored. + if (cwd.createFile(tmp_file_name, .{ .exclusive = true })) |file| { unreachable; } else |err| { std.debug.assert(err == File.OpenError.PathAlreadyExists); @@ -41,7 +43,7 @@ test "write a file, read it, then delete it" { } { - var file = try File.openRead(tmp_file_name); + var file = try cwd.openFile(tmp_file_name, .{}); defer file.close(); const file_size = try file.getEndPos(); @@ -58,7 +60,7 @@ test "write a file, read it, then delete it" { expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data)); expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); } - try fs.deleteFile(tmp_file_name); + try cwd.deleteFile(tmp_file_name); } test "BufferOutStream" { @@ -274,7 +276,7 @@ test "BitOutStream" { test "BitStreams with File Stream" { const tmp_file_name = "temp_test_file.txt"; { - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer file.close(); var file_out = file.outStream(); @@ -291,7 +293,7 @@ test "BitStreams with File Stream" { try bit_stream.flushBits(); } { - var file = try File.openRead(tmp_file_name); + var file = try fs.cwd().openFile(tmp_file_name, .{}); defer file.close(); var file_in = file.inStream(); @@ -316,7 +318,7 @@ test "BitStreams with File Stream" { expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1)); } - try fs.deleteFile(tmp_file_name); + try fs.cwd().deleteFile(tmp_file_name); } fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: io.Packing) !void { @@ -599,7 +601,7 @@ test "c out stream" { const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile; defer { _ = std.c.fclose(out_file); - fs.deleteFileC(filename) catch {}; + fs.cwd().deleteFileC(filename) catch {}; } const out_stream = &io.COutStream.init(out_file).stream; @@ -608,10 +610,10 @@ test "c out stream" { test "File seek ops" { const tmp_file_name = "temp_test_file.txt"; - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer { file.close(); - fs.deleteFile(tmp_file_name) catch {}; + fs.cwd().deleteFile(tmp_file_name) catch {}; } try file.write([_]u8{0x55} ** 8192); @@ -632,10 +634,10 @@ test "File seek ops" { test "updateTimes" { const tmp_file_name = "just_a_temporary_file.txt"; - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer { file.close(); - std.fs.deleteFile(tmp_file_name) catch {}; + std.fs.cwd().deleteFile(tmp_file_name) catch {}; } var stat_old = try file.stat(); // Set atime and mtime to 5s before diff --git a/lib/std/net.zig b/lib/std/net.zig index 7a7b2de02..e18e5d378 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -812,7 +812,7 @@ fn linuxLookupNameFromHosts( family: os.sa_family_t, port: u16, ) !void { - const file = fs.File.openReadC("/etc/hosts") catch |err| switch (err) { + const file = fs.openFileAbsoluteC("/etc/hosts", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, @@ -1006,7 +1006,7 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { }; errdefer rc.deinit(); - const file = fs.File.openReadC("/etc/resolv.conf") catch |err| switch (err) { + const file = fs.openFileAbsoluteC("/etc/resolv.conf", .{}) catch |err| switch (err) { error.FileNotFound, error.NotDir, error.AccessDenied, diff --git a/lib/std/os.zig b/lib/std/os.zig index 22d52b24a..6572d3730 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -798,7 +798,7 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e path_buf[search_path.len] = '/'; mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); path_buf[search_path.len + file_slice.len + 1] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 err = execveC(@ptrCast([*:0]u8, &path_buf), child_argv, envp); switch (err) { error.AccessDenied => seen_eacces = true, @@ -834,7 +834,7 @@ pub fn execvpe( @memcpy(arg_buf.ptr, arg.ptr, arg.len); arg_buf[arg.len] = 0; - // TODO avoid @ptrCast using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast using slice syntax with https://github.com/ziglang/zig/issues/3770 argv_buf[i] = @ptrCast([*:0]u8, arg_buf.ptr); } argv_buf[argv_slice.len] = null; @@ -842,7 +842,7 @@ pub fn execvpe( const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); defer freeNullDelimitedEnvMap(allocator, envp_buf); - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 const argv_ptr = @ptrCast([*:null]?[*:0]u8, argv_buf.ptr); return execvpeC(argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr); @@ -863,12 +863,12 @@ pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std. @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); env_buf[env_buf.len - 1] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 envp_buf[i] = @ptrCast([*:0]u8, env_buf.ptr); } assert(i == envp_count); } - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3731 + // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 assert(envp_buf[envp_count] == null); return @ptrCast([*:null]?[*:0]u8, envp_buf.ptr)[0..envp_count]; } @@ -1087,7 +1087,9 @@ pub const UnlinkatError = UnlinkError || error{ }; /// Delete a file name and possibly the file it refers to, based on an open directory handle. +/// Asserts that the path parameter has no null bytes. pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { + if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0); if (builtin.os == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return unlinkatW(dirfd, &file_path_w, flags); diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index 8281851d6..aa6655b7d 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -4,6 +4,7 @@ const linux = std.os.linux; const mem = std.mem; const elf = std.elf; const expect = std.testing.expect; +const fs = std.fs; test "getpid" { expect(linux.getpid() != 0); @@ -45,14 +46,12 @@ test "timer" { err = linux.epoll_wait(@intCast(i32, epoll_fd), @ptrCast([*]linux.epoll_event, &events), 8, -1); } -const File = std.fs.File; - test "statx" { const tmp_file_name = "just_a_temporary_file.txt"; - var file = try File.openWrite(tmp_file_name); + var file = try fs.cwd().createFile(tmp_file_name, .{}); defer { file.close(); - std.fs.deleteFile(tmp_file_name) catch {}; + fs.cwd().deleteFile(tmp_file_name) catch {}; } var statx_buf: linux.Statx = undefined; diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 778a39eb3..c49de1680 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -20,7 +20,7 @@ test "makePath, put some files in it, deleteTree" { try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); try fs.deleteTree("os_test_tmp"); - if (fs.Dir.cwd().openDirTraverse("os_test_tmp")) |dir| { + if (fs.cwd().openDirTraverse("os_test_tmp")) |dir| { @panic("expected error"); } else |err| { expect(err == error.FileNotFound); @@ -111,7 +111,7 @@ test "AtomicFile" { const content = try io.readFileAlloc(allocator, test_out_file); expect(mem.eql(u8, content, test_content)); - try fs.deleteFile(test_out_file); + try fs.cwd().deleteFile(test_out_file); } test "thread local storage" { diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index 8e4a9b5d6..2a21f6c43 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -6,6 +6,7 @@ const mem = std.mem; const os = std.os; const warn = std.debug.warn; const coff = std.coff; +const fs = std.fs; const File = std.fs.File; const ArrayList = std.ArrayList; @@ -469,7 +470,7 @@ pub const Pdb = struct { msf: Msf, pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []u8) !void { - self.in_file = try File.openRead(file_name); + self.in_file = try fs.cwd().openFile(file_name, .{}); self.allocator = coff_ptr.allocator; self.coff = coff_ptr; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 4288c4ea9..eab08337a 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -702,7 +702,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro max_src_size, ) catch |err| switch (err) { error.IsDir, error.AccessDenied => { - var dir = try fs.Dir.cwd().openDirList(file_path); + var dir = try fs.cwd().openDirList(file_path); defer dir.close(); var group = event.Group(FmtError!void).init(fmt.allocator); diff --git a/src-self-hosted/stage1.zig b/src-self-hosted/stage1.zig index 0708abaaf..7d4871759 100644 --- a/src-self-hosted/stage1.zig +++ b/src-self-hosted/stage1.zig @@ -279,7 +279,7 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => { // TODO make event based (and dir.next()) - var dir = try fs.Dir.cwd().openDirList(file_path); + var dir = try fs.cwd().openDirList(file_path); defer dir.close(); var dir_it = dir.iterate(); diff --git a/test/standalone/cat/main.zig b/test/standalone/cat/main.zig index 7c15c15e5..f22329050 100644 --- a/test/standalone/cat/main.zig +++ b/test/standalone/cat/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); const io = std.io; const process = std.process; -const File = std.fs.File; +const fs = std.fs; const mem = std.mem; const warn = std.debug.warn; const allocator = std.debug.global_allocator; @@ -12,6 +12,8 @@ pub fn main() !void { var catted_anything = false; const stdout_file = io.getStdOut(); + const cwd = fs.cwd(); + while (args_it.next(allocator)) |arg_or_err| { const arg = try unwrapArg(arg_or_err); if (mem.eql(u8, arg, "-")) { @@ -20,7 +22,7 @@ pub fn main() !void { } else if (arg[0] == '-') { return usage(exe); } else { - const file = File.openRead(arg) catch |err| { + const file = cwd.openFile(arg, .{}) catch |err| { warn("Unable to open file: {}\n", @errorName(err)); return err; }; @@ -40,7 +42,7 @@ fn usage(exe: []const u8) !void { return error.Invalid; } -fn cat_file(stdout: File, file: File) !void { +fn cat_file(stdout: fs.File, file: fs.File) !void { var buf: [1024 * 4]u8 = undefined; while (true) {