Commit 3dd9af9948

Andrew Kelley <superjoe30@gmail.com>
2018-06-12 07:55:08
implement std.os.Dir for windows
improve std.os.File.access so that it does not depend on shlwapi.dll closes #1084
1 parent 0a18d53
doc/docgen.zig
@@ -51,14 +51,8 @@ pub fn main() !void {
     var toc = try genToc(allocator, &tokenizer);
 
     try os.makePath(allocator, tmp_dir_name);
-    defer {
-        // TODO issue #709
-        // disabled to pass CI tests, but obviously we want to implement this
-        // and then remove this workaround
-        if (builtin.os != builtin.Os.windows) {
-            os.deleteTree(allocator, tmp_dir_name) catch {};
-        }
-    }
+    defer os.deleteTree(allocator, tmp_dir_name) catch {};
+
     try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe);
     try buffered_out_stream.flush();
 }
std/os/windows/index.zig
@@ -1,3 +1,7 @@
+test "import" {
+    _ = @import("util.zig");
+}
+
 pub const ERROR = @import("error.zig");
 
 pub extern "advapi32" stdcallcc fn CryptAcquireContextA(
@@ -61,6 +65,10 @@ pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
 
 pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
 
+pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE;
+pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL;
+pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL;
+
 pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL;
 
 pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
@@ -77,6 +85,8 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
 
 pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
 
+pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD;
+
 pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
@@ -97,7 +107,7 @@ pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
 
 pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
 
-pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(?*FILETIME) void;
+pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(*FILETIME) void;
 
 pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: SIZE_T, dwMaximumSize: SIZE_T) ?HANDLE;
 pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL;
@@ -131,6 +141,8 @@ pub extern "kernel32" stdcallcc fn ReadFile(
     in_out_lpOverlapped: ?*OVERLAPPED,
 ) BOOL;
 
+pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL;
+
 pub extern "kernel32" stdcallcc fn SetFilePointerEx(
     in_fFile: HANDLE,
     in_liDistanceToMove: LARGE_INTEGER,
@@ -196,7 +208,6 @@ pub const UNICODE = false;
 pub const WCHAR = u16;
 pub const WORD = u16;
 pub const LARGE_INTEGER = i64;
-pub const FILETIME = i64;
 
 pub const TRUE = 1;
 pub const FALSE = 0;
@@ -212,6 +223,8 @@ pub const STD_ERROR_HANDLE = @maxValue(DWORD) - 12 + 1;
 
 pub const INVALID_HANDLE_VALUE = @intToPtr(HANDLE, @maxValue(usize));
 
+pub const INVALID_FILE_ATTRIBUTES = DWORD(@maxValue(DWORD));
+
 pub const OVERLAPPED = extern struct {
     Internal: ULONG_PTR,
     InternalHigh: ULONG_PTR,
@@ -293,13 +306,24 @@ pub const OPEN_EXISTING = 3;
 pub const TRUNCATE_EXISTING = 5;
 
 pub const FILE_ATTRIBUTE_ARCHIVE = 0x20;
+pub const FILE_ATTRIBUTE_COMPRESSED = 0x800;
+pub const FILE_ATTRIBUTE_DEVICE = 0x40;
+pub const FILE_ATTRIBUTE_DIRECTORY = 0x10;
 pub const FILE_ATTRIBUTE_ENCRYPTED = 0x4000;
 pub const FILE_ATTRIBUTE_HIDDEN = 0x2;
+pub const FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x8000;
 pub const FILE_ATTRIBUTE_NORMAL = 0x80;
+pub const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000;
+pub const FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x20000;
 pub const FILE_ATTRIBUTE_OFFLINE = 0x1000;
 pub const FILE_ATTRIBUTE_READONLY = 0x1;
+pub const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x400000;
+pub const FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x40000;
+pub const FILE_ATTRIBUTE_REPARSE_POINT = 0x400;
+pub const FILE_ATTRIBUTE_SPARSE_FILE = 0x200;
 pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
 pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
+pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000;
 
 pub const PROCESS_INFORMATION = extern struct {
     hProcess: HANDLE,
@@ -372,6 +396,20 @@ pub const HEAP_NO_SERIALIZE = 0x00000001;
 pub const PTHREAD_START_ROUTINE = extern fn (LPVOID) DWORD;
 pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE;
 
-test "import" {
-    _ = @import("util.zig");
-}
+pub const WIN32_FIND_DATAA = extern struct {
+    dwFileAttributes: DWORD,
+    ftCreationTime: FILETIME,
+    ftLastAccessTime: FILETIME,
+    ftLastWriteTime: FILETIME,
+    nFileSizeHigh: DWORD,
+    nFileSizeLow: DWORD,
+    dwReserved0: DWORD,
+    dwReserved1: DWORD,
+    cFileName: [260]CHAR,
+    cAlternateFileName: [14]CHAR,
+};
+
+pub const FILETIME = extern struct {
+    dwLowDateTime: DWORD,
+    dwHighDateTime: DWORD,
+};
std/os/windows/util.zig
@@ -170,3 +170,41 @@ test "InvalidDll" {
         return;
     };
 }
+
+
+pub fn windowsFindFirstFile(allocator: *mem.Allocator, dir_path: []const u8,
+    find_file_data: *windows.WIN32_FIND_DATAA) !windows.HANDLE
+{
+    const wild_and_null = []u8{'\\', '*', 0};
+    const path_with_wild_and_null = try allocator.alloc(u8, dir_path.len + wild_and_null.len);
+    defer allocator.free(path_with_wild_and_null);
+
+    mem.copy(u8, path_with_wild_and_null, dir_path);
+    mem.copy(u8, path_with_wild_and_null[dir_path.len..], wild_and_null);
+
+    const handle = windows.FindFirstFileA(path_with_wild_and_null.ptr, find_file_data);
+
+    if (handle == windows.INVALID_HANDLE_VALUE) {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND,
+            windows.ERROR.PATH_NOT_FOUND,
+            => return error.PathNotFound,
+            else => return os.unexpectedErrorWindows(err),
+        }
+    }
+
+    return handle;
+} 
+
+/// Returns `true` if there was another file, `false` otherwise.
+pub fn windowsFindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAA) !bool {
+    if (windows.FindNextFileA(handle, find_file_data) == 0) {
+        const err = windows.GetLastError();
+        return switch (err) {
+            windows.ERROR.NO_MORE_FILES => false,
+            else => os.unexpectedErrorWindows(err),
+        };
+    }
+    return true;
+} 
std/os/file.zig
@@ -96,7 +96,20 @@ pub const File = struct {
         return File{ .handle = handle };
     }
 
