WIP file handles refactor

This commit is contained in:
Luke Street 2025-10-03 10:57:43 -06:00
parent 072b3e1da9
commit 704dfd90ec
11 changed files with 639 additions and 590 deletions

50
access_mask.cpp Normal file
View File

@ -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

40
access_mask.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include "common.h"
#include <cstdint>
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

View File

@ -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;

View File

@ -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<files::FileObject>(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<OVERLAPPED *>(lpOverlapped);
bool usingOverlapped = overlapped != nullptr;
if (!usingOverlapped && lpNumberOfBytesWritten == nullptr) {
if (lpOverlapped == nullptr && lpNumberOfBytesWritten == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
std::optional<uint64_t> offset;
std::optional<off64_t> offset;
bool updateFilePointer = true;
if (usingOverlapped) {
offset = (static_cast<uint64_t>(overlapped->Offset) | (static_cast<uint64_t>(overlapped->OffsetHigh) << 32));
overlapped->Internal = STATUS_PENDING;
overlapped->InternalHigh = 0;
updateFilePointer = !handleOverlapped;
resetOverlappedEvent(overlapped);
if (lpOverlapped != nullptr) {
offset = static_cast<off64_t>((static_cast<uint64_t>(lpOverlapped->Offset)) |
(static_cast<uint64_t>(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<DWORD>(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<DWORD>(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<DWORD>(newOffset & 0xFFFFFFFFu);
overlapped->OffsetHigh = static_cast<DWORD>(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<files::FileObject>(hFile);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
auto file = reinterpret_cast<files::FileHandle *>(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<files::FileObject>(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<OVERLAPPED *>(lpOverlapped);
bool usingOverlapped = overlapped != nullptr;
if (!usingOverlapped && lpNumberOfBytesRead == nullptr) {
if (lpOverlapped == nullptr && lpNumberOfBytesRead == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
std::optional<uint64_t> offset;
std::optional<off64_t> offset;
bool updateFilePointer = true;
if (usingOverlapped) {
offset = (static_cast<uint64_t>(overlapped->Offset) | (static_cast<uint64_t>(overlapped->OffsetHigh) << 32));
overlapped->Internal = STATUS_PENDING;
overlapped->InternalHigh = 0;
updateFilePointer = !handleOverlapped;
resetOverlappedEvent(overlapped);
if (lpOverlapped != nullptr) {
offset = static_cast<off64_t>((static_cast<uint64_t>(lpOverlapped->Offset)) |
(static_cast<uint64_t>(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<DWORD>(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<DWORD>(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<DWORD>(newOffset & 0xFFFFFFFFu);
overlapped->OffsetHigh = static_cast<DWORD>(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<files::FileObject>(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<files::FileObject>(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<files::FileObject>(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;

View File

@ -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;

View File

@ -50,4 +50,8 @@ NTSTATUS statusFromWinError(DWORD error) {
}
}
NTSTATUS statusFromErrno(int err) {
return statusFromWinError(winErrorFromErrno(err));
}
} // namespace wibo

View File

@ -59,4 +59,5 @@ typedef int HRESULT;
namespace wibo {
DWORD winErrorFromErrno(int err);
NTSTATUS statusFromWinError(DWORD error);
NTSTATUS statusFromErrno(int err);
} // namespace wibo

668
files.cpp
View File

@ -1,406 +1,376 @@
#include "common.h"
#include "files.h"
#include "common.h"
#include "handles.h"
#include "strutil.h"
#include <algorithm>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <map>
#include <optional>
#include <strings.h>
#include <string>
#include <strings.h>
#include <unistd.h>
namespace files {
static std::vector<std::string> splitList(const std::string &value, char delimiter) {
std::vector<std::string> 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<std::string> splitList(const std::string &value, char delimiter) {
std::vector<std::string> 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<FileHandle *>(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<FileHandle *>(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<FileHandle *>(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<off64_t> &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<FileHandle *>(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<uint64_t> &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<off_t>(*offset);
while (remaining > 0) {
ssize_t rc = pread(handle->fd, static_cast<uint8_t *>(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<uint8_t *>(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<size_t>(rc);
remaining -= static_cast<size_t>(rc);
pos += rc;
}
result.bytesTransferred = total;
return result;
}
off_t originalPos = -1;
std::unique_lock<std::mutex> 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<off_t>(*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<size_t>(rc);
remaining -= static_cast<size_t>(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<std::mutex> lock(file->pos_mu);
off64_t pos = offset.value_or(file->file_pos);
doRead(pos);
if (updateFilePointer) {
file->file_pos = pos + static_cast<off64_t>(result.bytesTransferred);
}
} else {
doRead(*offset);
}
return result;
}
IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, const std::optional<off64_t> &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<uint64_t> &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<off_t>(*offset);
while (remaining > 0) {
ssize_t rc = pwrite(handle->fd, static_cast<const uint8_t *>(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<const uint8_t *>(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<size_t>(rc);
remaining -= static_cast<size_t>(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<size_t>(rc);
remaining -= static_cast<size_t>(rc);
pos += rc;
}
result.bytesTransferred = total;
};
off_t originalPos = -1;
std::unique_lock<std::mutex> 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<off_t>(*offset), SEEK_SET) != 0) {
result.unixError = errno ? errno : EINVAL;
return result;
}
if (updateFilePointer || !offset.has_value()) {
std::unique_lock<std::mutex> lock(handle->pos_mu);
const off64_t pos = handle->file_pos;
doWrite(pos);
if (updateFilePointer) {
handle->file_pos = pos + static_cast<off64_t>(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<std::filesystem::path> 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<std::filesystem::path> 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

35
files.h
View File

@ -1,6 +1,7 @@
#pragma once
#include "common.h"
#include "handles.h"
#include <cstdio>
#include <filesystem>
@ -9,14 +10,22 @@
#include <string>
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<uint64_t> &offset, bool updateFilePointer);
IOResult write(FileHandle *handle, const void *buffer, size_t bytesToWrite, const std::optional<uint64_t> &offset, bool updateFilePointer);
// FileHandle *fileHandleFromHandle(void *handle);
IOResult read(FileObject *handle, void *buffer, size_t bytesToRead, const std::optional<uint64_t> &offset, bool updateFilePointer);
IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, const std::optional<uint64_t> &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;

View File

@ -1,6 +1,4 @@
#include "handles.h"
#include "common.h"
#include <utility>
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<int32_t>(h) < 0; }
inline uint32_t indexOf(HANDLE h) { return reinterpret_cast<uint32_t>(h) & kIndexMask; }
inline uint32_t generationOf(HANDLE h) { return (reinterpret_cast<uint32_t>(h) >> kGenerationShift) & kGenerationMask; }
inline HANDLE makeHandle(uint32_t index, uint32_t gen) {
return reinterpret_cast<HANDLE>((gen << kGenerationShift) | index);
}
inline bool isPseudo(HANDLE h) { return reinterpret_cast<int32_t>(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<ObjectHeader> &pinOut) {
bool HandleTable::get(HANDLE h, Pin<ObjectHeader> &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<ObjectHeader> &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<ObjectHeader>::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<uint16_t>((e.generation + 1) & kGenerationMask);
e.meta.generation = static_cast<uint16_t>((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<ObjectHeader> 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

View File

@ -1,10 +1,10 @@
#pragma once
#include "common.h"
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <cstdlib>
#include <shared_mutex>
#include <utility>
#include <vector>
@ -67,8 +67,8 @@ template <class T> 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 <class T> 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 <class U> Pin<U> downcast() && {
template <typename U> Pin<U> downcast() && {
if (obj && obj->type == U::kType) {
auto *u = static_cast<U *>(obj);
obj = nullptr;
@ -106,77 +107,69 @@ template <class T> 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 handles generation
};
// template <typename T>
// struct HandleRef {
// Pin<T> obj;
// HandleMeta meta;
// HandleRef() = default;
// HandleRef(Pin<T> 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<ObjectHeader> &pinOut);
template <typename T> Pin<T> getAs(Handle h) {
HANDLE create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags);
bool close(HANDLE h);
bool get(HANDLE h, Pin<ObjectHeader> &pinOut, HandleMeta *metaOut = nullptr);
template <typename T> Pin<T> getAs(HANDLE h, HandleMeta *metaOut = nullptr) {
static_assert(std::is_base_of_v<ObjectHeader, T>, "T must derive from ObjectHeader");
HandleEntry meta{};
Pin<ObjectHeader> pin;
if (!get(h, meta, pin)) {
HandleMeta metaOutLocal{};
if (!metaOut) {
metaOut = &metaOutLocal;
}
if (!get(h, pin, metaOut)) {
return {};
}
if constexpr (std::is_same_v<T, ObjectHeader>) {
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<T>
return Pin<T>::adopt(static_cast<T *>(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<HandleEntry> slots_;
std::vector<uint32_t> 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();
}