Commit ef67c49785

Andrew Kelley <andrew@ziglang.org>
2019-10-21 20:18:01
[wip] use NtDll APIs on Windows to implement std.fs.Dir
1 parent 5b1a492
lib/std/fs/file.zig
@@ -243,6 +243,7 @@ pub const File = struct {
             switch (rc) {
                 windows.STATUS.SUCCESS => {},
                 windows.STATUS.BUFFER_OVERFLOW => {},
+                windows.STATUS.INVALID_PARAMETER => unreachable,
                 else => return windows.unexpectedStatus(rc),
             }
             return Stat{
lib/std/fs/path.zig
@@ -136,6 +136,25 @@ pub fn isAbsolute(path: []const u8) bool {
     }
 }
 
+pub fn isAbsoluteW(path_w: [*]const u16) bool {
+    if (path_w[0] == '/')
+        return true;
+
+    if (path_w[0] == '\\') {
+        return true;
+    }
+    if (path_w[0] == 0 or path_w[1] == 0 or path_w[2] == 0) {
+        return false;
+    }
+    if (path_w[1] == ':') {
+        if (path_w[2] == '/')
+            return true;
+        if (path_w[2] == '\\')
+            return true;
+    }
+    return false;
+}
+
 pub fn isAbsoluteWindows(path: []const u8) bool {
     if (path[0] == '/')
         return true;
lib/std/os/bits/windows.zig
@@ -158,3 +158,6 @@ pub const EWOULDBLOCK = 140;
 pub const EDQUOT = 10069;
 
 pub const F_OK = 0;
+
+/// Remove directory instead of unlinking file
+pub const AT_REMOVEDIR = 0x200;
lib/std/os/windows/bits.zig
@@ -300,6 +300,44 @@ pub const FILE_SHARE_DELETE = 0x00000004;
 pub const FILE_SHARE_READ = 0x00000001;
 pub const FILE_SHARE_WRITE = 0x00000002;
 
+pub const DELETE = 0x00010000;
+pub const READ_CONTROL = 0x00020000;
+pub const WRITE_DAC = 0x00040000;
+pub const WRITE_OWNER = 0x00080000;
+pub const SYNCHRONIZE = 0x00100000;
+pub const STANDARD_RIGHTS_REQUIRED = 0x000f0000;
+
+// disposition for NtCreateFile
+pub const FILE_SUPERSEDE = 0;
+pub const FILE_OPEN = 1;
+pub const FILE_CREATE = 2;
+pub const FILE_OPEN_IF = 3;
+pub const FILE_OVERWRITE = 4;
+pub const FILE_OVERWRITE_IF = 5;
+pub const FILE_MAXIMUM_DISPOSITION = 5;
+
+// flags for NtCreateFile and NtOpenFile
+pub const FILE_DIRECTORY_FILE = 0x00000001;
+pub const FILE_WRITE_THROUGH = 0x00000002;
+pub const FILE_SEQUENTIAL_ONLY = 0x00000004;
+pub const FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008;
+pub const FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
+pub const FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
+pub const FILE_NON_DIRECTORY_FILE = 0x00000040;
+pub const FILE_CREATE_TREE_CONNECTION = 0x00000080;
+pub const FILE_COMPLETE_IF_OPLOCKED = 0x00000100;
+pub const FILE_NO_EA_KNOWLEDGE = 0x00000200;
+pub const FILE_OPEN_FOR_RECOVERY = 0x00000400;
+pub const FILE_RANDOM_ACCESS = 0x00000800;
+pub const FILE_DELETE_ON_CLOSE = 0x00001000;
+pub const FILE_OPEN_BY_FILE_ID = 0x00002000;
+pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
+pub const FILE_NO_COMPRESSION = 0x00008000;
+pub const FILE_RESERVE_OPFILTER = 0x00100000;
+pub const FILE_TRANSACTED_MODE = 0x00200000;
+pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;
+pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
+
 pub const CREATE_ALWAYS = 2;
 pub const CREATE_NEW = 1;
 pub const OPEN_ALWAYS = 4;
@@ -720,15 +758,117 @@ pub const VECTORED_EXCEPTION_HANDLER = stdcallcc fn (ExceptionInfo: *EXCEPTION_P
 
 pub const OBJECT_ATTRIBUTES = extern struct {
     Length: ULONG,
-    RootDirectory: HANDLE,
+    RootDirectory: ?HANDLE,
     ObjectName: *UNICODE_STRING,
     Attributes: ULONG,
     SecurityDescriptor: ?*c_void,
     SecurityQualityOfService: ?*c_void,
 };
 
+pub const OBJ_INHERIT = 0x00000002;
+pub const OBJ_PERMANENT = 0x00000010;
+pub const OBJ_EXCLUSIVE = 0x00000020;
+pub const OBJ_CASE_INSENSITIVE = 0x00000040;
+pub const OBJ_OPENIF = 0x00000080;
+pub const OBJ_OPENLINK = 0x00000100;
+pub const OBJ_KERNEL_HANDLE = 0x00000200;
+pub const OBJ_VALID_ATTRIBUTES = 0x000003F2;
+
 pub const UNICODE_STRING = extern struct {
-    Length: USHORT,
-    MaximumLength: USHORT,
+    Length: c_ushort,
+    MaximumLength: c_ushort,
     Buffer: [*]WCHAR,
 };
+
+pub const PEB = extern struct {
+    Reserved1: [2]BYTE,
+    BeingDebugged: BYTE,
+    Reserved2: [1]BYTE,
+    Reserved3: [2]PVOID,
+    Ldr: *PEB_LDR_DATA,
+    ProcessParameters: *RTL_USER_PROCESS_PARAMETERS,
+    Reserved4: [3]PVOID,
+    AtlThunkSListPtr: PVOID,
+    Reserved5: PVOID,
+    Reserved6: ULONG,
+    Reserved7: PVOID,
+    Reserved8: ULONG,
+    AtlThunkSListPtr32: ULONG,
+    Reserved9: [45]PVOID,
+    Reserved10: [96]BYTE,
+    PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
+    Reserved11: [128]BYTE,
+    Reserved12: [1]PVOID,
+    SessionId: ULONG,
+};
+
+pub const PEB_LDR_DATA = extern struct {
+    Reserved1: [8]BYTE,
+    Reserved2: [3]PVOID,
+    InMemoryOrderModuleList: LIST_ENTRY,
+};
+
+pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
+    AllocationSize: ULONG,
+    Size: ULONG,
+    Flags: ULONG,
+    DebugFlags: ULONG,
+    ConsoleHandle: HANDLE,
+    ConsoleFlags: ULONG,
+    hStdInput: HANDLE,
+    hStdOutput: HANDLE,
+    hStdError: HANDLE,
+    CurrentDirectory: CURDIR,
+    DllPath: UNICODE_STRING,
+    ImagePathName: UNICODE_STRING,
+    CommandLine: UNICODE_STRING,
+    Environment: [*]WCHAR,
+    dwX: ULONG,
+    dwY: ULONG,
+    dwXSize: ULONG,
+    dwYSize: ULONG,
+    dwXCountChars: ULONG,
+    dwYCountChars: ULONG,
+    dwFillAttribute: ULONG,
+    dwFlags: ULONG,
+    dwShowWindow: ULONG,
+    WindowTitle: UNICODE_STRING,
+    Desktop: UNICODE_STRING,
+    ShellInfo: UNICODE_STRING,
+    RuntimeInfo: UNICODE_STRING,
+    DLCurrentDirectory: [0x20]RTL_DRIVE_LETTER_CURDIR,
+};
+
+pub const RTL_DRIVE_LETTER_CURDIR = extern struct {
+    Flags: c_ushort,
+    Length: c_ushort,
+    TimeStamp: ULONG,
+    DosPath: UNICODE_STRING,
+};
+
+pub const PPS_POST_PROCESS_INIT_ROUTINE = ?extern fn () void;
+
+pub const FILE_BOTH_DIR_INFORMATION = extern struct {
+    NextEntryOffset: ULONG,
+    FileIndex: ULONG,
+    CreationTime: LARGE_INTEGER,
+    LastAccessTime: LARGE_INTEGER,
+    LastWriteTime: LARGE_INTEGER,
+    ChangeTime: LARGE_INTEGER,
+    EndOfFile: LARGE_INTEGER,
+    AllocationSize: LARGE_INTEGER,
+    FileAttributes: ULONG,
+    FileNameLength: ULONG,
+    EaSize: ULONG,
+    ShortNameLength: CHAR,
+    ShortName: [12]WCHAR,
+    FileName: [1]WCHAR,
+};
+pub const FILE_BOTH_DIRECTORY_INFORMATION = FILE_BOTH_DIR_INFORMATION;
+
+pub const IO_APC_ROUTINE = extern fn (PVOID, *IO_STATUS_BLOCK, ULONG) void;
+
+pub const CURDIR = extern struct {
+    DosPath: UNICODE_STRING,
+    Handle: HANDLE,
+};
lib/std/os/windows/ntdll.zig
@@ -13,12 +13,33 @@ pub extern "NtDll" stdcallcc fn NtCreateFile(
     DesiredAccess: ACCESS_MASK,
     ObjectAttributes: *OBJECT_ATTRIBUTES,
     IoStatusBlock: *IO_STATUS_BLOCK,
-    AllocationSize: *LARGE_INTEGER,
+    AllocationSize: ?*LARGE_INTEGER,
     FileAttributes: ULONG,
     ShareAccess: ULONG,
     CreateDisposition: ULONG,
     CreateOptions: ULONG,
-    EaBuffer: *c_void,
+    EaBuffer: ?*c_void,
     EaLength: ULONG,
 ) NTSTATUS;
 pub extern "NtDll" stdcallcc fn NtClose(Handle: HANDLE) NTSTATUS;
+pub extern "NtDll" stdcallcc fn RtlDosPathNameToNtPathName_U(
+    DosPathName: [*]const u16,
+    NtPathName: *UNICODE_STRING,
+    NtFileNamePart: ?*?[*]const u16,
+    DirectoryInfo: ?*CURDIR,
+) BOOL;
+pub extern "NtDll" stdcallcc fn RtlFreeUnicodeString(UnicodeString: *UNICODE_STRING) void;
+
+pub extern "NtDll" stdcallcc fn NtQueryDirectoryFile(
+    FileHandle: HANDLE,
+    Event: ?HANDLE,
+    ApcRoutine: ?IO_APC_ROUTINE,
+    ApcContext: ?*c_void,
+    IoStatusBlock: *IO_STATUS_BLOCK,
+    FileInformation: *c_void,
+    Length: ULONG,
+    FileInformationClass: FILE_INFORMATION_CLASS,
+    ReturnSingleEntry: BOOLEAN,
+    FileName: ?*UNICODE_STRING,
+    RestartScan: BOOLEAN,
+) NTSTATUS;
lib/std/os/windows/status.zig
@@ -3,3 +3,9 @@ pub const SUCCESS = 0x00000000;
 
 /// The data was too large to fit into the specified buffer.
 pub const BUFFER_OVERFLOW = 0x80000005;
+
+pub const INVALID_PARAMETER = 0xC000000D;
+pub const ACCESS_DENIED = 0xC0000022;
+pub const OBJECT_NAME_INVALID = 0xC0000033;
+pub const OBJECT_NAME_NOT_FOUND = 0xC0000034;
+pub const OBJECT_PATH_SYNTAX_BAD = 0xC000003B;
lib/std/os/windows.zig
@@ -792,6 +792,25 @@ pub fn SetFileTime(
     }
 }
 
+pub fn peb() *PEB {
+    switch (builtin.arch) {
+        .i386 => {
+            return asm (
+                \\ mov %%fs:0x18, %[ptr]
+                \\ mov %%ds:0x30(%[ptr]), %[ptr]
+                : [ptr] "=r" (-> *PEB)
+            );
+        },
+        .x86_64 => {
+            return asm (
+                \\ mov %%gs:0x60, %[ptr]
+                : [ptr] "=r" (-> *PEB)
+            );
+        },
+        else => @compileError("unsupported architecture"),
+    }
+}
+
 /// A file time is a 64-bit value that represents the number of 100-nanosecond
 /// intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated
 /// Universal Time (UTC).
@@ -844,8 +863,8 @@ pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16)
             else => {},
         }
     }