-    pub fn access(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) !bool {
+    pub const AccessError = error {
+        PermissionDenied,
+        NotFound,
+        NameTooLong,
+        BadMode,
+        BadPathName,
+        Io,
+        SystemResources,
+        OutOfMemory,
+
+        Unexpected,
+    };
+
+    pub fn access(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) AccessError!bool {
         const path_with_null = try std.cstr.addNullByte(allocator, path);
         defer allocator.free(path_with_null);
 
@@ -123,8 +136,7 @@ pub const File = struct {
             }
             return true;
         } else if (is_windows) {
-            // TODO do not depend on shlwapi.dll
-            if (os.windows.PathFileExistsA(path_with_null.ptr) == os.windows.TRUE) {
+            if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {
                 return true;
             }
 
std/os/index.zig
@@ -734,7 +734,23 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
     }
 }
 
-pub fn deleteFile(allocator: *Allocator, file_path: []const u8) !void {
+pub const DeleteFileError = error {
+    FileNotFound,
+    AccessDenied,
+    FileBusy,
+    FileSystem,
+    IsDir,
+    SymLinkLoop,
+    NameTooLong,
+    NotDir,
+    SystemResources,
+    ReadOnlyFileSystem,
+    OutOfMemory,
+
+    Unexpected,
+};
+
+pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void {
     if (builtin.os == Os.windows) {
         return deleteFileWindows(allocator, file_path);
     } else {
@@ -1019,37 +1035,67 @@ pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
     }
 }
 
+pub const DeleteDirError = error {
+    AccessDenied,
+    FileBusy,
+    SymLinkLoop,
+    NameTooLong,
+    FileNotFound,
+    SystemResources,
+    NotDir,
+    DirNotEmpty,
+    ReadOnlyFileSystem,
+    OutOfMemory,
+
+    Unexpected,
+};
+
 /// Returns ::error.DirNotEmpty if the directory is not empty.
 /// To delete a directory recursively, see ::deleteTree
-pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) !void {
+pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) DeleteDirError!void {
     const path_buf = try allocator.alloc(u8, dir_path.len + 1);
     defer allocator.free(path_buf);
 
     mem.copy(u8, path_buf, dir_path);
     path_buf[dir_path.len] = 0;
 
-    const err = posix.getErrno(posix.rmdir(path_buf.ptr));
-    if (err > 0) {
-        return switch (err) {
-            posix.EACCES, posix.EPERM => error.AccessDenied,
-            posix.EBUSY => error.FileBusy,
-            posix.EFAULT, posix.EINVAL => unreachable,
-            posix.ELOOP => error.SymLinkLoop,
-            posix.ENAMETOOLONG => error.NameTooLong,
-            posix.ENOENT => error.FileNotFound,
-            posix.ENOMEM => error.SystemResources,
-            posix.ENOTDIR => error.NotDir,
-            posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty,
-            posix.EROFS => error.ReadOnlyFileSystem,
-            else => unexpectedErrorPosix(err),
-        };
+    switch (builtin.os) {
+        Os.windows => {
+            if (windows.RemoveDirectoryA(path_buf.ptr) == 0) {
+                const err = windows.GetLastError();
+                return switch (err) {
+                    windows.ERROR.PATH_NOT_FOUND => error.FileNotFound,
+                    windows.ERROR.DIR_NOT_EMPTY => error.DirNotEmpty,
+                    else => unexpectedErrorWindows(err),
+                };
+            }
+        },
+        Os.linux, Os.macosx, Os.ios => {
+            const err = posix.getErrno(posix.rmdir(path_buf.ptr));
+            if (err > 0) {
+                return switch (err) {
+                    posix.EACCES, posix.EPERM => error.AccessDenied,
+                    posix.EBUSY => error.FileBusy,
+                    posix.EFAULT, posix.EINVAL => unreachable,
+                    posix.ELOOP => error.SymLinkLoop,
+                    posix.ENAMETOOLONG => error.NameTooLong,
+                    posix.ENOENT => error.FileNotFound,
+                    posix.ENOMEM => error.SystemResources,
+                    posix.ENOTDIR => error.NotDir,
+                    posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty,
+                    posix.EROFS => error.ReadOnlyFileSystem,
+                    else => unexpectedErrorPosix(err),
+                };
+            }
+        },
+        else => @compileError("unimplemented"),
     }
+
 }
 
 /// Whether ::full_path describes a symlink, file, or directory, this function
 /// removes it. If it cannot be removed because it is a non-empty directory,
 /// this function recursively removes its entries and then tries again.
-/// TODO non-recursive implementation
 const DeleteTreeError = error{
     OutOfMemory,
     AccessDenied,
@@ -1128,7 +1174,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
                 try full_entry_buf.resize(full_path.len + entry.name.len + 1);
                 const full_entry_path = full_entry_buf.toSlice();
                 mem.copy(u8, full_entry_path, full_path);
-                full_entry_path[full_path.len] = '/';
+                full_entry_path[full_path.len] = path.sep;
                 mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name);
 
                 try deleteTree(allocator, full_entry_path);
@@ -1139,16 +1185,29 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
 }
 
 pub const Dir = struct {
-    fd: i32,
-    darwin_seek: darwin_seek_t,
+    handle: Handle,
     allocator: *Allocator,
-    buf: []u8,
-    index: usize,
-    end_index: usize,
 
-    const darwin_seek_t = switch (builtin.os) {
-        Os.macosx, Os.ios => i64,
-        else => void,
+    pub const Handle = switch (builtin.os) {
+        Os.macosx, Os.ios => struct {
+            fd: i32,
+            seek: i64,
+            buf: []u8,
+            index: usize,
+            end_index: usize,
+        },
+        Os.linux => struct {
+            fd: i32,
+            buf: []u8,
+            index: usize,
+            end_index: usize,
+        },
+        Os.windows => struct {
+            handle: windows.HANDLE,
+            find_file_data: windows.WIN32_FIND_DATAA,
+            first: bool,
+        },
+        else => @compileError("unimplemented"),
     };
 
     pub const Entry = struct {
@@ -1168,81 +1227,117 @@ pub const Dir = struct {
         };
     };
 
-    pub fn open(allocator: *Allocator, dir_path: []const u8) !Dir {
-        const fd = switch (builtin.os) {
-            Os.windows => @compileError("TODO support Dir.open for windows"),
-            Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0),
-            Os.macosx, Os.ios => try posixOpen(
-                allocator,
-                dir_path,
-                posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
-                0,
-            ),
-            else => @compileError("Dir.open is not supported for this platform"),
-        };
-        const darwin_seek_init = switch (builtin.os) {
-            Os.macosx, Os.ios => 0,
-            else => {},
-        };
+    pub const OpenError = error {
+        PathNotFound,
+        NotDir,
+        AccessDenied,
+        FileTooBig,
+        IsDir,
+        SymLinkLoop,
+        ProcessFdQuotaExceeded,
+        NameTooLong,
+        SystemFdQuotaExceeded,
+        NoDevice,
+        SystemResources,
+        NoSpaceLeft,
+        PathAlreadyExists,
+        OutOfMemory,
+
+        Unexpected,
+    };
+
+    pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
         return Dir{
             .allocator = allocator,
-            .fd = fd,
-            .darwin_seek = darwin_seek_init,
-            .index = 0,
-            .end_index = 0,
-            .buf = []u8{},
+            .handle = switch (builtin.os) {
+                Os.windows => blk: {
+                    var find_file_data: windows.WIN32_FIND_DATAA = undefined;
+                    const handle = try windows_util.windowsFindFirstFile(allocator, dir_path, &find_file_data);
+                    break :blk Handle {
+                        .handle = handle,
+                        .find_file_data = find_file_data, // TODO guaranteed copy elision
+                        .first = true,
+                    };
+                },
+                Os.macosx, Os.ios => Handle {
+                    .fd = try posixOpen(
+                        allocator,
+                        dir_path,
+                        posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
+                        0,
+                    ),
+                    .seek = 0,
+                    .index = 0,
+                    .end_index = 0,
+                    .buf = []u8{},
+                },
+                Os.linux => Handle {
+                    .fd = try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0,),
+                    .index = 0,
+                    .end_index = 0,
+                    .buf = []u8{},
+                },
+                else => @compileError("unimplemented"),
+            },
         };
     }
 
     pub fn close(self: *Dir) void {
-        self.allocator.free(self.buf);
-        os.close(self.fd);
+        switch (builtin.os) {
+            Os.windows => {
+                _ = windows.FindClose(self.handle.handle);
+            },
+            Os.macosx, Os.ios, Os.linux => {
+                self.allocator.free(self.handle.buf);
+                os.close(self.handle.fd);
+            },
+            else => @compileError("unimplemented"),
+        }
     }
 
     /// Memory such as file names referenced in this returned entry becomes invalid
-    /// with subsequent calls to next, as well as when this ::Dir is deinitialized.
+    /// with subsequent calls to next, as well as when this `Dir` is deinitialized.
     pub fn next(self: *Dir) !?Entry {
         switch (builtin.os) {
             Os.linux => return self.nextLinux(),
             Os.macosx, Os.ios => return self.nextDarwin(),
             Os.windows => return self.nextWindows(),
-            else => @compileError("Dir.next not supported on " ++ @tagName(builtin.os)),
+            else => @compileError("unimplemented"),
         }
     }
 
     fn nextDarwin(self: *Dir) !?Entry {
         start_over: while (true) {
-            if (self.index >= self.end_index) {
-                if (self.buf.len == 0) {
-                    self.buf = try self.allocator.alloc(u8, page_size);
+            if (self.handle.index >= self.handle.end_index) {
+                if (self.handle.buf.len == 0) {
+                    self.handle.buf = try self.allocator.alloc(u8, page_size);
                 }
 
                 while (true) {
-                    const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len, &self.darwin_seek);
+                    const result = posix.getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek);
                     const err = posix.getErrno(result);
                     if (err > 0) {
                         switch (err) {
                             posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
                             posix.EINVAL => {
-                                self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
+                                self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2);
                                 continue;
                             },
                             else => return unexpectedErrorPosix(err),
                         }
                     }
                     if (result == 0) return null;
-                    self.index = 0;
-                    self.end_index = result;
+                    self.handle.index = 0;
+                    self.handle.end_index = result;
                     break;
                 }
             }
-            const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.buf[self.index]);
-            const next_index = self.index + darwin_entry.d_reclen;
-            self.index = next_index;
+            const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]);
+            const next_index = self.handle.index + darwin_entry.d_reclen;
+            self.handle.index = next_index;
 
             const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
 
