mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-20 04:03:57 +00:00
Improvements to NtQueryDirectoryFileEx (#235)
This PR aims to: - [Stub FileBasicInformation in NtSetInformationFile](6d0ad1dd61) - [Make sure a root `windows_path` have slash at the end](02ed4fbb03), this was necessary because `directory_iterator` fails when the path is `C:` (without slash).. - [Improve NtQueryDirectoryFileEx](f6ec1fc9cc) by adding filename filtering support, including more basic file information in the struct, and fixing an issue that prevented the last chunk of files from being enumerated correctly.
This commit is contained in:
@@ -41,9 +41,7 @@ namespace utils
|
||||
|
||||
bool operator()(const std::string_view lhs, const std::string_view rhs) const
|
||||
{
|
||||
return std::ranges::equal(lhs, rhs, [](const char c1, const char c2) {
|
||||
return string::char_to_lower(c1) == string::char_to_lower(c2);
|
||||
});
|
||||
return string::equals_ignore_case(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -149,4 +149,20 @@ namespace utils::string
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
template <class Elem, class Traits, class Alloc>
|
||||
bool equals_ignore_case(const std::basic_string<Elem, Traits, Alloc>& lhs,
|
||||
const std::basic_string<Elem, Traits, Alloc>& rhs)
|
||||
{
|
||||
return std::ranges::equal(lhs, rhs,
|
||||
[](const auto c1, const auto c2) { return char_to_lower(c1) == char_to_lower(c2); });
|
||||
}
|
||||
|
||||
template <class Elem, class Traits>
|
||||
bool equals_ignore_case(const std::basic_string_view<Elem, Traits>& lhs,
|
||||
const std::basic_string_view<Elem, Traits>& rhs)
|
||||
{
|
||||
return std::ranges::equal(lhs, rhs,
|
||||
[](const auto c1, const auto c2) { return char_to_lower(c1) == char_to_lower(c2); });
|
||||
}
|
||||
}
|
||||
|
||||
99
src/common/utils/wildcard.hpp
Normal file
99
src/common/utils/wildcard.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace utils::wildcard
|
||||
{
|
||||
inline bool is_wildcard(char16_t c)
|
||||
{
|
||||
return c == '*' || c == '?' || c == '>' || c == '<' || c == '\"';
|
||||
}
|
||||
|
||||
inline bool has_wildcard(const std::u16string_view mask)
|
||||
{
|
||||
return std::ranges::any_of(mask, is_wildcard);
|
||||
}
|
||||
|
||||
inline bool match_filename(std::u16string_view name, std::u16string_view mask)
|
||||
{
|
||||
if (mask.empty() || mask == u"*" || mask == u"*.*")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t name_pos = 0;
|
||||
size_t mask_pos = 0;
|
||||
|
||||
size_t star_mask_pos = std::u16string_view::npos;
|
||||
size_t star_name_pos = 0;
|
||||
|
||||
while (name_pos < name.size())
|
||||
{
|
||||
if (mask_pos < mask.size())
|
||||
{
|
||||
char16_t mask_char = mask[mask_pos];
|
||||
char16_t name_char = name[name_pos];
|
||||
|
||||
bool char_matches = false;
|
||||
if (mask_char == u'?' || mask_char == u'>')
|
||||
{
|
||||
char_matches = true;
|
||||
}
|
||||
else if (mask_char == u'"')
|
||||
{
|
||||
char_matches = name_char == u'.';
|
||||
}
|
||||
else
|
||||
{
|
||||
char_matches = string::char_to_lower(name_char) == string::char_to_lower(mask_char);
|
||||
}
|
||||
|
||||
// Advance if current characters match
|
||||
if (char_matches)
|
||||
{
|
||||
name_pos++;
|
||||
mask_pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a wildcard, skip all consecutive wildcards and save position for backtracking
|
||||
if (mask[mask_pos] == u'*' || mask[mask_pos] == u'<')
|
||||
{
|
||||
mask_pos++;
|
||||
while (mask_pos < mask.size() && (mask[mask_pos] == u'*' || mask[mask_pos] == u'<'))
|
||||
{
|
||||
mask_pos++;
|
||||
}
|
||||
|
||||
if (mask_pos == mask.size())
|
||||
{
|
||||
// There is no need to continue because all that remained were star masks.
|
||||
return true;
|
||||
}
|
||||
|
||||
star_mask_pos = mask_pos;
|
||||
star_name_pos = name_pos;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The current characters didn't match...
|
||||
// If we had a wildcard earlier, backtrack to it and try to match at the next position
|
||||
if (star_mask_pos != std::u16string_view::npos)
|
||||
{
|
||||
mask_pos = star_mask_pos;
|
||||
name_pos = ++star_name_pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip any remaining wildcards in the mask
|
||||
while (mask_pos < mask.size() && (mask[mask_pos] == u'*' || mask[mask_pos] == u'<'))
|
||||
{
|
||||
mask_pos++;
|
||||
}
|
||||
|
||||
return mask_pos == mask.size();
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,14 @@ namespace syscalls
|
||||
emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
|
||||
uint64_t file_information, uint32_t length, uint32_t info_class,
|
||||
ULONG query_flags,
|
||||
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*file_name*/);
|
||||
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name);
|
||||
NTSTATUS handle_NtQueryDirectoryFile(const syscall_context& c, handle file_handle, handle event_handle,
|
||||
emulator_pointer /*PIO_APC_ROUTINE*/ apc_routine, emulator_pointer apc_context,
|
||||
emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
|
||||
uint64_t file_information, uint32_t length, uint32_t info_class,
|
||||
BOOLEAN return_single_entry,
|
||||
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name,
|
||||
BOOLEAN restart_scan);
|
||||
NTSTATUS handle_NtQueryInformationFile(const syscall_context& c, handle file_handle,
|
||||
emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
|
||||
uint64_t file_information, uint32_t length, uint32_t info_class);
|
||||
@@ -831,6 +838,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
|
||||
add_handler(NtSetInformationKey);
|
||||
add_handler(NtUserGetKeyboardLayout);
|
||||
add_handler(NtQueryDirectoryFileEx);
|
||||
add_handler(NtQueryDirectoryFile);
|
||||
add_handler(NtUserSystemParametersInfo);
|
||||
add_handler(NtGetContextThread);
|
||||
add_handler(NtYieldExecution);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <utils/finally.hpp>
|
||||
#include <utils/wildcard.hpp>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -21,6 +22,11 @@ namespace syscalls
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (info_class == FileBasicInformation)
|
||||
{
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (info_class == FilePositionInformation)
|
||||
{
|
||||
if (!f->handle)
|
||||
@@ -99,17 +105,27 @@ namespace syscalls
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<file_entry> scan_directory(const std::filesystem::path& dir)
|
||||
std::vector<file_entry> scan_directory(const std::filesystem::path& dir, const std::u16string_view file_mask)
|
||||
{
|
||||
std::vector<file_entry> files{
|
||||
{"."},
|
||||
{".."},
|
||||
};
|
||||
std::vector<file_entry> files{};
|
||||
|
||||
if (file_mask.empty() || file_mask == u"*")
|
||||
{
|
||||
files.emplace_back(file_entry{.file_path = ".", .is_directory = true});
|
||||
files.emplace_back(file_entry{.file_path = "..", .is_directory = true});
|
||||
}
|
||||
|
||||
for (const auto& file : std::filesystem::directory_iterator(dir))
|
||||
{
|
||||
if (!file_mask.empty() && !utils::wildcard::match_filename(file.path().filename().u16string(), file_mask))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
files.emplace_back(file_entry{
|
||||
.file_path = file.path().filename(),
|
||||
.file_size = file.file_size(),
|
||||
.is_directory = file.is_directory(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,12 +136,13 @@ namespace syscalls
|
||||
NTSTATUS handle_file_enumeration(const syscall_context& c,
|
||||
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
|
||||
const uint64_t file_information, const uint32_t length, const ULONG query_flags,
|
||||
file* f)
|
||||
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_mask, file* f)
|
||||
{
|
||||
if (!f->enumeration_state || query_flags & SL_RESTART_SCAN)
|
||||
{
|
||||
f->enumeration_state.emplace(file_enumeration_state{});
|
||||
f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name));
|
||||
f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name),
|
||||
file_mask ? read_unicode_string(c.emu, file_mask) : u"");
|
||||
}
|
||||
|
||||
auto& enum_state = *f->enumeration_state;
|
||||
@@ -135,13 +152,13 @@ namespace syscalls
|
||||
|
||||
size_t current_index = enum_state.current_index;
|
||||
|
||||
if (current_index >= enum_state.files.size())
|
||||
{
|
||||
return STATUS_NO_MORE_FILES;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (current_index >= enum_state.files.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const auto new_offset = align_up(current_offset, 8);
|
||||
const auto& current_file = enum_state.files[current_index];
|
||||
const auto file_name = current_file.file_path.u16string();
|
||||
@@ -175,7 +192,12 @@ namespace syscalls
|
||||
info.NextEntryOffset = 0;
|
||||
info.FileIndex = static_cast<ULONG>(current_index);
|
||||
info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
||||
if (current_file.is_directory)
|
||||
{
|
||||
info.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
||||
}
|
||||
info.FileNameLength = static_cast<ULONG>(file_name.size() * 2);
|
||||
info.EndOfFile.QuadPart = current_file.file_size;
|
||||
|
||||
object.set_address(file_information + new_offset);
|
||||
object.write(info);
|
||||
@@ -184,7 +206,7 @@ namespace syscalls
|
||||
|
||||
++current_index;
|
||||
current_offset = end_offset;
|
||||
} while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0);
|
||||
} while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0 && current_index < enum_state.files.size());
|
||||
|
||||
if ((query_flags & SL_NO_CURSOR_UPDATE) == 0)
|
||||
{
|
||||
@@ -195,7 +217,7 @@ namespace syscalls
|
||||
block.Information = current_offset;
|
||||
io_status_block.write(block);
|
||||
|
||||
return current_index < enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES;
|
||||
return current_index <= enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtQueryDirectoryFileEx(
|
||||
@@ -203,7 +225,7 @@ namespace syscalls
|
||||
const emulator_pointer /*PIO_APC_ROUTINE*/ /*apc_routine*/, const emulator_pointer /*apc_context*/,
|
||||
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block, const uint64_t file_information,
|
||||
const uint32_t length, const uint32_t info_class, const ULONG query_flags,
|
||||
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*file_name*/)
|
||||
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name)
|
||||
{
|
||||
auto* f = c.proc.files.get(file_handle);
|
||||
if (!f || !f->is_directory())
|
||||
@@ -214,19 +236,19 @@ namespace syscalls
|
||||
if (info_class == FileDirectoryInformation)
|
||||
{
|
||||
return handle_file_enumeration<FILE_DIRECTORY_INFORMATION>(c, io_status_block, file_information, length,
|
||||
query_flags, f);
|
||||
query_flags, file_name, f);
|
||||
}
|
||||
|
||||
if (info_class == FileFullDirectoryInformation)
|
||||
{
|
||||
return handle_file_enumeration<FILE_FULL_DIR_INFORMATION>(c, io_status_block, file_information, length,
|
||||
query_flags, f);
|
||||
query_flags, file_name, f);
|
||||
}
|
||||
|
||||
if (info_class == FileBothDirectoryInformation)
|
||||
{
|
||||
return handle_file_enumeration<FILE_BOTH_DIR_INFORMATION>(c, io_status_block, file_information, length,
|
||||
query_flags, f);
|
||||
query_flags, file_name, f);
|
||||
}
|
||||
|
||||
c.win_emu.log.error("Unsupported query directory file info class: %X\n", info_class);
|
||||
@@ -235,6 +257,28 @@ namespace syscalls
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtQueryDirectoryFile(const syscall_context& c, const handle file_handle, const handle event_handle,
|
||||
const emulator_pointer /*PIO_APC_ROUTINE*/ apc_routine,
|
||||
const emulator_pointer apc_context,
|
||||
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
|
||||
const uint64_t file_information, const uint32_t length,
|
||||
const uint32_t info_class, const BOOLEAN return_single_entry,
|
||||
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name,
|
||||
const BOOLEAN restart_scan)
|
||||
{
|
||||
ULONG query_flags = 0;
|
||||
if (return_single_entry)
|
||||
{
|
||||
query_flags |= SL_RETURN_SINGLE_ENTRY;
|
||||
}
|
||||
if (restart_scan)
|
||||
{
|
||||
query_flags |= SL_RESTART_SCAN;
|
||||
}
|
||||
return handle_NtQueryDirectoryFileEx(c, file_handle, event_handle, apc_routine, apc_context, io_status_block,
|
||||
file_information, length, info_class, query_flags, file_name);
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtQueryInformationFile(
|
||||
const syscall_context& c, const handle file_handle,
|
||||
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block, const uint64_t file_information,
|
||||
@@ -599,21 +643,24 @@ namespace syscalls
|
||||
|
||||
printer.cancel();
|
||||
|
||||
if (f.name.ends_with(u"\\") || create_options & FILE_DIRECTORY_FILE)
|
||||
const windows_path path = f.name;
|
||||
const bool is_directory = std::filesystem::is_directory(c.win_emu.file_sys.translate(path));
|
||||
|
||||
if (is_directory || create_options & FILE_DIRECTORY_FILE)
|
||||
{
|
||||
c.win_emu.log.print(color::dark_gray, "--> Opening folder: %s\n", u16_to_u8(f.name).c_str());
|
||||
|
||||
if (create_disposition & FILE_CREATE)
|
||||
{
|
||||
std::error_code ec{};
|
||||
create_directory(c.win_emu.file_sys.translate(f.name), ec);
|
||||
create_directory(c.win_emu.file_sys.translate(path), ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
return STATUS_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
else if (!is_directory(c.win_emu.file_sys.translate(f.name)))
|
||||
else if (!is_directory)
|
||||
{
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
}
|
||||
@@ -626,7 +673,6 @@ namespace syscalls
|
||||
|
||||
c.win_emu.log.print(color::dark_gray, "--> Opening file: %s\n", u16_to_u8(f.name).c_str());
|
||||
|
||||
const windows_path path = f.name;
|
||||
std::u16string mode = map_mode(desired_access, create_disposition);
|
||||
|
||||
if (mode.empty() || path.is_relative())
|
||||
|
||||
@@ -94,15 +94,21 @@ struct mutant : ref_counted_object
|
||||
struct file_entry
|
||||
{
|
||||
std::filesystem::path file_path{};
|
||||
uint64_t file_size{};
|
||||
bool is_directory{};
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->file_path);
|
||||
buffer.write(this->file_size);
|
||||
buffer.write(this->is_directory);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->file_path);
|
||||
buffer.read(this->file_size);
|
||||
buffer.read(this->is_directory);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -117,6 +117,11 @@ class windows_path
|
||||
path.append(folder);
|
||||
}
|
||||
|
||||
if (this->is_absolute() && this->folders_.empty())
|
||||
{
|
||||
path.push_back(u'\\');
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user