From 704dfd90ec990a7fb8f0eda168b59a852278a6d2 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 3 Oct 2025 10:57:43 -0600 Subject: [PATCH] WIP file handles refactor --- access_mask.cpp | 50 +++ access_mask.h | 40 +++ common.h | 33 ++ dll/kernel32/fileapi.cpp | 217 ++++++------- dll/kernel32/fileapi.h | 5 - errors.cpp | 4 + errors.h | 1 + files.cpp | 668 +++++++++++++++++++-------------------- files.h | 35 +- handles.cpp | 93 ++---- handles.h | 83 +++-- 11 files changed, 639 insertions(+), 590 deletions(-) create mode 100644 access_mask.cpp create mode 100644 access_mask.h diff --git a/access_mask.cpp b/access_mask.cpp new file mode 100644 index 0000000..7df3d83 --- /dev/null +++ b/access_mask.cpp @@ -0,0 +1,50 @@ +#include "access_mask.h" + +namespace wibo::access { + +const GenericMapping kFileGenericMapping{FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS}; + +const GenericMapping kDirectoryGenericMapping{ + STANDARD_RIGHTS_READ | FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_TRAVERSE | SYNCHRONIZE, + STANDARD_RIGHTS_WRITE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | SYNCHRONIZE, + STANDARD_RIGHTS_EXECUTE | FILE_TRAVERSE | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ALL_ACCESS | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_TRAVERSE | FILE_DELETE_CHILD}; + +uint32_t mapGenericMask(uint32_t desiredMask, const GenericMapping &mapping) { + uint32_t mask = desiredMask; + if ((mask & GENERIC_ALL) != 0) { + mask = (mask & ~GENERIC_ALL) | mapping.genericAll; + } + if ((mask & GENERIC_READ) != 0) { + mask = (mask & ~GENERIC_READ) | mapping.genericRead; + } + if ((mask & GENERIC_WRITE) != 0) { + mask = (mask & ~GENERIC_WRITE) | mapping.genericWrite; + } + if ((mask & GENERIC_EXECUTE) != 0) { + mask = (mask & ~GENERIC_EXECUTE) | mapping.genericExecute; + } + return mask; +} + +NormalizedAccess normalizeDesiredAccess(uint32_t desiredMask, const GenericMapping &mapping, uint32_t supportedMask, + uint32_t alwaysGrantMask, uint32_t defaultMask) { + NormalizedAccess out{}; + uint32_t requested = mapGenericMask(desiredMask, mapping); + if (requested == 0 && desiredMask == 0 && defaultMask != 0) { + requested = defaultMask; + } + requested |= alwaysGrantMask; + + out.requestedMask = requested; + out.grantedMask = requested & supportedMask; + out.deniedMask = requested & ~supportedMask; + return out; +} + +// handles.cpp: normalizeDesiredAccess(...) can feed HandleTable::create so duplicates never exceed original rights. +// files.cpp: store the NormalizedAccess::grantedMask in FileObject::maxAccessMask (or similar) for later enforcement. +// dll/kernel32/fileapi.cpp: replace meta.grantedAccess & GENERIC_* checks with FileAccessView helpers once rights are +// canonical. + +} // namespace wibo::access diff --git a/access_mask.h b/access_mask.h new file mode 100644 index 0000000..3a7358d --- /dev/null +++ b/access_mask.h @@ -0,0 +1,40 @@ +#pragma once + +#include "common.h" + +#include + +namespace wibo::access { + +// Maps the Win32 generic access bits (GENERIC_* constants) to the +// object-specific rights supplied in the mapping table. +struct GenericMapping { + uint32_t genericRead; + uint32_t genericWrite; + uint32_t genericExecute; + uint32_t genericAll; +}; + +struct NormalizedAccess { + uint32_t requestedMask; // mask after generic expansion + implicit bits + uint32_t grantedMask; // requested & supported + uint32_t deniedMask; // requested & ~supported +}; + +uint32_t mapGenericMask(uint32_t desiredMask, const GenericMapping &mapping); +NormalizedAccess normalizeDesiredAccess(uint32_t desiredMask, const GenericMapping &mapping, uint32_t supportedMask, + uint32_t alwaysGrantMask = 0, uint32_t defaultMask = 0); + +inline bool containsAny(uint32_t mask, uint32_t rights) { return (mask & rights) != 0; } +inline bool containsAll(uint32_t mask, uint32_t rights) { return (mask & rights) == rights; } + +extern const GenericMapping kFileGenericMapping; +extern const GenericMapping kDirectoryGenericMapping; + +constexpr DWORD kFileSpecificRightsMask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_EXECUTE | + FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES; +constexpr DWORD kDirectorySpecificRightsMask = FILE_LIST_DIRECTORY | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | + FILE_TRAVERSE | FILE_DELETE_CHILD | FILE_READ_EA | FILE_WRITE_EA | + FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES; + +} // namespace wibo::access diff --git a/common.h b/common.h index b75fff1..107b441 100644 --- a/common.h +++ b/common.h @@ -148,6 +148,39 @@ constexpr DWORD STD_INPUT_HANDLE = ((DWORD)-10); constexpr DWORD STD_OUTPUT_HANDLE = ((DWORD)-11); constexpr DWORD STD_ERROR_HANDLE = ((DWORD)-12); +constexpr DWORD FILE_READ_DATA = 0x00000001; +constexpr DWORD FILE_LIST_DIRECTORY = 0x00000001; +constexpr DWORD FILE_WRITE_DATA = 0x00000002; +constexpr DWORD FILE_ADD_FILE = 0x00000002; +constexpr DWORD FILE_APPEND_DATA = 0x00000004; +constexpr DWORD FILE_ADD_SUBDIRECTORY = 0x00000004; +constexpr DWORD FILE_CREATE_PIPE_INSTANCE = 0x00000004; +constexpr DWORD FILE_READ_EA = 0x00000008; +constexpr DWORD FILE_WRITE_EA = 0x00000010; +constexpr DWORD FILE_EXECUTE = 0x00000020; +constexpr DWORD FILE_TRAVERSE = 0x00000020; +constexpr DWORD FILE_DELETE_CHILD = 0x00000040; +constexpr DWORD FILE_READ_ATTRIBUTES = 0x00000080; +constexpr DWORD FILE_WRITE_ATTRIBUTES = 0x00000100; + +constexpr DWORD STANDARD_RIGHTS_READ = 0x00020000; +constexpr DWORD STANDARD_RIGHTS_WRITE = 0x00020000; +constexpr DWORD STANDARD_RIGHTS_EXECUTE = 0x00020000; +constexpr DWORD STANDARD_RIGHTS_REQUIRED = 0x000f0000; +constexpr DWORD SYNCHRONIZE = 0x00100000; + +constexpr DWORD FILE_GENERIC_READ = + STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE; +constexpr DWORD FILE_GENERIC_WRITE = + STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE; +constexpr DWORD FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE; +constexpr DWORD FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF; + +constexpr DWORD GENERIC_READ = 0x80000000; +constexpr DWORD GENERIC_WRITE = 0x40000000; +constexpr DWORD GENERIC_EXECUTE = 0x20000000; +constexpr DWORD GENERIC_ALL = 0x10000000; + struct UNICODE_STRING { unsigned short Length; unsigned short MaximumLength; diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index 4dce0aa..8a971fd 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -1,5 +1,7 @@ #include "fileapi.h" +#include "access_mask.h" +#include "common.h" #include "errors.h" #include "files.h" #include "handles.h" @@ -250,20 +252,12 @@ bool tryOpenConsoleDevice(DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCrea return false; } HANDLE baseHandle = files::getStdHandle(*stdHandleKind); - auto *baseFile = files::fileHandleFromHandle(baseHandle); - if (!baseFile) { - wibo::lastError = ERROR_INVALID_HANDLE; - outHandle = INVALID_HANDLE_VALUE; - return true; - } - HANDLE duplicated = files::duplicateFileHandle(baseFile, false); - if (!duplicated) { + if (!wibo::handles().duplicateTo(baseHandle, wibo::handles(), &outHandle, dwDesiredAccess, false, 0)) { wibo::lastError = ERROR_INVALID_HANDLE; outHandle = INVALID_HANDLE_VALUE; return true; } wibo::lastError = ERROR_SUCCESS; - outHandle = duplicated; return true; } @@ -433,85 +427,72 @@ BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr DEBUG_LOG("WriteFile(%p, %u)\n", hFile, nNumberOfBytesToWrite); wibo::lastError = ERROR_SUCCESS; - auto file = files::fileHandleFromHandle(hFile); - if (!file || !file->fp) { + HandleMeta meta{}; + auto file = wibo::handles().getAs(hFile, &meta); + if (!file || !file->valid()) { wibo::lastError = ERROR_INVALID_HANDLE; return FALSE; } + if ((meta.grantedAccess & FILE_WRITE_DATA) == 0) { + wibo::lastError = ERROR_ACCESS_DENIED; + DEBUG_LOG("!!! DENIED: 0x%x\n", meta.grantedAccess); + return FALSE; + } - bool handleOverlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0; - auto *overlapped = reinterpret_cast(lpOverlapped); - bool usingOverlapped = overlapped != nullptr; - if (!usingOverlapped && lpNumberOfBytesWritten == nullptr) { + if (lpOverlapped == nullptr && lpNumberOfBytesWritten == nullptr) { wibo::lastError = ERROR_INVALID_PARAMETER; return FALSE; } - std::optional offset; + std::optional offset; bool updateFilePointer = true; - if (usingOverlapped) { - offset = (static_cast(overlapped->Offset) | (static_cast(overlapped->OffsetHigh) << 32)); - overlapped->Internal = STATUS_PENDING; - overlapped->InternalHigh = 0; - updateFilePointer = !handleOverlapped; - resetOverlappedEvent(overlapped); + if (lpOverlapped != nullptr) { + offset = static_cast((static_cast(lpOverlapped->Offset)) | + (static_cast(lpOverlapped->OffsetHigh) << 32)); + lpOverlapped->Internal = STATUS_PENDING; + lpOverlapped->InternalHigh = 0; + updateFilePointer = !file->overlapped; + resetOverlappedEvent(lpOverlapped); } - auto io = files::write(file, lpBuffer, nNumberOfBytesToWrite, offset, updateFilePointer); + auto io = files::write(file.get(), lpBuffer, nNumberOfBytesToWrite, offset, updateFilePointer); DWORD completionStatus = STATUS_SUCCESS; if (io.unixError != 0) { - completionStatus = wibo::winErrorFromErrno(io.unixError); - wibo::lastError = completionStatus; + completionStatus = wibo::statusFromErrno(io.unixError); + wibo::lastError = wibo::winErrorFromErrno(io.unixError); if (lpNumberOfBytesWritten) { - *lpNumberOfBytesWritten = static_cast(io.bytesTransferred); + *lpNumberOfBytesWritten = io.bytesTransferred; } - if (usingOverlapped) { - overlapped->Internal = completionStatus; - overlapped->InternalHigh = io.bytesTransferred; - signalOverlappedEvent(overlapped); + if (lpOverlapped != nullptr) { + lpOverlapped->Internal = completionStatus; + lpOverlapped->InternalHigh = io.bytesTransferred; + signalOverlappedEvent(lpOverlapped); } return FALSE; } - if (lpNumberOfBytesWritten && (!handleOverlapped || !usingOverlapped)) { + if (lpNumberOfBytesWritten && (!file->overlapped || lpOverlapped == nullptr)) { *lpNumberOfBytesWritten = static_cast(io.bytesTransferred); } - if (usingOverlapped) { - overlapped->Internal = completionStatus; - overlapped->InternalHigh = io.bytesTransferred; - if (!handleOverlapped) { - uint64_t baseOffset = offset.value_or(0); - uint64_t newOffset = baseOffset + io.bytesTransferred; - overlapped->Offset = static_cast(newOffset & 0xFFFFFFFFu); - overlapped->OffsetHigh = static_cast(newOffset >> 32); - } - signalOverlappedEvent(overlapped); + if (lpOverlapped != nullptr) { + lpOverlapped->Internal = completionStatus; + lpOverlapped->InternalHigh = io.bytesTransferred; + signalOverlappedEvent(lpOverlapped); } - return (io.bytesTransferred == nNumberOfBytesToWrite) ? TRUE : FALSE; + return TRUE; } BOOL WIN_FUNC FlushFileBuffers(HANDLE hFile) { DEBUG_LOG("FlushFileBuffers(%p)\n", hFile); - auto data = handles::dataFromHandle(hFile, false); - if (data.type != handles::TYPE_FILE || data.ptr == nullptr) { + auto file = wibo::handles().getAs(hFile); + if (!file || !file->valid()) { wibo::lastError = ERROR_INVALID_HANDLE; return FALSE; } - auto file = reinterpret_cast(data.ptr); - if (!file || !file->fp) { - wibo::lastError = ERROR_INVALID_HANDLE; - return FALSE; - } - FILE *fp = file->fp; - if (fflush(fp) != 0) { - wibo::lastError = ERROR_ACCESS_DENIED; - return FALSE; - } - int fd = file->fd; - if (fd >= 0 && fsync(fd) != 0) { - wibo::lastError = ERROR_ACCESS_DENIED; + if (fsync(file->host_fd) != 0) { + setLastErrorFromErrno(); return FALSE; } wibo::lastError = ERROR_SUCCESS; @@ -523,64 +504,58 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead DEBUG_LOG("ReadFile(%p, %u)\n", hFile, nNumberOfBytesToRead); wibo::lastError = ERROR_SUCCESS; - auto file = files::fileHandleFromHandle(hFile); - if (!file || !file->fp) { + HandleMeta meta{}; + auto file = wibo::handles().getAs(hFile, &meta); + if (!file || !file->valid()) { wibo::lastError = ERROR_INVALID_HANDLE; return FALSE; } + if ((meta.grantedAccess & FILE_READ_DATA) == 0) { + wibo::lastError = ERROR_ACCESS_DENIED; + DEBUG_LOG("!!! DENIED: 0x%x\n", meta.grantedAccess); + return FALSE; + } - bool handleOverlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0; - auto *overlapped = reinterpret_cast(lpOverlapped); - bool usingOverlapped = overlapped != nullptr; - if (!usingOverlapped && lpNumberOfBytesRead == nullptr) { + if (lpOverlapped == nullptr && lpNumberOfBytesRead == nullptr) { wibo::lastError = ERROR_INVALID_PARAMETER; return FALSE; } - std::optional offset; + std::optional offset; bool updateFilePointer = true; - if (usingOverlapped) { - offset = (static_cast(overlapped->Offset) | (static_cast(overlapped->OffsetHigh) << 32)); - overlapped->Internal = STATUS_PENDING; - overlapped->InternalHigh = 0; - updateFilePointer = !handleOverlapped; - resetOverlappedEvent(overlapped); + if (lpOverlapped != nullptr) { + offset = static_cast((static_cast(lpOverlapped->Offset)) | + (static_cast(lpOverlapped->OffsetHigh) << 32)); + lpOverlapped->Internal = STATUS_PENDING; + lpOverlapped->InternalHigh = 0; + updateFilePointer = !file->overlapped; + resetOverlappedEvent(lpOverlapped); } - auto io = files::read(file, lpBuffer, nNumberOfBytesToRead, offset, updateFilePointer); + auto io = files::read(file.get(), lpBuffer, nNumberOfBytesToRead, offset, updateFilePointer); DWORD completionStatus = STATUS_SUCCESS; if (io.unixError != 0) { - completionStatus = wibo::winErrorFromErrno(io.unixError); - wibo::lastError = completionStatus; + completionStatus = wibo::statusFromErrno(io.unixError); + wibo::lastError = wibo::winErrorFromErrno(io.unixError); if (lpNumberOfBytesRead) { *lpNumberOfBytesRead = static_cast(io.bytesTransferred); } - if (usingOverlapped) { - overlapped->Internal = completionStatus; - overlapped->InternalHigh = io.bytesTransferred; - signalOverlappedEvent(overlapped); + if (lpOverlapped != nullptr) { + lpOverlapped->Internal = completionStatus; + lpOverlapped->InternalHigh = io.bytesTransferred; + signalOverlappedEvent(lpOverlapped); } return FALSE; } - if (io.reachedEnd && io.bytesTransferred == 0 && handleOverlapped) { - completionStatus = ERROR_HANDLE_EOF; - } - - if (lpNumberOfBytesRead && (!handleOverlapped || !usingOverlapped)) { + if (lpNumberOfBytesRead && (!file->overlapped || lpOverlapped == nullptr)) { *lpNumberOfBytesRead = static_cast(io.bytesTransferred); } - if (usingOverlapped) { - overlapped->Internal = completionStatus; - overlapped->InternalHigh = io.bytesTransferred; - if (!handleOverlapped) { - uint64_t baseOffset = offset.value_or(0); - uint64_t newOffset = baseOffset + io.bytesTransferred; - overlapped->Offset = static_cast(newOffset & 0xFFFFFFFFu); - overlapped->OffsetHigh = static_cast(newOffset >> 32); - } - signalOverlappedEvent(overlapped); + if (lpOverlapped != nullptr) { + lpOverlapped->Internal = completionStatus; + lpOverlapped->InternalHigh = io.bytesTransferred; + signalOverlappedEvent(lpOverlapped); } return TRUE; @@ -589,6 +564,7 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead HANDLE WIN_FUNC CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { + (void)dwShareMode; (void)lpSecurityAttributes; (void)hTemplateFile; if (!lpFileName) { @@ -646,27 +622,28 @@ HANDLE WIN_FUNC CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSh assert(false); } - FILE *fp = nullptr; + int fd = -1; if (dwDesiredAccess == GENERIC_READ) { - fp = fopen(path.c_str(), "rb"); + fd = open(path.c_str(), O_RDONLY); } else if (dwDesiredAccess == GENERIC_WRITE) { if (shouldTruncate || !fileExists) { - fp = fopen(path.c_str(), "wb"); + fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); } else { - fp = fopen(path.c_str(), "rb+"); + fd = open(path.c_str(), O_RDWR); } } else if (dwDesiredAccess == (GENERIC_READ | GENERIC_WRITE)) { if (shouldTruncate || !fileExists) { - fp = fopen(path.c_str(), "wb+"); + fd = open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666); } else { - fp = fopen(path.c_str(), "rb+"); + fd = open(path.c_str(), O_RDWR); } } else { assert(false); } - if (fp) { - void *handle = files::allocFpHandle(fp, dwDesiredAccess, dwShareMode, dwFlagsAndAttributes, true); + if (fd >= 0) { + bool overlapped = (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED) != 0; + auto handle = wibo::handles().create(new files::FileObject(fd, overlapped), dwDesiredAccess, 0); DEBUG_LOG("-> %p\n", handle); return handle; } @@ -683,13 +660,6 @@ HANDLE WIN_FUNC CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwS return INVALID_HANDLE_VALUE; } std::string lpFileNameA = wideStringToString(lpFileName); - HANDLE consoleHandle = INVALID_HANDLE_VALUE; - if (tryOpenConsoleDevice(dwDesiredAccess, dwShareMode, dwCreationDisposition, dwFlagsAndAttributes, consoleHandle, - lpFileNameA)) { - DEBUG_LOG("CreateFileW(console=%s, desiredAccess=0x%x, shareMode=%u, flags=0x%x) -> %p\n", lpFileNameA.c_str(), - dwDesiredAccess, dwShareMode, dwFlagsAndAttributes, consoleHandle); - return consoleHandle; - } return CreateFileA(lpFileNameA.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } @@ -912,13 +882,19 @@ DWORD WIN_FUNC GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) { BOOL WIN_FUNC GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime) { DEBUG_LOG("GetFileTime(%p, %p, %p, %p)\n", hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime); - auto file = files::fileHandleFromHandle(hFile); - if (!file || !file->fp) { + HandleMeta meta{}; + auto file = wibo::handles().getAs(hFile, &meta); + if (!file || !file->valid()) { wibo::lastError = ERROR_INVALID_HANDLE; return FALSE; } + if ((meta.grantedAccess & FILE_READ_ATTRIBUTES) == 0) { + wibo::lastError = ERROR_ACCESS_DENIED; + return FALSE; + } + struct stat st{}; - if (fstat(fileno(file->fp), &st) != 0) { + if (fstat(file->host_fd, &st) != 0) { setLastErrorFromErrno(); return FALSE; } @@ -946,16 +922,17 @@ BOOL WIN_FUNC GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lp BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FILETIME *lpLastAccessTime, const FILETIME *lpLastWriteTime) { DEBUG_LOG("SetFileTime(%p, %p, %p, %p)\n", hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime); - FILE *fp = files::fpFromHandle(hFile); - if (!fp) { + HandleMeta meta{}; + auto file = wibo::handles().getAs(hFile, &meta); + if (!file || !file->valid()) { wibo::lastError = ERROR_INVALID_HANDLE; return FALSE; } - int fd = fileno(fp); - if (fd < 0) { - setLastErrorFromErrno(); + if ((meta.grantedAccess & FILE_WRITE_ATTRIBUTES) == 0) { + wibo::lastError = ERROR_ACCESS_DENIED; return FALSE; } + bool changeAccess = !shouldIgnoreFileTimeParam(lpLastAccessTime); bool changeWrite = !shouldIgnoreFileTimeParam(lpLastWriteTime); if (!changeAccess && !changeWrite) { @@ -963,7 +940,7 @@ BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FI return TRUE; } struct stat st{}; - if (fstat(fd, &st) != 0) { + if (fstat(file->host_fd, &st) != 0) { setLastErrorFromErrno(); return FALSE; } @@ -999,7 +976,7 @@ BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FI } #else struct timespec times[2] = {accessSpec, writeSpec}; - if (futimens(fd, times) != 0) { + if (futimens(file->host_fd, times) != 0) { setLastErrorFromErrno(); return FALSE; } @@ -1047,14 +1024,14 @@ BOOL WIN_FUNC GetFileInformationByHandle(HANDLE hFile, LPBY_HANDLE_FILE_INFORMAT DWORD WIN_FUNC GetFileType(HANDLE hFile) { DEBUG_LOG("GetFileType(%p) ", hFile); - auto *file = files::fileHandleFromHandle(hFile); - if (!file || file->fd < 0) { + auto file = wibo::handles().getAs(hFile); + if (!file || !file->valid()) { wibo::lastError = ERROR_INVALID_HANDLE; DEBUG_LOG("-> ERROR_INVALID_HANDLE\n"); return FILE_TYPE_UNKNOWN; } struct stat st{}; - if (fstat(file->fd, &st) != 0) { + if (fstat(file->host_fd, &st) != 0) { setLastErrorFromErrno(); DEBUG_LOG("-> fstat error\n"); return FILE_TYPE_UNKNOWN; diff --git a/dll/kernel32/fileapi.h b/dll/kernel32/fileapi.h index 24463dc..d7aa78d 100644 --- a/dll/kernel32/fileapi.h +++ b/dll/kernel32/fileapi.h @@ -19,11 +19,6 @@ struct BY_HANDLE_FILE_INFORMATION { using PBY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION *; using LPBY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION *; -constexpr DWORD GENERIC_READ = 0x80000000; -constexpr DWORD GENERIC_WRITE = 0x40000000; -constexpr DWORD GENERIC_EXECUTE = 0x20000000; -constexpr DWORD GENERIC_ALL = 0x10000000; - constexpr DWORD FILE_SHARE_READ = 0x00000001; constexpr DWORD FILE_SHARE_WRITE = 0x00000002; constexpr DWORD FILE_SHARE_DELETE = 0x00000004; diff --git a/errors.cpp b/errors.cpp index a8303d3..a916eeb 100644 --- a/errors.cpp +++ b/errors.cpp @@ -50,4 +50,8 @@ NTSTATUS statusFromWinError(DWORD error) { } } +NTSTATUS statusFromErrno(int err) { + return statusFromWinError(winErrorFromErrno(err)); +} + } // namespace wibo diff --git a/errors.h b/errors.h index 08e872d..93139ba 100644 --- a/errors.h +++ b/errors.h @@ -59,4 +59,5 @@ typedef int HRESULT; namespace wibo { DWORD winErrorFromErrno(int err); NTSTATUS statusFromWinError(DWORD error); +NTSTATUS statusFromErrno(int err); } // namespace wibo diff --git a/files.cpp b/files.cpp index 6314c55..1843019 100644 --- a/files.cpp +++ b/files.cpp @@ -1,406 +1,376 @@ -#include "common.h" #include "files.h" +#include "common.h" #include "handles.h" #include "strutil.h" + #include #include +#include +#include #include #include -#include #include +#include +#include namespace files { - static std::vector splitList(const std::string &value, char delimiter) { - std::vector entries; - size_t start = 0; - while (start <= value.size()) { - size_t end = value.find(delimiter, start); - if (end == std::string::npos) { - end = value.size(); - } - entries.emplace_back(value.substr(start, end - start)); - if (end == value.size()) { - break; - } - start = end + 1; +static std::vector splitList(const std::string &value, char delimiter) { + std::vector entries; + size_t start = 0; + while (start <= value.size()) { + size_t end = value.find(delimiter, start); + if (end == std::string::npos) { + end = value.size(); } - return entries; + entries.emplace_back(value.substr(start, end - start)); + if (end == value.size()) { + break; + } + start = end + 1; } + return entries; +} - static std::string toWindowsPathEntry(const std::string &entry) { - if (entry.empty()) { - return std::string(); - } - bool looksWindows = entry.find('\\') != std::string::npos || - (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/'); - if (looksWindows) { - std::string normalized = entry; - std::replace(normalized.begin(), normalized.end(), '/', '\\'); - return normalized; - } - return pathToWindows(std::filesystem::path(entry)); +static std::string toWindowsPathEntry(const std::string &entry) { + if (entry.empty()) { + return {}; } - - static std::string toHostPathEntry(const std::string &entry) { - if (entry.empty()) { - return std::string(); - } - auto converted = pathFromWindows(entry.c_str()); - if (!converted.empty()) { - return converted.string(); - } + bool looksWindows = + entry.find('\\') != std::string::npos || (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/'); + if (looksWindows) { std::string normalized = entry; - std::replace(normalized.begin(), normalized.end(), '\\', '/'); + std::replace(normalized.begin(), normalized.end(), '/', '\\'); return normalized; } + return pathToWindows(std::filesystem::path(entry)); +} - static void *stdinHandle; - static void *stdoutHandle; - static void *stderrHandle; +static std::string toHostPathEntry(const std::string &entry) { + if (entry.empty()) { + return {}; + } + auto converted = pathFromWindows(entry.c_str()); + if (!converted.empty()) { + return converted.string(); + } + std::string normalized = entry; + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + return normalized; +} - std::filesystem::path pathFromWindows(const char *inStr) { - // Convert to forward slashes - std::string str = inStr; - std::replace(str.begin(), str.end(), '\\', '/'); +static void *stdinHandle; +static void *stdoutHandle; +static void *stderrHandle; - // Remove "//?/" prefix - if (str.rfind("//?/", 0) == 0) { - str.erase(0, 4); - } +std::filesystem::path pathFromWindows(const char *inStr) { + // Convert to forward slashes + std::string str = inStr; + std::replace(str.begin(), str.end(), '\\', '/'); - // Remove the drive letter - if (str.rfind("z:/", 0) == 0 || str.rfind("Z:/", 0) == 0 || str.rfind("c:/", 0) == 0 || str.rfind("C:/", 0) == 0) { - str.erase(0, 2); - } + // Remove "//?/" prefix + if (str.rfind("//?/", 0) == 0) { + str.erase(0, 4); + } - // Return as-is if it exists, else traverse the filesystem looking for - // a path that matches case insensitively - std::filesystem::path path = std::filesystem::path(str).lexically_normal(); - if (std::filesystem::exists(path)) { - return path; - } + // Remove the drive letter + if (str.rfind("z:/", 0) == 0 || str.rfind("Z:/", 0) == 0 || str.rfind("c:/", 0) == 0 || str.rfind("C:/", 0) == 0) { + str.erase(0, 2); + } - std::filesystem::path newPath = "."; - bool followingExisting = true; - for (const auto& component : path) { - std::filesystem::path newPath2 = newPath / component; - if (followingExisting && !std::filesystem::exists(newPath2) && (component != ".." && component != "." && component != "")) { - followingExisting = false; - try { - for (std::filesystem::path entry : std::filesystem::directory_iterator{newPath}) { - if (strcasecmp(entry.filename().c_str(), component.c_str()) == 0) { - followingExisting = true; - newPath2 = entry; - break; - } + // Return as-is if it exists, else traverse the filesystem looking for + // a path that matches case insensitively + std::filesystem::path path = std::filesystem::path(str).lexically_normal(); + if (std::filesystem::exists(path)) { + return path; + } + + std::filesystem::path newPath = "."; + bool followingExisting = true; + for (const auto &component : path) { + std::filesystem::path newPath2 = newPath / component; + if (followingExisting && !std::filesystem::exists(newPath2) && + (component != ".." && component != "." && component != "")) { + followingExisting = false; + try { + for (std::filesystem::path entry : std::filesystem::directory_iterator{newPath}) { + if (strcasecmp(entry.filename().c_str(), component.c_str()) == 0) { + followingExisting = true; + newPath2 = entry; + break; } - } catch (const std::filesystem::filesystem_error&) { - // not a directory } + } catch (const std::filesystem::filesystem_error &) { + // not a directory } - newPath = newPath2; } - if (followingExisting) { - DEBUG_LOG("Resolved case-insensitive path: %s\n", newPath.c_str()); - } else { - DEBUG_LOG("Failed to resolve path: %s\n", newPath.c_str()); - } - - return newPath; + newPath = newPath2; + } + if (followingExisting) { + DEBUG_LOG("Resolved case-insensitive path: %s\n", newPath.c_str()); + } else { + DEBUG_LOG("Failed to resolve path: %s\n", newPath.c_str()); } - std::string pathToWindows(const std::filesystem::path &path) { - std::string str = path.lexically_normal(); + return newPath; +} - if (path.is_absolute()) { - str.insert(0, "Z:"); - } +std::string pathToWindows(const std::filesystem::path &path) { + std::string str = path.lexically_normal(); - std::replace(str.begin(), str.end(), '/', '\\'); - return str; + if (path.is_absolute()) { + str.insert(0, "Z:"); } - FileHandle *fileHandleFromHandle(void *handle) { - handles::Data data = handles::dataFromHandle(handle, false); - if (data.type == handles::TYPE_FILE) { - return reinterpret_cast(data.ptr); - } - return nullptr; + std::replace(str.begin(), str.end(), '/', '\\'); + return str; +} + +// FileHandle *fileHandleFromHandle(void *handle) { +// handles::Data data = handles::dataFromHandle(handle, false); +// if (data.type == handles::TYPE_FILE) { +// return reinterpret_cast(data.ptr); +// } +// return nullptr; +// } + +// FILE *fpFromHandle(void *handle, bool pop) { +// handles::Data data = handles::dataFromHandle(handle, pop); +// if (data.type == handles::TYPE_FILE) { +// return reinterpret_cast(data.ptr)->fp; +// } +// return nullptr; +// } + +// void *duplicateFileHandle(FileHandle *source, bool closeOnDestroy) { +// if (!source) { +// return nullptr; +// } +// auto *clone = new FileHandle(); +// clone->fp = source->fp; +// clone->fd = source->fd; +// clone->desiredAccess = source->desiredAccess; +// clone->shareMode = source->shareMode; +// clone->flags = source->flags; +// clone->closeOnDestroy = closeOnDestroy; +// return handles::allocDataHandle({handles::TYPE_FILE, clone, 0}); +// } + +IOResult read(FileObject *file, void *buffer, size_t bytesToRead, const std::optional &offset, + bool updateFilePointer) { + IOResult result{}; + if (!file || !file->valid()) { + result.unixError = EBADF; + return result; + } + if (bytesToRead == 0) { + return result; } - FILE *fpFromHandle(void *handle, bool pop) { - handles::Data data = handles::dataFromHandle(handle, pop); - if (data.type == handles::TYPE_FILE) { - return reinterpret_cast(data.ptr)->fp; - } - return nullptr; - } + // Sanity check: if no offset is given, we must update the file pointer + assert(offset.has_value() || updateFilePointer); - void *allocFpHandle(FILE *fp, unsigned int desiredAccess, unsigned int shareMode, unsigned int flags, bool closeOnDestroy) { - auto *handle = new FileHandle(); - handle->fp = fp; - handle->fd = (fp ? fileno(fp) : -1); - handle->desiredAccess = desiredAccess; - handle->shareMode = shareMode; - handle->flags = flags; - handle->closeOnDestroy = closeOnDestroy; - return handles::allocDataHandle({handles::TYPE_FILE, handle, 0}); - } - - void *duplicateFileHandle(FileHandle *source, bool closeOnDestroy) { - if (!source) { - return nullptr; - } - auto *clone = new FileHandle(); - clone->fp = source->fp; - clone->fd = source->fd; - clone->desiredAccess = source->desiredAccess; - clone->shareMode = source->shareMode; - clone->flags = source->flags; - clone->closeOnDestroy = closeOnDestroy; - return handles::allocDataHandle({handles::TYPE_FILE, clone, 0}); - } - - IOResult read(FileHandle *handle, void *buffer, size_t bytesToRead, const std::optional &offset, bool updateFilePointer) { - IOResult result{}; - if (!handle || !handle->fp) { - result.unixError = EBADF; - return result; - } - if (bytesToRead == 0) { - return result; - } - - bool useOffset = offset.has_value(); - if (useOffset && !updateFilePointer && handle->fd >= 0) { - size_t total = 0; - size_t remaining = bytesToRead; - off_t pos = static_cast(*offset); - while (remaining > 0) { - ssize_t rc = pread(handle->fd, static_cast(buffer) + total, remaining, pos); - if (rc == -1) { - if (errno == EINTR) { - continue; - } - result.bytesTransferred = total; - result.unixError = errno ? errno : EIO; - return result; + const auto doRead = [&](off64_t pos) { + size_t total = 0; + size_t remaining = bytesToRead; + uint8_t *in = static_cast(buffer); + while (remaining > 0) { + size_t chunk = remaining > SSIZE_MAX ? SSIZE_MAX : remaining; + ssize_t rc = pread64(file->host_fd, in + total, chunk, pos); + if (rc == -1) { + if (errno == EINTR) { + continue; } - if (rc == 0) { - result.bytesTransferred = total; - result.reachedEnd = true; - return result; - } - total += static_cast(rc); - remaining -= static_cast(rc); - pos += rc; - } - result.bytesTransferred = total; - return result; - } - - off_t originalPos = -1; - std::unique_lock lock(handle->mutex); - if (useOffset) { - originalPos = ftello(handle->fp); - if (!updateFilePointer && originalPos == -1) { - result.unixError = errno ? errno : ESPIPE; - return result; - } - if (fseeko(handle->fp, static_cast(*offset), SEEK_SET) != 0) { - result.unixError = errno ? errno : EINVAL; - return result; - } - } - - size_t readCount = fread(buffer, 1, bytesToRead, handle->fp); - result.bytesTransferred = readCount; - if (readCount < bytesToRead) { - if (feof(handle->fp)) { - result.reachedEnd = true; - clearerr(handle->fp); - } else if (ferror(handle->fp)) { + result.bytesTransferred = total; result.unixError = errno ? errno : EIO; - clearerr(handle->fp); + return; } + if (rc == 0) { + result.bytesTransferred = total; + result.reachedEnd = true; + return; + } + total += static_cast(rc); + remaining -= static_cast(rc); + pos += rc; } + result.bytesTransferred = total; + }; - if (useOffset && !updateFilePointer) { - if (originalPos != -1) { - fseeko(handle->fp, originalPos, SEEK_SET); - } + if (updateFilePointer || !offset.has_value()) { + std::unique_lock lock(file->pos_mu); + off64_t pos = offset.value_or(file->file_pos); + doRead(pos); + if (updateFilePointer) { + file->file_pos = pos + static_cast(result.bytesTransferred); } + } else { + doRead(*offset); + } + + return result; +} + +IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, const std::optional &offset, + bool updateFilePointer) { + IOResult result{}; + if (!handle || handle->host_fd < 0) { + result.unixError = EBADF; + return result; + } + if (bytesToWrite == 0) { return result; } - IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional &offset, bool updateFilePointer) { - IOResult result{}; - if (!handle || !handle->fp) { - result.unixError = EBADF; - return result; - } - if (bytesToWrite == 0) { - return result; - } + // Sanity check: if no offset is given, we must update the file pointer + assert(offset.has_value() || updateFilePointer); - bool useOffset = offset.has_value(); - if (useOffset && !updateFilePointer && handle->fd >= 0) { - size_t total = 0; - size_t remaining = bytesToWrite; - off_t pos = static_cast(*offset); - while (remaining > 0) { - ssize_t rc = pwrite(handle->fd, static_cast(buffer) + total, remaining, pos); - if (rc == -1) { - if (errno == EINTR) { - continue; - } - result.bytesTransferred = total; - result.unixError = errno ? errno : EIO; - return result; + auto doWrite = [&](off64_t pos) { + size_t total = 0; + size_t remaining = bytesToWrite; + const uint8_t *in = static_cast(buffer); + while (remaining > 0) { + size_t chunk = remaining > SSIZE_MAX ? SSIZE_MAX : remaining; + ssize_t rc = pwrite64(handle->host_fd, in + total, chunk, pos); + if (rc == -1) { + if (errno == EINTR) { + continue; } - total += static_cast(rc); - remaining -= static_cast(rc); - pos += rc; + result.bytesTransferred = total; + result.unixError = errno ? errno : EIO; + return; } - result.bytesTransferred = total; - return result; + if (rc == 0) { + result.bytesTransferred = total; + return; + } + total += static_cast(rc); + remaining -= static_cast(rc); + pos += rc; } + result.bytesTransferred = total; + }; - off_t originalPos = -1; - std::unique_lock lock(handle->mutex); - if (useOffset) { - originalPos = ftello(handle->fp); - if (!updateFilePointer && originalPos == -1) { - result.unixError = errno ? errno : ESPIPE; - return result; - } - if (fseeko(handle->fp, static_cast(*offset), SEEK_SET) != 0) { - result.unixError = errno ? errno : EINVAL; - return result; - } + if (updateFilePointer || !offset.has_value()) { + std::unique_lock lock(handle->pos_mu); + const off64_t pos = handle->file_pos; + doWrite(pos); + if (updateFilePointer) { + handle->file_pos = pos + static_cast(result.bytesTransferred); } - - size_t writeCount = fwrite(buffer, 1, bytesToWrite, handle->fp); - result.bytesTransferred = writeCount; - if (writeCount < bytesToWrite) { - result.unixError = errno ? errno : EIO; - clearerr(handle->fp); - } - - if (useOffset && !updateFilePointer) { - if (originalPos != -1) { - fseeko(handle->fp, originalPos, SEEK_SET); - } - } - return result; + } else { + doWrite(*offset); } - HANDLE getStdHandle(DWORD nStdHandle) { - switch (nStdHandle) { - case STD_INPUT_HANDLE: - return stdinHandle; - case STD_OUTPUT_HANDLE: - return stdoutHandle; - case STD_ERROR_HANDLE: - return stderrHandle; - default: - return (void *)0xFFFFFFFF; - } - } + return result; +} - BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle) { - switch (nStdHandle) { - case STD_INPUT_HANDLE: - stdinHandle = hHandle; - break; - case STD_OUTPUT_HANDLE: - stdoutHandle = hHandle; - break; - case STD_ERROR_HANDLE: - stderrHandle = hHandle; - break; - default: - return 0; // fail - } - return 1; // success - } - - void init() { - stdinHandle = allocFpHandle(stdin, 0, 0, 0, false); - stdoutHandle = allocFpHandle(stdout, 0, 0, 0, false); - stderrHandle = allocFpHandle(stderr, 0, 0, 0, false); - } - - std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, - const std::string &filename) { - std::error_code ec; - if (directory.empty()) { - return std::nullopt; - } - if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { - return std::nullopt; - } - std::string needle = filename; - toLowerInPlace(needle); - for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { - if (ec) { - break; - } - std::string candidate = entry.path().filename().string(); - toLowerInPlace(candidate); - if (candidate == needle) { - return canonicalPath(entry.path()); - } - } - auto direct = directory / filename; - if (std::filesystem::exists(direct, ec)) { - return canonicalPath(direct); - } - return std::nullopt; - } - - std::filesystem::path canonicalPath(const std::filesystem::path &path) { - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(path, ec); - if (!ec) { - return canonical; - } - return std::filesystem::absolute(path); - } - - std::string hostPathListToWindows(const std::string &value) { - if (value.empty()) { - return value; - } - char delimiter = value.find(';') != std::string::npos ? ';' : ':'; - auto entries = splitList(value, delimiter); - std::string result; - for (size_t i = 0; i < entries.size(); ++i) { - if (i != 0) { - result.push_back(';'); - } - if (!entries[i].empty()) { - result += toWindowsPathEntry(entries[i]); - } - } - return result; - } - - std::string windowsPathListToHost(const std::string &value) { - if (value.empty()) { - return value; - } - auto entries = splitList(value, ';'); - std::string result; - for (size_t i = 0; i < entries.size(); ++i) { - if (i != 0) { - result.push_back(':'); - } - if (!entries[i].empty()) { - result += toHostPathEntry(entries[i]); - } - } - return result; +HANDLE getStdHandle(DWORD nStdHandle) { + switch (nStdHandle) { + case STD_INPUT_HANDLE: + return stdinHandle; + case STD_OUTPUT_HANDLE: + return stdoutHandle; + case STD_ERROR_HANDLE: + return stderrHandle; + default: + return (void *)0xFFFFFFFF; } } + +BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle) { + switch (nStdHandle) { + case STD_INPUT_HANDLE: + stdinHandle = hHandle; + break; + case STD_OUTPUT_HANDLE: + stdoutHandle = hHandle; + break; + case STD_ERROR_HANDLE: + stderrHandle = hHandle; + break; + default: + return 0; // fail + } + return 1; // success +} + +void init() { + auto &handles = wibo::handles(); + stdinHandle = handles.create(new FileObject(STDIN_FILENO, false), FILE_GENERIC_READ, 0); + stdoutHandle = handles.create(new FileObject(STDOUT_FILENO, false), FILE_GENERIC_WRITE, 0); + stderrHandle = handles.create(new FileObject(STDERR_FILENO, false), FILE_GENERIC_WRITE, 0); +} + +std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, + const std::string &filename) { + std::error_code ec; + if (directory.empty()) { + return std::nullopt; + } + if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { + return std::nullopt; + } + std::string needle = filename; + toLowerInPlace(needle); + for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { + if (ec) { + break; + } + std::string candidate = entry.path().filename().string(); + toLowerInPlace(candidate); + if (candidate == needle) { + return canonicalPath(entry.path()); + } + } + auto direct = directory / filename; + if (std::filesystem::exists(direct, ec)) { + return canonicalPath(direct); + } + return std::nullopt; +} + +std::filesystem::path canonicalPath(const std::filesystem::path &path) { + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(path, ec); + if (!ec) { + return canonical; + } + return std::filesystem::absolute(path); +} + +std::string hostPathListToWindows(const std::string &value) { + if (value.empty()) { + return value; + } + char delimiter = value.find(';') != std::string::npos ? ';' : ':'; + auto entries = splitList(value, delimiter); + std::string result; + for (size_t i = 0; i < entries.size(); ++i) { + if (i != 0) { + result.push_back(';'); + } + if (!entries[i].empty()) { + result += toWindowsPathEntry(entries[i]); + } + } + return result; +} + +std::string windowsPathListToHost(const std::string &value) { + if (value.empty()) { + return value; + } + auto entries = splitList(value, ';'); + std::string result; + for (size_t i = 0; i < entries.size(); ++i) { + if (i != 0) { + result.push_back(':'); + } + if (!entries[i].empty()) { + result += toHostPathEntry(entries[i]); + } + } + return result; +} +} // namespace files diff --git a/files.h b/files.h index cd4154c..4dd32af 100644 --- a/files.h +++ b/files.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" +#include "handles.h" #include #include @@ -9,14 +10,22 @@ #include namespace files { - struct FileHandle { - FILE *fp = nullptr; - int fd = -1; - unsigned int desiredAccess = 0; - unsigned int shareMode = 0; - unsigned int flags = 0; - bool closeOnDestroy = true; - std::mutex mutex; + struct FileObject : ObjectHeader { + int host_fd = -1; // use pread/pwrite + std::mutex pos_mu; // sync for file_pos w/o overlapped + off64_t file_pos = 0; // shared across duplicated handles + bool overlapped = false; // set from FILE_FLAG_OVERLAPPED + + explicit FileObject(int fd, bool ov) + : ObjectHeader(ObjectType::File), host_fd(fd), overlapped(ov) {} + virtual ~FileObject() { + if (host_fd != -1) { + close(host_fd); + host_fd = -1; + } + } + + [[nodiscard]] bool valid() const { return host_fd >= 0; } }; struct IOResult { @@ -28,11 +37,11 @@ namespace files { std::filesystem::path pathFromWindows(const char *inStr); std::string pathToWindows(const std::filesystem::path &path); void *allocFpHandle(FILE *fp, unsigned int desiredAccess = 0, unsigned int shareMode = 0, unsigned int flags = 0, bool closeOnDestroy = true); - void *duplicateFileHandle(FileHandle *handle, bool closeOnDestroy); + // void *duplicateFileHandle(FileHandle *handle, bool closeOnDestroy); FILE *fpFromHandle(void *handle, bool pop = false); - FileHandle *fileHandleFromHandle(void *handle); - IOResult read(FileHandle *handle, void *buffer, size_t bytesToRead, const std::optional &offset, bool updateFilePointer); - IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional &offset, bool updateFilePointer); + // FileHandle *fileHandleFromHandle(void *handle); + IOResult read(FileObject *handle, void *buffer, size_t bytesToRead, const std::optional &offset, bool updateFilePointer); + IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, const std::optional &offset, bool updateFilePointer); HANDLE getStdHandle(DWORD nStdHandle); BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle); void init(); @@ -40,7 +49,7 @@ namespace files { std::filesystem::path canonicalPath(const std::filesystem::path &path); std::string hostPathListToWindows(const std::string &value); std::string windowsPathListToHost(const std::string &value); -} + } // namespace files inline bool endsWith(const std::string &str, const std::string &suffix) { return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; diff --git a/handles.cpp b/handles.cpp index 7aca9dc..2479734 100644 --- a/handles.cpp +++ b/handles.cpp @@ -1,6 +1,4 @@ #include "handles.h" -#include "common.h" -#include namespace { @@ -9,14 +7,16 @@ constexpr uint32_t kIndexMask = (1u << kIndexBits) - 1; // 0x1FFFF constexpr uint32_t kGenerationMask = (1u << 15) - 1; // 0x7FFF constexpr unsigned kGenerationShift = kIndexBits; // 17 -inline uint32_t indexOf(Handle h) { return h & kIndexMask; } -inline uint32_t generationOf(Handle h) { return (h >> kGenerationShift) & kGenerationMask; } -inline Handle makeHandle(uint32_t index, uint32_t gen) { return (gen << kGenerationShift) | index; } -inline bool isPseudo(Handle h) { return static_cast(h) < 0; } +inline uint32_t indexOf(HANDLE h) { return reinterpret_cast(h) & kIndexMask; } +inline uint32_t generationOf(HANDLE h) { return (reinterpret_cast(h) >> kGenerationShift) & kGenerationMask; } +inline HANDLE makeHandle(uint32_t index, uint32_t gen) { + return reinterpret_cast((gen << kGenerationShift) | index); +} +inline bool isPseudo(HANDLE h) { return reinterpret_cast(h) < 0; } } // namespace -Handle HandleTable::create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags) { +HANDLE HandleTable::create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags) { std::unique_lock lk(mu_); uint32_t idx; @@ -31,26 +31,26 @@ Handle HandleTable::create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t f auto &e = slots_[idx]; // Initialize generation if needed - if (e.generation == 0) { - e.generation = 1; + if (e.meta.generation == 0) { + e.meta.generation = 1; } - const uint16_t gen = e.generation; + const uint16_t gen = e.meta.generation; // Table owns one pointer ref for this entry detail::ref(obj); // Initialize entry e.obj = obj; - e.grantedAccess = grantedAccess; - e.flags = flags; - e.typeCache = obj->type; + e.meta.grantedAccess = grantedAccess; + e.meta.flags = flags; + e.meta.typeCache = obj->type; - const Handle h = makeHandle(idx, gen); + HANDLE h = makeHandle(idx, gen); obj->handleCount.fetch_add(1, std::memory_order_acq_rel); return h; } -bool HandleTable::get(Handle h, HandleEntry &out, Pin &pinOut) { +bool HandleTable::get(HANDLE h, Pin &pinOut, HandleMeta *metaOut) { if (isPseudo(h)) { return false; // pseudo-handles have no entries } @@ -61,17 +61,19 @@ bool HandleTable::get(Handle h, HandleEntry &out, Pin &pinOut) { return false; } const auto &e = slots_[idx]; - if (e.generation != generationOf(h) || !e.obj) { + if (e.meta.generation != generationOf(h) || !e.obj) { return false; } detail::ref(e.obj); // pin under the lock pinOut = Pin::adopt(e.obj); // dtor will deref - out = e; + if (metaOut) { + *metaOut = e.meta; + } return true; } -bool HandleTable::close(Handle h) { +bool HandleTable::close(HANDLE h) { if (isPseudo(h)) { return true; // no-op, success } @@ -82,16 +84,16 @@ bool HandleTable::close(Handle h) { return false; } auto &e = slots_[idx]; - if (e.generation != generationOf(h) || !e.obj || e.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) { + if (e.meta.generation != generationOf(h) || !e.obj || e.meta.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) { return false; } ObjectHeader *obj = e.obj; e.obj = nullptr; // tombstone - auto newHandleCnt = obj->handleCount.fetch_sub(1, std::memory_order_acq_rel) - 1; + /*auto newHandleCnt =*/ obj->handleCount.fetch_sub(1, std::memory_order_acq_rel) /* - 1*/; // bump generation & recycle while still holding the lock - e.generation = static_cast((e.generation + 1) & kGenerationMask); + e.meta.generation = static_cast((e.meta.generation + 1) & kGenerationMask); freeList_.push_back(idx); lk.unlock(); @@ -102,7 +104,7 @@ bool HandleTable::close(Handle h) { return true; } -bool HandleTable::setInformation(Handle h, uint32_t mask, uint32_t value) { +bool HandleTable::setInformation(HANDLE h, uint32_t mask, uint32_t value) { if (isPseudo(h)) { return true; // no-op, success } @@ -113,18 +115,18 @@ bool HandleTable::setInformation(Handle h, uint32_t mask, uint32_t value) { return false; } auto &e = slots_[idx]; - if (e.generation != generationOf(h) || !e.obj) { + if (e.meta.generation != generationOf(h) || !e.obj) { return false; } constexpr uint32_t kAllowedFlags = HANDLE_FLAG_INHERIT | HANDLE_FLAG_PROTECT_FROM_CLOSE; mask &= kAllowedFlags; - e.flags = (e.flags & ~mask) | (value & mask); + e.meta.flags = (e.meta.flags & ~mask) | (value & mask); return true; } -bool HandleTable::getInformation(Handle h, uint32_t *outFlags) const { +bool HandleTable::getInformation(HANDLE h, uint32_t *outFlags) const { if (!outFlags) { return false; } @@ -138,14 +140,14 @@ bool HandleTable::getInformation(Handle h, uint32_t *outFlags) const { return false; } const auto &e = slots_[idx]; - if (e.generation != generationOf(h) || !e.obj) { + if (e.meta.generation != generationOf(h) || !e.obj) { return false; } - *outFlags = e.flags; + *outFlags = e.meta.flags; return true; } -bool HandleTable::duplicateTo(Handle src, HandleTable &dst, Handle *out, uint32_t desiredAccess, bool inherit, +bool HandleTable::duplicateTo(HANDLE src, HandleTable &dst, HANDLE *out, uint32_t desiredAccess, bool inherit, uint32_t options) { if (!out) return false; @@ -160,18 +162,19 @@ bool HandleTable::duplicateTo(Handle src, HandleTable &dst, Handle *out, uint32_ // return true; // } - HandleEntry e{}; + HandleMeta meta{}; Pin pin; - if (!get(src, e, pin)) + if (!get(src, pin, &meta)) { return false; + } bool closeSource = (options & DUPLICATE_CLOSE_SOURCE) != 0; - if (closeSource && (e.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) != 0) { + if (closeSource && (meta.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) != 0) { // Cannot close source if it is protected return false; } - uint32_t effAccess = (options & DUPLICATE_SAME_ACCESS) ? e.grantedAccess : (desiredAccess & e.grantedAccess); + uint32_t effAccess = (options & DUPLICATE_SAME_ACCESS) ? meta.grantedAccess : (desiredAccess & meta.grantedAccess); const uint32_t flags = (inherit ? HANDLE_FLAG_INHERIT : 0); *out = dst.create(pin.obj, effAccess, flags); @@ -180,29 +183,3 @@ bool HandleTable::duplicateTo(Handle src, HandleTable &dst, Handle *out, uint32_ } return true; } - -namespace handles { -static Data datas[MAX_HANDLES]; - -Data dataFromHandle(void *handle, bool pop) { - uintptr_t index = (uintptr_t)handle; - if (index > 0 && index < MAX_HANDLES) { - Data ret = datas[index]; - if (pop) - datas[index] = Data{}; - return ret; - } - return Data{}; -} - -void *allocDataHandle(Data data) { - for (size_t i = 1; i < MAX_HANDLES; i++) { - if (datas[i].type == TYPE_UNUSED) { - datas[i] = data; - return (void *)i; - } - } - printf("Out of handles\n"); - assert(0); -} -} // namespace handles diff --git a/handles.h b/handles.h index 6a07021..a64b663 100644 --- a/handles.h +++ b/handles.h @@ -1,10 +1,10 @@ #pragma once #include "common.h" + #include #include #include -#include #include #include #include @@ -67,8 +67,8 @@ template struct Pin { } } - static Pin acquire(ObjectHeader *o) { return Pin{o, Tag::Acquire}; } - static Pin adopt(ObjectHeader *o) { return Pin{o, Tag::Adopt}; } + static Pin acquire(ObjectHeader *o) { return {o, Tag::Acquire}; } + static Pin adopt(ObjectHeader *o) { return {o, Tag::Adopt}; } Pin(const Pin &) = delete; Pin &operator=(const Pin &) = delete; @@ -94,9 +94,10 @@ template struct Pin { T *operator->() const { return obj; } T &operator*() const { return *obj; } + [[nodiscard]] T *get() const { return obj; } explicit operator bool() const { return obj != nullptr; } - template Pin downcast() && { + template Pin downcast() && { if (obj && obj->type == U::kType) { auto *u = static_cast(obj); obj = nullptr; @@ -106,77 +107,69 @@ template struct Pin { } }; -using Handle = uint32_t; - constexpr DWORD HANDLE_FLAG_INHERIT = 0x1; constexpr DWORD HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x2; constexpr DWORD DUPLICATE_CLOSE_SOURCE = 0x1; constexpr DWORD DUPLICATE_SAME_ACCESS = 0x2; -struct HandleEntry { - struct ObjectHeader *obj; // intrusive ref (pointerCount++) +struct HandleMeta { uint32_t grantedAccess; // effective access mask for this handle uint32_t flags; // inherit/protect/etc ObjectType typeCache; // cached ObjectType for fast getAs uint16_t generation; // must match handle’s generation }; +// template +// struct HandleRef { +// Pin obj; +// HandleMeta meta; + +// HandleRef() = default; +// HandleRef(Pin o, HandleMeta m) : obj(std::move(o)), meta(m) {} + +// explicit operator bool() const { return obj.operator bool(); } +// }; + class HandleTable { public: - Handle create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags); - bool close(Handle h); - bool get(Handle h, HandleEntry &out, Pin &pinOut); - template Pin getAs(Handle h) { + HANDLE create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags); + bool close(HANDLE h); + bool get(HANDLE h, Pin &pinOut, HandleMeta *metaOut = nullptr); + template Pin getAs(HANDLE h, HandleMeta *metaOut = nullptr) { static_assert(std::is_base_of_v, "T must derive from ObjectHeader"); - HandleEntry meta{}; Pin pin; - if (!get(h, meta, pin)) { + HandleMeta metaOutLocal{}; + if (!metaOut) { + metaOut = &metaOutLocal; + } + if (!get(h, pin, metaOut)) { return {}; } if constexpr (std::is_same_v) { return std::move(pin); - } else if (meta.typeCache != T::kType || pin->type != T::kType) { + } else if (metaOut->typeCache != T::kType || pin->type != T::kType) { return {}; } else { // Cast directly to T* and transfer ownership to Pin return Pin::adopt(static_cast(pin.release())); } } - bool setInformation(Handle h, uint32_t mask, uint32_t value); - bool getInformation(Handle h, uint32_t *outFlags) const; - bool duplicateTo(Handle src, HandleTable &dst, Handle *out, uint32_t desiredAccess, bool inherit, uint32_t options); + bool setInformation(HANDLE h, uint32_t mask, uint32_t value); + bool getInformation(HANDLE h, uint32_t *outFlags) const; + bool duplicateTo(HANDLE src, HandleTable &dst, HANDLE *out, uint32_t desiredAccess, bool inherit, uint32_t options); private: + struct HandleEntry { + struct ObjectHeader *obj; + HandleMeta meta; + }; + std::vector slots_; std::vector freeList_; mutable std::shared_mutex mu_; }; -namespace handles { - -constexpr size_t MAX_HANDLES = 0x10000; - -enum Type { - TYPE_UNUSED, - TYPE_FILE, - TYPE_MAPPED, - TYPE_PROCESS, - TYPE_TOKEN, - TYPE_MUTEX, - TYPE_EVENT, - TYPE_SEMAPHORE, - TYPE_THREAD, - TYPE_HEAP, - TYPE_REGISTRY_KEY -}; - -struct Data { - Type type = TYPE_UNUSED; - void *ptr; - size_t size; -}; - -Data dataFromHandle(void *handle, bool pop); -void *allocDataHandle(Data data); -} // namespace handles +namespace wibo { +extern HandleTable &handles(); +}