-            // skip . and .. entries
             if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
                 continue :start_over;
             }
@@ -1266,38 +1361,59 @@ pub const Dir = struct {
     }
 
     fn nextWindows(self: *Dir) !?Entry {
-        @compileError("TODO support Dir.next for windows");
+        while (true) {
+            if (self.handle.first) {
+                self.handle.first = false;
+            } else {
+                if (!try windows_util.windowsFindNextFile(self.handle.handle, &self.handle.find_file_data))
+                    return null;
+            }
+            const name = std.cstr.toSlice(self.handle.find_file_data.cFileName[0..].ptr);
+            if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
+                continue;
+            const kind = blk: {
+                const attrs = self.handle.find_file_data.dwFileAttributes;
+                if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
+                if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
+                if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File;
+                break :blk Entry.Kind.Unknown;
+            };
+            return Entry {
+                .name = name,
+                .kind = kind,
+            };
+        }
     }
 
     fn nextLinux(self: *Dir) !?Entry {
         start_over: while (true) {
-            if (self.index >= self.end_index) {
-                if (self.buf.len == 0) {
-                    self.buf = try self.allocator.alloc(u8, page_size);
+            if (self.handle.index >= self.handle.end_index) {
+                if (self.handle.buf.len == 0) {
+                    self.handle.buf = try self.allocator.alloc(u8, page_size);
                 }
 
                 while (true) {
-                    const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
+                    const result = posix.getdents(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len);
                     const err = posix.getErrno(result);
                     if (err > 0) {
                         switch (err) {
                             posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
                             posix.EINVAL => {
-                                self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
+                                self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2);
                                 continue;
                             },
                             else => return unexpectedErrorPosix(err),
                         }
                     }
                     if (result == 0) return null;
-                    self.index = 0;
-                    self.end_index = result;
+                    self.handle.index = 0;
+                    self.handle.end_index = result;
                     break;
                 }
             }
-            const linux_entry = @ptrCast(*align(1) posix.dirent, &self.buf[self.index]);
-            const next_index = self.index + linux_entry.d_reclen;
-            self.index = next_index;
+            const linux_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]);
+            const next_index = self.handle.index + linux_entry.d_reclen;
+            self.handle.index = next_index;
 
             const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name));
 
