From 94601e1ffda6fda555ba71de1793a6b9ebd760af Mon Sep 17 00:00:00 2001 From: Cameron Youell Date: Wed, 30 Aug 2023 17:19:29 +1000 Subject: [PATCH] LibCore: Add Windows version of DirIterator Co-authored-by: stasoid --- AK/Error.cpp | 36 ++++++ AK/Error.h | 7 + Userland/Libraries/LibCore/CMakeLists.txt | 14 +- Userland/Libraries/LibCore/DirIterator.cpp | 33 ++--- Userland/Libraries/LibCore/DirIterator.h | 6 +- .../Libraries/LibCore/DirIteratorWindows.cpp | 122 ++++++++++++++++++ Userland/Libraries/LibCore/DirectoryEntry.h | 10 +- .../LibCore/DirectoryEntryWindows.cpp | 31 +++++ 8 files changed, 238 insertions(+), 21 deletions(-) create mode 100644 Userland/Libraries/LibCore/DirIteratorWindows.cpp create mode 100644 Userland/Libraries/LibCore/DirectoryEntryWindows.cpp diff --git a/AK/Error.cpp b/AK/Error.cpp index 882c00d350c..7ef941449f8 100644 --- a/AK/Error.cpp +++ b/AK/Error.cpp @@ -1,10 +1,17 @@ /* * Copyright (c) 2023, Liav A. + * Copyright (c) 2023, Cameron Youell + * Copyright (c) 2024, stasoid * * SPDX-License-Identifier: BSD-2-Clause */ #include +#ifdef AK_OS_WINDOWS +# include +# include +# include +#endif namespace AK { @@ -13,4 +20,33 @@ Error Error::from_string_view_or_print_error_and_return_errno(StringView string_ return Error::from_string_view(string_literal); } +#ifdef AK_OS_WINDOWS +Error Error::from_windows_error(DWORD code) +{ + static HashMap windows_errors; + + auto string = windows_errors.get(code); + if (string.has_value()) { + return Error::from_string_view(string->view()); + } else { + char* message = nullptr; + auto size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&message, + 0, + nullptr); + + if (size == 0) + return Error::from_string_view_or_print_error_and_return_errno("Unknown error"sv, code); + + windows_errors.set(code, { message, size }); + LocalFree(message); + return from_windows_error(code); + } +} +#endif + } diff --git a/AK/Error.h b/AK/Error.h index 3d07164eaac..fd2b704432d 100644 --- a/AK/Error.h +++ b/AK/Error.h @@ -11,6 +11,9 @@ #include #include #include +#ifdef AK_OS_WINDOWS +typedef unsigned long DWORD; +#endif namespace AK { @@ -25,6 +28,10 @@ public: return Error(code); } +#ifdef AK_OS_WINDOWS + static Error from_windows_error(DWORD code); +#endif + // NOTE: For calling this method from within kernel code, we will simply print // the error message and return the errno code. // For calling this method from userspace programs, we will simply return from diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index 57b21a7fe02..3b4175b8599 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -5,8 +5,6 @@ include(vulkan) set(SOURCES ArgsParser.cpp Directory.cpp - DirectoryEntry.cpp - DirIterator.cpp Environment.cpp File.cpp StandardPaths.cpp @@ -14,6 +12,18 @@ set(SOURCES Version.cpp ) +if (WIN32) + list(APPEND SOURCES + DirectoryEntryWindows.cpp + DirIteratorWindows.cpp + ) +else() + list(APPEND SOURCES + DirectoryEntry.cpp + DirIterator.cpp + ) +endif() + serenity_lib(LibCoreMinimal coreminimal) if (LAGOM_TOOLS_ONLY) diff --git a/Userland/Libraries/LibCore/DirIterator.cpp b/Userland/Libraries/LibCore/DirIterator.cpp index 76f41a61d74..28df35591f7 100644 --- a/Userland/Libraries/LibCore/DirIterator.cpp +++ b/Userland/Libraries/LibCore/DirIterator.cpp @@ -5,7 +5,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include #include @@ -13,32 +12,36 @@ namespace Core { +struct DirIterator::Impl { + DIR* dir { nullptr }; +}; + DirIterator::DirIterator(ByteString path, Flags flags) - : m_path(move(path)) + : m_impl(make()) + , m_path(move(path)) , m_flags(flags) { - m_dir = opendir(m_path.characters()); - if (!m_dir) { + m_impl->dir = opendir(m_path.characters()); + if (!m_impl->dir) { m_error = Error::from_errno(errno); } } DirIterator::~DirIterator() { - if (m_dir) { - closedir(m_dir); - m_dir = nullptr; + if (m_impl && m_impl->dir) { + closedir(m_impl->dir); + m_impl->dir = nullptr; } } DirIterator::DirIterator(DirIterator&& other) - : m_dir(other.m_dir) + : m_impl(move(other.m_impl)) , m_error(move(other.m_error)) , m_next(move(other.m_next)) , m_path(move(other.m_path)) , m_flags(other.m_flags) { - other.m_dir = nullptr; } static constexpr bool dirent_has_d_type = @@ -50,12 +53,12 @@ static constexpr bool dirent_has_d_type = bool DirIterator::advance_next() { - if (!m_dir) + if (!m_impl || !m_impl->dir) return false; while (true) { errno = 0; - auto* de = readdir(m_dir); + auto* de = readdir(m_impl->dir); if (!de) { if (errno != 0) { m_error = Error::from_errno(errno); @@ -68,7 +71,7 @@ bool DirIterator::advance_next() if constexpr (dirent_has_d_type) m_next = DirectoryEntry::from_dirent(*de); else - m_next = DirectoryEntry::from_stat(m_dir, *de); + m_next = DirectoryEntry::from_stat(m_impl->dir, *de); if (m_next->name.is_empty()) return false; @@ -86,7 +89,7 @@ bool DirIterator::advance_next() // the calling code will be given the raw unknown type. if ((m_flags & Flags::NoStat) == 0 && m_next->type == DirectoryEntry::Type::Unknown) { struct stat statbuf; - if (fstatat(dirfd(m_dir), de->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) < 0) { + if (fstatat(dirfd(m_impl->dir), de->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) < 0) { m_error = Error::from_errno(errno); dbgln("DirIteration error: {}", m_error.value()); return false; @@ -137,9 +140,9 @@ ByteString DirIterator::next_full_path() int DirIterator::fd() const { - if (!m_dir) + if (!m_impl || !m_impl->dir) return -1; - return dirfd(m_dir); + return dirfd(m_impl->dir); } } diff --git a/Userland/Libraries/LibCore/DirIterator.h b/Userland/Libraries/LibCore/DirIterator.h index 262ef5844ae..d6abe550f8f 100644 --- a/Userland/Libraries/LibCore/DirIterator.h +++ b/Userland/Libraries/LibCore/DirIterator.h @@ -8,9 +8,8 @@ #pragma once #include +#include #include -#include -#include namespace Core { @@ -38,7 +37,8 @@ public: int fd() const; private: - DIR* m_dir = nullptr; + struct Impl; + OwnPtr m_impl; Optional m_error; Optional m_next; ByteString m_path; diff --git a/Userland/Libraries/LibCore/DirIteratorWindows.cpp b/Userland/Libraries/LibCore/DirIteratorWindows.cpp new file mode 100644 index 00000000000..db3280e2b66 --- /dev/null +++ b/Userland/Libraries/LibCore/DirIteratorWindows.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023, Cameron Youell + * Copyright (c) 2024, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Core { + +struct DirIterator::Impl { + HANDLE handle { INVALID_HANDLE_VALUE }; + WIN32_FIND_DATA find_data; + bool initialized { false }; +}; + +DirIterator::DirIterator(ByteString path, Flags flags) + : m_impl(make()) + , m_path(move(path)) + , m_flags(flags) +{ +} + +DirIterator::~DirIterator() +{ + if (m_impl && m_impl->handle != INVALID_HANDLE_VALUE) { + FindClose(m_impl->handle); + m_impl->handle = INVALID_HANDLE_VALUE; + } +} + +DirIterator::DirIterator(DirIterator&& other) + : m_impl(move(other.m_impl)) + , m_error(move(other.m_error)) + , m_next(move(other.m_next)) + , m_path(move(other.m_path)) + , m_flags(other.m_flags) +{ +} + +bool DirIterator::advance_next() +{ + if (!m_impl) + return false; + + while (true) { + if (!m_impl->initialized) { + m_impl->initialized = true; + auto path = ByteString::formatted("{}/*", m_path); + m_impl->handle = FindFirstFile(path.characters(), &m_impl->find_data); + if (m_impl->handle == INVALID_HANDLE_VALUE) { + m_error = Error::from_windows_error(GetLastError()); + return false; + } + } else { + if (!FindNextFile(m_impl->handle, &m_impl->find_data)) { + if (GetLastError() != ERROR_NO_MORE_FILES) + m_error = Error::from_windows_error(GetLastError()); + return false; + } + } + + m_next = DirectoryEntry::from_find_data(m_impl->find_data); + + if (m_next->name.is_empty()) + return false; + + if (m_flags & Flags::SkipDots && m_next->name.starts_with('.')) + continue; + + if (m_flags & Flags::SkipParentAndBaseDir && (m_next->name == "." || m_next->name == "..")) + continue; + + return !m_next->name.is_empty(); + } +} + +bool DirIterator::has_next() +{ + if (m_next.has_value()) + return true; + + return advance_next(); +} + +Optional DirIterator::next() +{ + if (!m_next.has_value()) + advance_next(); + + auto result = m_next; + m_next.clear(); + return result; +} + +ByteString DirIterator::next_path() +{ + auto entry = next(); + if (entry.has_value()) + return entry->name; + return ""; +} + +ByteString DirIterator::next_full_path() +{ + StringBuilder builder; + builder.append(m_path); + if (!m_path.ends_with('/')) + builder.append('/'); + builder.append(next_path()); + return builder.to_byte_string(); +} + +int DirIterator::fd() const +{ + dbgln("DirIterator::fd() not implemented"); + VERIFY_NOT_REACHED(); +} + +} diff --git a/Userland/Libraries/LibCore/DirectoryEntry.h b/Userland/Libraries/LibCore/DirectoryEntry.h index c33d4adf750..7c66e4fb8c3 100644 --- a/Userland/Libraries/LibCore/DirectoryEntry.h +++ b/Userland/Libraries/LibCore/DirectoryEntry.h @@ -8,7 +8,11 @@ #include #include -#include +#ifdef AK_OS_WINDOWS +using WIN32_FIND_DATA = struct _WIN32_FIND_DATAA; +#else +# include +#endif namespace Core { @@ -27,6 +31,7 @@ struct DirectoryEntry { Type type; // FIXME: Once we have a special Path string class, use that. ByteString name; +#if !defined(AK_OS_WINDOWS) ino_t inode_number; static StringView posix_name_from_directory_entry_type(Type); @@ -34,6 +39,9 @@ struct DirectoryEntry { static Type directory_entry_type_from_stat(mode_t st_mode); static DirectoryEntry from_dirent(dirent const&); static DirectoryEntry from_stat(DIR*, dirent const&); +#else + static DirectoryEntry from_find_data(WIN32_FIND_DATA const&); +#endif }; } diff --git a/Userland/Libraries/LibCore/DirectoryEntryWindows.cpp b/Userland/Libraries/LibCore/DirectoryEntryWindows.cpp new file mode 100644 index 00000000000..b76fbee31d4 --- /dev/null +++ b/Userland/Libraries/LibCore/DirectoryEntryWindows.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, Cameron Youell + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Core { + +static DirectoryEntry::Type directory_entry_type_from_win32(DWORD file_attributes) +{ + if (file_attributes & FILE_ATTRIBUTE_DIRECTORY) + return DirectoryEntry::Type::Directory; + if (file_attributes & FILE_ATTRIBUTE_DEVICE) + return DirectoryEntry::Type::CharacterDevice; + if (file_attributes & FILE_ATTRIBUTE_REPARSE_POINT) + return DirectoryEntry::Type::SymbolicLink; + return DirectoryEntry::Type::File; +} + +DirectoryEntry DirectoryEntry::from_find_data(WIN32_FIND_DATA const& de) +{ + return DirectoryEntry { + .type = directory_entry_type_from_win32(de.dwFileAttributes), + .name = de.cFileName, + }; +} + +}