-    const start_index = if (mem.startsWith(u8, s, "\\\\") or !std.fs.path.isAbsolute(s)) 0 else blk: {
-        const prefix = [_]u16{ '\\', '\\', '?', '\\' };
+    const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: {
+        const prefix = [_]u16{ '\\', '?', '?', '\\' };
         mem.copy(u16, result[0..], prefix);
         break :blk prefix.len;
     };
lib/std/fs.zig
@@ -353,18 +353,13 @@ pub fn deleteTree(full_path: []const u8) !void {
 
         return dir.deleteTree(path.basename(full_path));
     } else {
-        return Dir.posix_cwd.deleteTree(full_path);
+        return Dir.cwd().deleteTree(full_path);
     }
 }
 
 pub const Dir = struct {
     fd: os.fd_t,
 
-    /// An open handle to the current working directory.
-    /// Closing this directory is safety-checked illegal behavior.
-    /// Not available on Windows.
-    pub const posix_cwd = Dir{ .fd = os.AT_FDCWD };
-
     pub const Entry = struct {
         name: []const u8,
         kind: Kind,
@@ -386,12 +381,10 @@ pub const Dir = struct {
         .macosx, .ios, .freebsd, .netbsd => struct {
             dir: Dir,
             seek: i64,
-            buf: [buffer_len]u8,
+            buf: [8192]u8, // TODO align(@alignOf(os.dirent)),
             index: usize,
             end_index: usize,
 
-            pub const buffer_len = 8192;
-
             const Self = @This();
 
             /// Memory such as file names referenced in this returned entry becomes invalid
@@ -407,27 +400,24 @@ pub const Dir = struct {
             fn nextDarwin(self: *Self) !?Entry {
                 start_over: while (true) {
                     if (self.index >= self.end_index) {
-                        while (true) {
-                            const rc = os.system.__getdirentries64(
-                                self.dir.fd,
-                                &self.buf,
-                                self.buf.len,
-                                &self.seek,
-                            );
-                            if (rc == 0) return null;
-                            if (rc < 0) {
-                                switch (os.errno(rc)) {
-                                    os.EBADF => unreachable,
-                                    os.EFAULT => unreachable,
-                                    os.ENOTDIR => unreachable,
-                                    os.EINVAL => unreachable,
-                                    else => |err| return os.unexpectedErrno(err),
-                                }
+                        const rc = os.system.__getdirentries64(
+                            self.dir.fd,
+                            &self.buf,
+                            self.buf.len,
+                            &self.seek,
+                        );
+                        if (rc == 0) return null;
+                        if (rc < 0) {
+                            switch (os.errno(rc)) {
+                                os.EBADF => unreachable,
+                                os.EFAULT => unreachable,
+                                os.ENOTDIR => unreachable,
+                                os.EINVAL => unreachable,
+                                else => |err| return os.unexpectedErrno(err),
                             }
-                            self.index = 0;
-                            self.end_index = @intCast(usize, rc);
-                            break;
                         }
+                        self.index = 0;
+                        self.end_index = @intCast(usize, rc);
                     }
                     const darwin_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
                     const next_index = self.index + darwin_entry.d_reclen;
@@ -460,26 +450,23 @@ pub const Dir = struct {
             fn nextBsd(self: *Self) !?Entry {
                 start_over: while (true) {
                     if (self.index >= self.end_index) {
-                        while (true) {
-                            const rc = os.system.getdirentries(
-                                self.dir.fd,
-                                self.buf[0..].ptr,
-                                self.buf.len,
-                                &self.seek,
-                            );
-                            switch (os.errno(rc)) {
-                                0 => {},
-                                os.EBADF => unreachable,
-                                os.EFAULT => unreachable,
-                                os.ENOTDIR => unreachable,
-                                os.EINVAL => unreachable,
-                                else => |err| return os.unexpectedErrno(err),
-                            }
-                            if (rc == 0) return null;
-                            self.index = 0;
-                            self.end_index = @intCast(usize, rc);
-                            break;
+                        const rc = os.system.getdirentries(
+                            self.dir.fd,
+                            self.buf[0..].ptr,
+                            self.buf.len,
+                            &self.seek,
+                        );
+                        switch (os.errno(rc)) {
+                            0 => {},
+                            os.EBADF => unreachable,
+                            os.EFAULT => unreachable,
+                            os.ENOTDIR => unreachable,
+                            os.EINVAL => unreachable,
+                            else => |err| return os.unexpectedErrno(err),
                         }
+                        if (rc == 0) return null;
+                        self.index = 0;
+                        self.end_index = @intCast(usize, rc);
                     }
                     const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
                     const next_index = self.index + freebsd_entry.d_reclen;
@@ -511,12 +498,10 @@ pub const Dir = struct {
         },
         .linux => struct {
             dir: Dir,
-            buf: [buffer_len]u8,
+            buf: [8192]u8, // TODO align(@alignOf(os.dirent64)),
             index: usize,
             end_index: usize,
 
-            pub const buffer_len = 8192;
-
             const Self = @This();
 
             /// Memory such as file names referenced in this returned entry becomes invalid
@@ -524,21 +509,18 @@ pub const Dir = struct {
             pub fn next(self: *Self) !?Entry {
                 start_over: while (true) {
                     if (self.index >= self.end_index) {
-                        while (true) {
-                            const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
-                            switch (os.linux.getErrno(rc)) {
-                                0 => {},
-                                os.EBADF => unreachable,
-                                os.EFAULT => unreachable,
-                                os.ENOTDIR => unreachable,
-                                os.EINVAL => unreachable,
-                                else => |err| return os.unexpectedErrno(err),
-                            }
-                            if (rc == 0) return null;
-                            self.index = 0;
-                            self.end_index = rc;
-                            break;
+                        const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
+                        switch (os.linux.getErrno(rc)) {
+                            0 => {},
+                            os.EBADF => unreachable,
+                            os.EFAULT => unreachable,
+                            os.ENOTDIR => unreachable,
+                            os.EINVAL => unreachable,
+                            else => |err| return os.unexpectedErrno(err),
                         }
+                        if (rc == 0) return null;
+                        self.index = 0;
+                        self.end_index = rc;
                     }
                     const linux_entry = @ptrCast(*align(1) os.dirent64, &self.buf[self.index]);
                     const next_index = self.index + linux_entry.d_reclen;
@@ -570,13 +552,117 @@ pub const Dir = struct {
         },
         .windows => struct {
             dir: Dir,
-            find_file_data: os.windows.WIN32_FIND_DATAW,
+            buf: [8192]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
+            index: usize,
+            end_index: usize,
             first: bool,
             name_data: [256]u8,
+
+            const Self = @This();
+
+            pub fn next(self: *Self) !?Entry {
+                start_over: while (true) {
+                    const w = os.windows;
+                    if (self.index >= self.end_index) {
+                        var io: w.IO_STATUS_BLOCK = undefined;
+                        //var mask_buf = [2]u16{ 'a', 0 };
+                        //var mask = w.UNICODE_STRING{
+                        //    .Length = 2,
+                        //    .MaximumLength = 2,
+                        //    .Buffer = &mask_buf,
+                        //};
+                        const rc = w.ntdll.NtQueryDirectoryFile(
+                            self.dir.fd,
+                            null,
+                            null,
+                            null,
+                            &io,
+                            &self.buf,
+                            self.buf.len,
+                            .FileBothDirectoryInformation,
+                            w.FALSE,
+                            null,
+                            if (self.first) w.BOOLEAN(w.TRUE) else w.BOOLEAN(w.FALSE),
+                        );
+                        self.first = false;
+                        if (io.Information == 0) return null;
+                        self.index = 0;
+                        self.end_index = io.Information;
+                        switch (rc) {
+                            w.STATUS.SUCCESS => {},
+                            else => return w.unexpectedStatus(rc),
+                        }
+                    }
+
+                    const aligned_ptr = @alignCast(@alignOf(w.FILE_BOTH_DIR_INFORMATION), &self.buf[self.index]);
+                    const dir_info = @ptrCast(*w.FILE_BOTH_DIR_INFORMATION, aligned_ptr);
+                    if (dir_info.NextEntryOffset != 0) {
+                        self.index += dir_info.NextEntryOffset;
+                    } else {
+                        self.index = self.buf.len;
+                    }
+
+                    const name_utf16le = @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2];
+
+                    if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' }))
+                        continue;
+                    // Trust that Windows gives us valid UTF-16LE
+                    const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
+                    const name_utf8 = self.name_data[0..name_utf8_len];
+                    const kind = blk: {
+                        const attrs = dir_info.FileAttributes;
+                        if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
+                        if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
+                        break :blk Entry.Kind.File;
+                    };
+                    return Entry{
+                        .name = name_utf8,
+                        .kind = kind,
+                    };
+                }
+            }
         },
         else => @compileError("unimplemented"),
     };
 
+    pub fn iterate(self: Dir) Iterator {
+        switch (builtin.os) {
+            .macosx, .ios, .freebsd, .netbsd => return Iterator{
+                .dir = self,
+                .seek = 0,
+                .index = 0,
+                .end_index = 0,
+                .buf = undefined,
+            },
+            .linux => return Iterator{
+                .dir = self,
+                .index = 0,
+                .end_index = 0,
+                .buf = undefined,
+            },
+            .windows => return Iterator{
+                .dir = self,
+                .index = 0,
+                .end_index = 0,
+                .first = true,
+                .buf = undefined,
+                .name_data = undefined,
+            },
+            else => @compileError("unimplemented"),
+        }
+    }
+
+    /// Returns an open handle to the current working directory.
+    /// Closing the returned `Dir` is checked illegal behavior.
+    /// On POSIX targets, this function is comptime-callable.
+    pub fn cwd() Dir {
+        if (os.windows.is_the_target) {
+            return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
+        } else {
+            return Dir{ .fd = os.AT_FDCWD };
+        }
+    }
+
     pub const OpenError = error{
         FileNotFound,
         NotDir,
@@ -594,18 +680,15 @@ pub const Dir = struct {
 
     /// Call `close` to free the directory handle.
     pub fn open(dir_path: []const u8) OpenError!Dir {
-        return posix_cwd.openDir(dir_path);
+        return cwd().openDir(dir_path);
     }
 
     /// Same as `open` except the parameter is null-terminated.
     pub fn openC(dir_path_c: [*]const u8) OpenError!Dir {
-        return posix_cwd.openDirC(dir_path_c);
+        return cwd().openDirC(dir_path_c);
     }
 
     pub fn close(self: *Dir) void {
-        if (os.windows.is_the_target) {
-            @panic("TODO");
-        }
         os.close(self.fd);
         self.* = undefined;
     }
@@ -625,14 +708,25 @@ pub const Dir = struct {
 
     /// Call `close` on the result when done.
     pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir {
+        std.debug.warn("openDir {}\n", sub_path);
+        if (os.windows.is_the_target) {
+            const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+            return self.openDirW(&sub_path_w);
+        }
+
         const sub_path_c = try os.toPosixPath(sub_path);
         return self.openDirC(&sub_path_c);
     }
 
-    /// Call `close` on the result when done.
-    pub fn openDirC(self: Dir, sub_path: [*]const u8) OpenError!Dir {
+    /// Same as `openDir` except the parameter is null-terminated.
+    pub fn openDirC(self: Dir, sub_path_c: [*]const u8) OpenError!Dir {
+        if (os.windows.is_the_target) {
+            const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
+            return self.openDirW(&sub_path_w);
+        }
+
         const flags = os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC;
-        const fd = os.openatC(self.fd, sub_path, flags, 0) catch |err| switch (err) {
+        const fd = os.openatC(self.fd, sub_path_c, flags, 0) catch |err| switch (err) {
             error.FileTooBig => unreachable, // can't happen for directories
             error.IsDir => unreachable, // we're providing O_DIRECTORY
             error.NoSpaceLeft => unreachable, // not providing O_CREAT
@@ -642,6 +736,79 @@ pub const Dir = struct {
         return Dir{ .fd = fd };
     }
 
+    /// Same as `openDir` except the path parameter is UTF16LE, NT-prefixed.
+    /// This function is Windows-only.
+    pub fn openDirW(self: Dir, sub_path_w: [*]const u16) OpenError!Dir {
+        const w = os.windows;
+        var result = Dir{
+            .fd = undefined,
+        };
+        //var mask: ?[*]const u16 = undefined;
+        //var nt_name: w.UNICODE_STRING = undefined;
+        //if (w.ntdll.RtlDosPathNameToNtPathName_U(sub_path_w, &nt_name, null, null) == 0) {
+        //    return error.FileNotFound;
+        //}
+        //defer w.ntdll.RtlFreeUnicodeString(&nt_name);
+        //if (mask) |m| {
+        //    if (m[0] == 0) {
+        //        return error.FileNotFound;
+        //    } else {
+        //        nt_name.Length = @intCast(u16, @ptrToInt(mask) - @ptrToInt(nt_name.Buffer));
+        //    }
+        //} else {
+        //    return error.FileNotFound;
+        //}
+
+        const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2);
+        std.debug.warn("path_len_bytes = {}\n", path_len_bytes);
+        var nt_name = w.UNICODE_STRING{
+            .Length = path_len_bytes,
+            .MaximumLength = path_len_bytes,
+            .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+        };
+        var attr = w.OBJECT_ATTRIBUTES{
+            .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+            .RootDirectory = if (path.isAbsoluteW(sub_path_w)) null else self.fd,
+            .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+            .ObjectName = &nt_name,
+            .SecurityDescriptor = null,
+            .SecurityQualityOfService = null,
+        };
+        std.debug.warn("RootDirectory = {}\n", attr.RootDirectory);
+        var io: w.IO_STATUS_BLOCK = undefined;
+        const wide_slice = nt_name.Buffer[0 .. nt_name.Length / 2];
+        //const wide_slice2 = std.mem.toSliceConst(u16, mask.?);
+        var buf: [200]u8 = undefined;
+        //var buf2: [200]u8 = undefined;
+        const len = std.unicode.utf16leToUtf8(&buf, wide_slice) catch unreachable;
+        //const len2 = std.unicode.utf16leToUtf8(&buf2, wide_slice2) catch unreachable;
+        std.debug.warn("path: {}\n", buf[0..len]);
+        //std.debug.warn("path: {}\nmask: {}\n", buf[0..len], buf2[0..len2]);
+        const rc = w.ntdll.NtCreateFile(
+            &result.fd,
+            w.GENERIC_READ | w.SYNCHRONIZE,
+            &attr,
+            &io,
+            null,
+            0,
+            w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
+            w.FILE_OPEN,
+            w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
+            null,
+            0,
+        );
+        std.debug.warn("result.fd = {}\n", result.fd);
+        switch (rc) {
+            w.STATUS.SUCCESS => return result,
+            w.STATUS.OBJECT_NAME_INVALID => @panic("openDirW invalid object name"),
+            w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+            w.STATUS.INVALID_PARAMETER => {
+                @panic("invalid parameter");
+            },
+            else => return w.unexpectedStatus(rc),
+        }
+    }
+
     pub const DeleteFileError = os.UnlinkError;
 
     /// Delete a file name and possibly the file it refers to, based on an open directory handle.
@@ -677,6 +844,10 @@ pub const Dir = struct {
     /// Returns `error.DirNotEmpty` if the directory is not empty.
     /// To delete a directory recursively, see `deleteTree`.
     pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
+        if (os.windows.is_the_target) {
+            const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+            return self.deleteDirW(&sub_path_w);
+        }
         const sub_path_c = try os.toPosixPath(sub_path);
         return self.deleteDirC(&sub_path_c);
     }
@@ -689,24 +860,25 @@ pub const Dir = struct {
         };
     }
 
-    pub fn iterate(self: Dir) Iterator {
-        switch (builtin.os) {
-            .macosx, .ios, .freebsd, .netbsd => return Iterator{
-                .dir = self,
-                .seek = 0,
-                .index = 0,
-                .end_index = 0,
-                .buf = undefined,
-            },
-            .linux => return Iterator{
-                .dir = self,
-                .index = 0,
-                .end_index = 0,
-                .buf = undefined,
-            },
-            .windows => @panic("TODO"),
-            else => @compileError("unimplemented"),
-        }
+    /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
+    /// This function is Windows-only.
+    pub fn deleteDirW(self: Dir, sub_path_w: [*]const u16) DeleteDirError!void {
+        os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
+            error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
+            else => |e| return e,
+        };
+    }
+
+    /// Read value of a symbolic link.
+    /// The return value is a slice of `buffer`, from index `0`.
+    pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
+        const sub_path_c = try os.toPosixPath(sub_path);
+        return self.readLinkC(&sub_path_c, buffer);
+    }
+
+    /// Same as `readLink`, except the `pathname` parameter is null-terminated.
+    pub fn readLinkC(self: Dir, sub_path_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
+        return os.readlinkatC(self.fd, sub_path_c, buffer);
     }
 
     pub const DeleteTreeError = error{
@@ -953,7 +1125,6 @@ pub const Walker = struct {
 /// Must call `Walker.deinit` when done.
 /// `dir_path` must not end in a path separator.
 /// The order of returned file system entries is undefined.
-/// TODO: https://github.com/ziglang/zig/issues/2888
 pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
     assert(!mem.endsWith(u8, dir_path, path.sep_str));
 
@@ -978,15 +1149,13 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
 
 /// Read value of a symbolic link.
 /// The return value is a slice of buffer, from index `0`.
-/// TODO https://github.com/ziglang/zig/issues/2888
-pub fn readLink(pathname: []const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
+pub fn readLink(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
     return os.readlink(pathname, buffer);
 }
 
-/// Same as `readLink`, except the `pathname` parameter is null-terminated.
-/// TODO https://github.com/ziglang/zig/issues/2888
-pub fn readLinkC(pathname: [*]const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
-    return os.readlinkC(pathname, buffer);
+/// Same as `readLink`, except the parameter is null-terminated.
+pub fn readLinkC(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
+    return os.readlinkC(pathname_c, buffer);
 }
 
 pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError;
lib/std/io.zig
@@ -127,6 +127,7 @@ pub fn OutStream(comptime WriteError: type) type {
     };
 }
 
+/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
 pub fn writeFile(path: []const u8, data: []const u8) !void {
     var file = try File.openWrite(path);
     defer file.close();
@@ -134,11 +135,13 @@ pub fn writeFile(path: []const u8, data: []const u8) !void {
 }
 
 /// On success, caller owns returned buffer.
+/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
 pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
     return readFileAllocAligned(allocator, path, @alignOf(u8));
 }
 
 /// On success, caller owns returned buffer.
+/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
 pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
     var file = try File.openRead(path);
     defer file.close();
@@ -1084,7 +1087,7 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing,
                         // safety. If it is bad, it will be caught anyway.
                         const TagInt = @TagType(TagType);
                         const tag = try self.deserializeInt(TagInt);
-                        
+
                         inline for (info.fields) |field_info| {
                             if (field_info.enum_field.?.value == tag) {
                                 const name = field_info.name;
lib/std/os.zig
@@ -999,12 +999,20 @@ pub const UnlinkatError = UnlinkError || error{
 
 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
 pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
+    if (windows.is_the_target) {
+        const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+        return unlinkatW(dirfd, &file_path_w, flags);
+    }
     const file_path_c = try toPosixPath(file_path);
     return unlinkatC(dirfd, &file_path_c, flags);
 }
 
 /// Same as `unlinkat` but `file_path` is a null-terminated string.
 pub fn unlinkatC(dirfd: fd_t, file_path_c: [*]const u8, flags: u32) UnlinkatError!void {
+    if (windows.is_the_target) {
+        const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
+        return unlinkatW(dirfd, &file_path_w, flags);
+    }
     switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
         0 => return,
         EACCES => return error.AccessDenied,
@@ -1028,6 +1036,56 @@ pub fn unlinkatC(dirfd: fd_t, file_path_c: [*]const u8, flags: u32) UnlinkatErro
     }
 }
 
+/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
+pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*]const u16, flags: u32) UnlinkatError!void {
+    const w = windows;
+
+    const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
+    const create_options_flags = if (want_rmdir_behavior)
+        w.ULONG(w.FILE_DELETE_ON_CLOSE)
+    else
+        w.ULONG(w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE);
+    var nt_name: w.UNICODE_STRING = undefined;
+    if (w.ntdll.RtlDosPathNameToNtPathName_U(sub_path_w, &nt_name, null, null) == 0) {
+        return error.FileNotFound;
+    }
+    defer w.ntdll.RtlFreeUnicodeString(&nt_name);
+
+    var attr = w.OBJECT_ATTRIBUTES{
+        .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+        .RootDirectory = dirfd,
+        .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
+        .ObjectName = &nt_name,
+        .SecurityDescriptor = null,
+        .SecurityQualityOfService = null,
+    };
+    var io: w.IO_STATUS_BLOCK = undefined;
+    var tmp_handle: w.HANDLE = undefined;
+    var rc = w.ntdll.NtCreateFile(
+        &tmp_handle,
+        w.SYNCHRONIZE | w.DELETE,
+        &attr,
+        &io,
+        null,
+        0,
+        w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
+        w.FILE_OPEN,
+        create_options_flags,
+        null,
+        0,
+    );
+    if (rc == w.STATUS.SUCCESS) {
+        rc = w.ntdll.NtClose(tmp_handle);
+    }
+    switch (rc) {
+        w.STATUS.SUCCESS => return,
+        w.STATUS.OBJECT_NAME_INVALID => unreachable,
+        w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+        w.STATUS.INVALID_PARAMETER => unreachable,
+        else => return w.unexpectedStatus(rc),
+    }
+}
+
 const RenameError = error{
     AccessDenied,
     FileBusy,
@@ -1287,6 +1345,27 @@ pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
     }
 }
 
+pub fn readlinkatC(dirfd: fd_t, file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
+    if (windows.is_the_target) {
+        const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+        @compileError("TODO implement readlink for Windows");
+    }
+    const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
+    switch (errno(rc)) {
+        0 => return out_buffer[0..@bitCast(usize, rc)],
+        EACCES => return error.AccessDenied,
+        EFAULT => unreachable,
+        EINVAL => unreachable,
+        EIO => return error.FileSystem,
+        ELOOP => return error.SymLinkLoop,
+        ENAMETOOLONG => return error.NameTooLong,
+        ENOENT => return error.FileNotFound,
+        ENOMEM => return error.SystemResources,
+        ENOTDIR => return error.NotDir,
+        else => |err| return unexpectedErrno(err),
+    }
+}
+
 pub const SetIdError = error{
     ResourceLimitReached,
     InvalidUserId,