Commit 413f9a5cfc

Andrew Kelley <andrew@ziglang.org>
2019-11-30 21:14:04
move `std.fs.Dir.cwd` to `std.fs.cwd`
update to non-deprecated std.fs APIs throughout the codebase Related: #3811
1 parent d039fed
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,
         });
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);
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
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;
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" {
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());
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),
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" {
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 {
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,
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);
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;
 
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);
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();
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) {