From 3dd9af9948db696362aa5f41481dc4cb034bc6c2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Jun 2018 01:55:08 -0400 Subject: implement std.os.Dir for windows improve std.os.File.access so that it does not depend on shlwapi.dll closes #1084 --- std/os/file.zig | 18 +++- std/os/index.zig | 266 ++++++++++++++++++++++++++++++++++------------- std/os/test.zig | 9 -- std/os/time.zig | 6 +- std/os/windows/index.zig | 48 ++++++++- std/os/windows/util.zig | 38 +++++++ 6 files changed, 291 insertions(+), 94 deletions(-) (limited to 'std') diff --git a/std/os/file.zig b/std/os/file.zig index f15fa77688..e09c03c08b 100644 --- a/std/os/file.zig +++ b/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; } diff --git a/std/os/index.zig b/std/os/index.zig index 6a13ff94d4..b8907776c5 100644 --- a/std/os/index.zig +++ b/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, diff --git a/std/os/test.zig b/std/os/test.zig index 4aa3535829..5a977a569a 100644 --- a/std/os/test.zig +++ b/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; diff --git a/std/os/time.zig b/std/os/time.zig index dd64df2156..43a584d936 100644 --- a/std/os/time.zig +++ b/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 { diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index 0934c3fd90..d631c6adbf 100644 --- a/std/os/windows/index.zig +++ b/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, +}; diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig index f93a673be0..0f0a190ed1 100644 --- a/std/os/windows/util.zig +++ b/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; +} -- cgit v1.2.3