@@ -1306,7 +1422,7 @@ pub const Dir = struct {
                 continue :start_over;
             }
 
-            const type_char = self.buf[next_index - 1];
+            const type_char = self.handle.buf[next_index - 1];
             const entry_kind = switch (type_char) {
                 posix.DT_BLK => Entry.Kind.BlockDevice,
                 posix.DT_CHR => Entry.Kind.CharacterDevice,
std/os/test.zig
@@ -10,11 +10,6 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
 const AtomicOrder = builtin.AtomicOrder;
 
 test "makePath, put some files in it, deleteTree" {
-    if (builtin.os == builtin.Os.windows) {
-        // TODO implement os.Dir for windows
-        // https://github.com/ziglang/zig/issues/709
-        return;
-    }
     try os.makePath(a, "os_test_tmp/b/c");
     try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense");
     try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah");
@@ -27,10 +22,6 @@ test "makePath, put some files in it, deleteTree" {
 }
 
 test "access file" {
-    if (builtin.os == builtin.Os.windows) {
-        return;
-    }
-
     try os.makePath(a, "os_test_tmp");
     if (os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) |ok| {
         unreachable;
std/os/time.zig
@@ -68,11 +68,13 @@ pub const milliTimestamp = switch (builtin.os) {
 fn milliTimestampWindows() u64 {
     //FileTime has a granularity of 100 nanoseconds
     //  and uses the NTFS/Windows epoch
-    var ft: i64 = undefined;
+    var ft: windows.FILETIME = undefined;
     windows.GetSystemTimeAsFileTime(&ft);
     const hns_per_ms = (ns_per_s / 100) / ms_per_s;
     const epoch_adj = epoch.windows * ms_per_s;
-    return u64(@divFloor(ft, hns_per_ms) + epoch_adj);
+
+    const ft64 = (u64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+    return @divFloor(ft64, hns_per_ms) - - epoch_adj;
 }
 
 fn milliTimestampDarwin() u64 {