New handles, threading and processes subsystems

This commit is contained in:
Luke Street 2025-10-04 13:16:25 -06:00
parent 704dfd90ec
commit 390f26b28d
36 changed files with 2023 additions and 1875 deletions

View File

@ -73,6 +73,7 @@ add_executable(wibo
dll/user32.cpp
dll/vcruntime.cpp
dll/version.cpp
access.cpp
errors.cpp
files.cpp
handles.cpp

View File

@ -1,4 +1,4 @@
#include "access_mask.h"
#include "access.h"
namespace wibo::access {
@ -42,9 +42,4 @@ NormalizedAccess normalizeDesiredAccess(uint32_t desiredMask, const GenericMappi
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

140
common.h
View File

@ -78,6 +78,79 @@ using LPWSTR = uint16_t *;
using LPCWSTR = const uint16_t *;
using LPCWCH = const uint16_t *;
using WCHAR = uint16_t;
using LPCH = char *;
using LPWCH = uint16_t *;
using BOOL = int;
using PBOOL = BOOL *;
using LPBOOL = BOOL *;
using UCHAR = unsigned char;
using PUCHAR = UCHAR *;
using SIZE_T = size_t;
using PSIZE_T = SIZE_T *;
using BYTE = unsigned char;
using BOOLEAN = unsigned char;
using UINT = unsigned int;
using HKEY = void *;
using PHKEY = HKEY *;
using PSID = void *;
using REGSAM = DWORD;
using LSTATUS = LONG;
using LCID = DWORD;
using LCTYPE = DWORD;
constexpr BOOL TRUE = 1;
constexpr BOOL FALSE = 0;
constexpr DWORD STILL_ACTIVE = 259;
constexpr DWORD FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
constexpr DWORD FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
constexpr DWORD FILE_FLAG_NO_BUFFERING = 0x20000000;
constexpr DWORD FILE_FLAG_OVERLAPPED = 0x40000000;
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 SYNCHRONIZE = 0x00100000;
constexpr DWORD DELETE = 0x00010000;
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 STANDARD_RIGHTS_ALL = 0x001f0000;
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 EVENT_ALL_ACCESS = 0x1F0003;
constexpr DWORD MUTEX_ALL_ACCESS = 0x1F0001;
constexpr DWORD SEMAPHORE_ALL_ACCESS = 0x1F0003;
constexpr DWORD GENERIC_READ = 0x80000000;
constexpr DWORD GENERIC_WRITE = 0x40000000;
constexpr DWORD GENERIC_EXECUTE = 0x20000000;
constexpr DWORD GENERIC_ALL = 0x10000000;
// Page protection constants
constexpr DWORD PAGE_NOACCESS = 0x01;
@ -116,70 +189,11 @@ constexpr DWORD FILE_MAP_WRITE = 0x00000002;
constexpr DWORD FILE_MAP_READ = 0x00000004;
constexpr DWORD FILE_MAP_EXECUTE = 0x00000020;
constexpr DWORD FILE_MAP_ALL_ACCESS = 0x000f001f;
using LPCH = char *;
using LPWCH = uint16_t *;
using BOOL = int;
using PBOOL = BOOL *;
using LPBOOL = BOOL *;
using UCHAR = unsigned char;
using PUCHAR = UCHAR *;
using SIZE_T = size_t;
using PSIZE_T = SIZE_T *;
using BYTE = unsigned char;
using BOOLEAN = unsigned char;
using UINT = unsigned int;
using HKEY = void *;
using PHKEY = HKEY *;
using PSID = void *;
using REGSAM = DWORD;
using LSTATUS = LONG;
using LCID = DWORD;
using LCTYPE = DWORD;
constexpr BOOL TRUE = 1;
constexpr BOOL FALSE = 0;
constexpr DWORD STILL_ACTIVE = 259;
constexpr DWORD FILE_FLAG_OVERLAPPED = 0x40000000;
constexpr DWORD FILE_FLAG_NO_BUFFERING = 0x20000000;
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;
// File share modes
constexpr DWORD FILE_SHARE_READ = 0x00000001;
constexpr DWORD FILE_SHARE_WRITE = 0x00000002;
constexpr DWORD FILE_SHARE_DELETE = 0x00000004;
struct UNICODE_STRING {
unsigned short Length;

View File

@ -1,15 +1,21 @@
#pragma once
#include "common.h"
#include "handles.h"
#include "securitybaseapi.h"
constexpr DWORD SECURITY_LOCAL_SYSTEM_RID = 18;
constexpr BYTE kNtAuthority[6] = {0, 0, 0, 0, 0, 5};
struct TokenObject {
HANDLE processHandle;
struct TokenObject : ObjectBase {
static constexpr ObjectType kType = ObjectType::Token;
Pin<> obj;
DWORD desiredAccess;
explicit TokenObject(Pin<> obj, DWORD desiredAccess)
: ObjectBase(kType), obj(std::move(obj)), desiredAccess(desiredAccess) {}
};
using SidIdentifierAuthority = SID_IDENTIFIER_AUTHORITY;

View File

@ -3,6 +3,7 @@
#include "errors.h"
#include "handles.h"
#include "internal.h"
#include "processes.h"
namespace advapi32 {
@ -12,14 +13,13 @@ BOOL WIN_FUNC OpenProcessToken(HANDLE ProcessHandle, DWORD DesiredAccess, PHANDL
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
auto *token = new TokenObject;
token->processHandle = ProcessHandle;
token->desiredAccess = DesiredAccess;
handles::Data data;
data.type = handles::TYPE_TOKEN;
data.ptr = token;
data.size = sizeof(TokenObject);
*TokenHandle = handles::allocDataHandle(data);
auto obj = wibo::handles().getAs<ProcessObject>(ProcessHandle);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
auto token = make_pin<TokenObject>(std::move(obj), DesiredAccess);
*TokenHandle = wibo::handles().alloc(std::move(token), 0, 0);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}

View File

@ -323,21 +323,14 @@ BOOL WIN_FUNC DuplicateTokenEx(HANDLE hExistingToken, DWORD dwDesiredAccess, voi
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
handles::Data existingData = handles::dataFromHandle(hExistingToken, false);
if (existingData.type != handles::TYPE_TOKEN || existingData.ptr == nullptr) {
auto existing = wibo::handles().getAs<TokenObject>(hExistingToken);
if (!existing) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
auto *existingToken = static_cast<TokenObject *>(existingData.ptr);
auto *newToken = new TokenObject(*existingToken);
if (dwDesiredAccess != 0) {
newToken->desiredAccess = dwDesiredAccess;
}
handles::Data newData;
newData.type = handles::TYPE_TOKEN;
newData.ptr = newToken;
newData.size = sizeof(TokenObject);
*phNewToken = handles::allocDataHandle(newData);
auto newToken =
make_pin<TokenObject>(existing->obj.clone(), dwDesiredAccess == 0 ? existing->desiredAccess : dwDesiredAccess);
*phNewToken = wibo::handles().alloc(std::move(newToken), 0, 0);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
@ -411,8 +404,8 @@ BOOL WIN_FUNC SetKernelObjectSecurity(HANDLE Handle, SECURITY_INFORMATION Securi
wibo::lastError = ERROR_INVALID_SECURITY_DESCR;
return FALSE;
}
auto data = handles::dataFromHandle(Handle, false);
if (data.type == handles::TYPE_UNUSED) {
auto obj = wibo::handles().get(Handle);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
@ -461,14 +454,14 @@ BOOL WIN_FUNC SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR pSecurityDescriptor
BOOL WIN_FUNC GetTokenInformation(HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
LPVOID TokenInformation, DWORD TokenInformationLength, LPDWORD ReturnLength) {
DEBUG_LOG("GetTokenInformation(%p, %u, %p, %u, %p)\n", TokenHandle, TokenInformationClass, TokenInformation,
DEBUG_LOG("STUB: GetTokenInformation(%p, %u, %p, %u, %p)\n", TokenHandle, TokenInformationClass, TokenInformation,
TokenInformationLength, ReturnLength);
if (!ReturnLength) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
auto data = handles::dataFromHandle(TokenHandle, false);
if (data.type != handles::TYPE_TOKEN) {
auto token = wibo::handles().getAs<TokenObject>(TokenHandle);
if (!token) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
@ -563,8 +556,8 @@ BOOL WIN_FUNC SetTokenInformation(HANDLE TokenHandle, TOKEN_INFORMATION_CLASS To
(void)TokenInformationClass;
(void)TokenInformation;
(void)TokenInformationLength;
auto data = handles::dataFromHandle(TokenHandle, false);
if (data.type != handles::TYPE_TOKEN) {
auto token = wibo::handles().getAs<TokenObject>(TokenHandle);
if (!token) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}

View File

@ -12,9 +12,16 @@
namespace {
struct RegistryKeyHandleData {
struct RegistryKeyObject : ObjectBase {
static constexpr ObjectType kType = ObjectType::RegistryKey;
std::mutex m;
std::u16string canonicalPath;
bool closed = false;
bool predefined = false;
RegistryKeyObject() : ObjectBase(kType) {}
explicit RegistryKeyObject(std::u16string path) : ObjectBase(kType), canonicalPath(std::move(path)) {}
};
struct PredefinedKeyInfo {
@ -34,9 +41,6 @@ constexpr PredefinedKeyInfo kPredefinedKeyInfos[] = {
constexpr size_t kPredefinedKeyCount = std::size(kPredefinedKeyInfos);
std::mutex g_registryMutex;
bool g_predefinedHandlesInitialized = false;
RegistryKeyHandleData g_predefinedHandles[kPredefinedKeyCount];
bool g_registryInitialized = false;
std::unordered_set<std::u16string> g_existingKeys;
std::u16string canonicalizeKeySegment(const std::u16string &input) {
@ -74,63 +78,49 @@ std::u16string canonicalizeKeySegment(LPCWSTR input) {
return canonicalizeKeySegment(wide);
}
void initializePredefinedHandlesLocked() {
if (g_predefinedHandlesInitialized) {
return;
}
for (size_t i = 0; i < kPredefinedKeyCount; ++i) {
g_predefinedHandles[i].canonicalPath = canonicalizeKeySegment(std::u16string(kPredefinedKeyInfos[i].name));
g_predefinedHandles[i].predefined = true;
}
g_predefinedHandlesInitialized = true;
}
RegistryKeyHandleData *predefinedHandleForValue(uintptr_t value) {
Pin<RegistryKeyObject> predefinedHandleForValue(uintptr_t value) {
static std::array<Pin<RegistryKeyObject>, kPredefinedKeyCount> g_predefinedHandles = [] {
std::array<Pin<RegistryKeyObject>, kPredefinedKeyCount> arr;
for (size_t i = 0; i < kPredefinedKeyCount; ++i) {
arr[i] = make_pin<RegistryKeyObject>();
arr[i]->canonicalPath = canonicalizeKeySegment(std::u16string(kPredefinedKeyInfos[i].name));
arr[i]->predefined = true;
}
return arr;
}();
for (size_t i = 0; i < kPredefinedKeyCount; ++i) {
if (kPredefinedKeyInfos[i].value == value) {
return &g_predefinedHandles[i];
return g_predefinedHandles[i].clone();
}
}
return nullptr;
return {};
}
RegistryKeyHandleData *handleDataFromHKeyLocked(HKEY hKey) {
Pin<RegistryKeyObject> handleDataFromHKeyLocked(HKEY hKey) {
uintptr_t raw = reinterpret_cast<uintptr_t>(hKey);
if (raw == 0) {
return nullptr;
return {};
}
initializePredefinedHandlesLocked();
if (auto *predefined = predefinedHandleForValue(raw)) {
if (auto predefined = predefinedHandleForValue(raw)) {
return predefined;
}
auto data = handles::dataFromHandle(hKey, false);
if (data.type != handles::TYPE_REGISTRY_KEY || data.ptr == nullptr) {
return nullptr;
auto obj = wibo::handles().getAs<RegistryKeyObject>(hKey);
if (!obj || obj->closed) {
return {};
}
return static_cast<RegistryKeyHandleData *>(data.ptr);
return obj;
}
bool isPredefinedKeyHandle(HKEY hKey) {
uintptr_t raw = reinterpret_cast<uintptr_t>(hKey);
for (size_t i = 0; i < kPredefinedKeyCount; ++i) {
if (kPredefinedKeyInfos[i].value == raw) {
for (const auto &kPredefinedKeyInfo : kPredefinedKeyInfos) {
if (kPredefinedKeyInfo.value == raw) {
return true;
}
}
return false;
}
void ensureRegistryInitializedLocked() {
if (g_registryInitialized) {
return;
}
initializePredefinedHandlesLocked();
for (auto &g_predefinedHandle : g_predefinedHandles) {
g_existingKeys.insert(g_predefinedHandle.canonicalPath);
}
g_registryInitialized = true;
}
} // namespace
namespace advapi32 {
@ -163,8 +153,7 @@ LSTATUS WIN_FUNC RegCreateKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, LP
DEBUG_LOG("RegCreateKeyExW: ignoring WOW64 access mask 0x%x\n", samDesired ^ sanitizedAccess);
}
std::lock_guard<std::mutex> lock(g_registryMutex);
ensureRegistryInitializedLocked();
RegistryKeyHandleData *baseHandle = handleDataFromHKeyLocked(hKey);
Pin<RegistryKeyObject> baseHandle = handleDataFromHKeyLocked(hKey);
if (!baseHandle) {
wibo::lastError = ERROR_INVALID_HANDLE;
return ERROR_INVALID_HANDLE;
@ -197,10 +186,8 @@ LSTATUS WIN_FUNC RegCreateKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, LP
wibo::lastError = ERROR_SUCCESS;
return ERROR_SUCCESS;
}
auto *handleData = new RegistryKeyHandleData;
handleData->canonicalPath = targetPath;
handleData->predefined = false;
auto handle = handles::allocDataHandle({handles::TYPE_REGISTRY_KEY, handleData, sizeof(*handleData)});
auto obj = make_pin<RegistryKeyObject>(std::move(targetPath));
auto handle = wibo::handles().alloc(std::move(obj), 0, 0);
*phkResult = reinterpret_cast<HKEY>(handle);
wibo::lastError = ERROR_SUCCESS;
return ERROR_SUCCESS;
@ -246,8 +233,7 @@ LSTATUS WIN_FUNC RegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REG
}
(void)sanitizedAccess;
std::lock_guard<std::mutex> lock(g_registryMutex);
ensureRegistryInitializedLocked();
RegistryKeyHandleData *baseHandle = handleDataFromHKeyLocked(hKey);
Pin<RegistryKeyObject> baseHandle = handleDataFromHKeyLocked(hKey);
if (!baseHandle) {
wibo::lastError = ERROR_INVALID_HANDLE;
return ERROR_INVALID_HANDLE;
@ -277,10 +263,8 @@ LSTATUS WIN_FUNC RegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REG
return ERROR_SUCCESS;
}
}
auto *handleData = new RegistryKeyHandleData;
handleData->canonicalPath = targetPath;
handleData->predefined = false;
auto handle = handles::allocDataHandle({handles::TYPE_REGISTRY_KEY, handleData, sizeof(*handleData)});
auto obj = make_pin<RegistryKeyObject>(std::move(targetPath));
auto handle = wibo::handles().alloc(std::move(obj), 0, 0);
*phkResult = reinterpret_cast<HKEY>(handle);
wibo::lastError = ERROR_SUCCESS;
return ERROR_SUCCESS;
@ -391,13 +375,11 @@ LSTATUS WIN_FUNC RegCloseKey(HKEY hKey) {
wibo::lastError = ERROR_SUCCESS;
return ERROR_SUCCESS;
}
auto data = handles::dataFromHandle(hKey, true);
if (data.type != handles::TYPE_REGISTRY_KEY || data.ptr == nullptr) {
auto obj = wibo::handles().getAs<RegistryKeyObject>(hKey);
if (!obj || obj->closed) {
wibo::lastError = ERROR_INVALID_HANDLE;
return ERROR_INVALID_HANDLE;
}
auto *handleData = static_cast<RegistryKeyHandleData *>(data.ptr);
delete handleData;
wibo::lastError = ERROR_SUCCESS;
return ERROR_SUCCESS;
}

View File

@ -1,6 +1,6 @@
#include "fileapi.h"
#include "access_mask.h"
#include "access.h"
#include "common.h"
#include "errors.h"
#include "files.h"
@ -41,6 +41,14 @@ const FILETIME kDefaultFindFileTime = {
const FILETIME kDefaultFileInformationTime = {static_cast<DWORD>(UNIX_TIME_ZERO & 0xFFFFFFFFULL),
static_cast<DWORD>(UNIX_TIME_ZERO >> 32)};
using wibo::access::containsAny;
constexpr uint32_t kFileReadMask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES;
constexpr uint32_t kDirectoryReadMask = FILE_LIST_DIRECTORY | FILE_TRAVERSE | FILE_READ_EA | FILE_READ_ATTRIBUTES;
constexpr uint32_t kFileWriteMask = FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES;
constexpr uint32_t kDirectoryWriteMask =
FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES;
struct timespec accessTimespec(const struct stat &st) {
#if defined(__APPLE__)
return st.st_atimespec;
@ -252,7 +260,7 @@ bool tryOpenConsoleDevice(DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCrea
return false;
}
HANDLE baseHandle = files::getStdHandle(*stdHandleKind);
if (!wibo::handles().duplicateTo(baseHandle, wibo::handles(), &outHandle, dwDesiredAccess, false, 0)) {
if (!wibo::handles().duplicateTo(baseHandle, wibo::handles(), outHandle, dwDesiredAccess, false, 0)) {
wibo::lastError = ERROR_INVALID_HANDLE;
outHandle = INVALID_HANDLE_VALUE;
return true;
@ -265,21 +273,26 @@ bool tryOpenConsoleDevice(DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCrea
namespace kernel32 {
int64_t getFileSizeFromHandle(HANDLE hFile) {
FILE *fp = files::fpFromHandle(hFile);
if (!fp) {
wibo::lastError = ERROR_INVALID_HANDLE;
return -1;
namespace {
void signalOverlappedEvent(OVERLAPPED *ov) {
if (ov && ov->hEvent) {
if (auto ev = wibo::handles().getAs<EventObject>(ov->hEvent)) {
ev->set();
}
}
struct stat64 st{};
fflush(fp);
if (fstat64(fileno(fp), &st) == -1 || !S_ISREG(st.st_mode)) {
setLastErrorFromErrno();
return -1;
}
return st.st_size;
}
void resetOverlappedEvent(OVERLAPPED *ov) {
if (ov && ov->hEvent) {
if (auto ev = wibo::handles().getAs<EventObject>(ov->hEvent)) {
ev->reset();
}
}
}
} // namespace
DWORD WIN_FUNC GetFileAttributesA(LPCSTR lpFileName) {
if (!lpFileName) {
wibo::lastError = ERROR_INVALID_PARAMETER;
@ -424,20 +437,23 @@ LONG WIN_FUNC CompareFileTime(const FILETIME *lpFileTime1, const FILETIME *lpFil
BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped) {
DEBUG_LOG("WriteFile(%p, %u)\n", hFile, nNumberOfBytesToWrite);
DEBUG_LOG("WriteFile(%p, %p, %u, %p, %p)\n", hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten,
lpOverlapped);
wibo::lastError = ERROR_SUCCESS;
HandleMeta meta{};
auto file = wibo::handles().getAs<files::FileObject>(hFile, &meta);
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
if ((meta.grantedAccess & FILE_WRITE_DATA) == 0) {
#ifdef CHECK_ACCESS
if ((meta.grantedAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) == 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("!!! DENIED: 0x%x\n", meta.grantedAccess);
return FALSE;
}
#endif
if (lpOverlapped == nullptr && lpNumberOfBytesWritten == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
@ -460,15 +476,8 @@ BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr
if (io.unixError != 0) {
completionStatus = wibo::statusFromErrno(io.unixError);
wibo::lastError = wibo::winErrorFromErrno(io.unixError);
if (lpNumberOfBytesWritten) {
*lpNumberOfBytesWritten = io.bytesTransferred;
}
if (lpOverlapped != nullptr) {
lpOverlapped->Internal = completionStatus;
lpOverlapped->InternalHigh = io.bytesTransferred;
signalOverlappedEvent(lpOverlapped);
}
return FALSE;
} else if (io.reachedEnd && io.bytesTransferred == 0) {
completionStatus = STATUS_END_OF_FILE;
}
if (lpNumberOfBytesWritten && (!file->overlapped || lpOverlapped == nullptr)) {
@ -481,17 +490,17 @@ BOOL WIN_FUNC WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWr
signalOverlappedEvent(lpOverlapped);
}
return TRUE;
return io.unixError == 0;
}
BOOL WIN_FUNC FlushFileBuffers(HANDLE hFile) {
DEBUG_LOG("FlushFileBuffers(%p)\n", hFile);
auto file = wibo::handles().getAs<files::FileObject>(hFile);
auto file = wibo::handles().getAs<FileObject>(hFile);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
if (fsync(file->host_fd) != 0) {
if (fsync(file->fd) != 0) {
setLastErrorFromErrno();
return FALSE;
}
@ -505,16 +514,18 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
wibo::lastError = ERROR_SUCCESS;
HandleMeta meta{};
auto file = wibo::handles().getAs<files::FileObject>(hFile, &meta);
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
#ifdef CHECK_ACCESS
if ((meta.grantedAccess & FILE_READ_DATA) == 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("!!! DENIED: 0x%x\n", meta.grantedAccess);
return FALSE;
}
#endif
if (lpOverlapped == nullptr && lpNumberOfBytesRead == nullptr) {
wibo::lastError = ERROR_INVALID_PARAMETER;
@ -537,15 +548,8 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
if (io.unixError != 0) {
completionStatus = wibo::statusFromErrno(io.unixError);
wibo::lastError = wibo::winErrorFromErrno(io.unixError);
if (lpNumberOfBytesRead) {
*lpNumberOfBytesRead = static_cast<DWORD>(io.bytesTransferred);
}
if (lpOverlapped != nullptr) {
lpOverlapped->Internal = completionStatus;
lpOverlapped->InternalHigh = io.bytesTransferred;
signalOverlappedEvent(lpOverlapped);
}
return FALSE;
} else if (io.reachedEnd && io.bytesTransferred == 0) {
completionStatus = STATUS_END_OF_FILE;
}
if (lpNumberOfBytesRead && (!file->overlapped || lpOverlapped == nullptr)) {
@ -558,19 +562,18 @@ BOOL WIN_FUNC ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead
signalOverlappedEvent(lpOverlapped);
}
return TRUE;
return io.unixError == 0;
}
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) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return INVALID_HANDLE_VALUE;
}
HANDLE consoleHandle = INVALID_HANDLE_VALUE;
if (tryOpenConsoleDevice(dwDesiredAccess, dwShareMode, dwCreationDisposition, dwFlagsAndAttributes, consoleHandle,
std::string(lpFileName))) {
@ -578,77 +581,209 @@ HANDLE WIN_FUNC CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwSh
dwDesiredAccess, dwShareMode, dwFlagsAndAttributes, consoleHandle);
return consoleHandle;
}
std::string path = files::pathFromWindows(lpFileName);
std::filesystem::path hostPath = files::pathFromWindows(lpFileName);
std::string hostPathStr = hostPath.string();
DEBUG_LOG("CreateFileA(filename=%s (%s), desiredAccess=0x%x, shareMode=%u, securityAttributes=%p, "
"creationDisposition=%u, flagsAndAttributes=%u)\n",
lpFileName, path.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition,
dwFlagsAndAttributes);
lpFileName, hostPathStr.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes,
dwCreationDisposition, dwFlagsAndAttributes);
constexpr DWORD kAttributeMask = 0x0000FFFFu;
DWORD fileAttributes = dwFlagsAndAttributes & kAttributeMask;
bool backupSemantics = (dwFlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS) != 0;
bool deleteOnClose = (dwFlagsAndAttributes & FILE_FLAG_DELETE_ON_CLOSE) != 0;
bool overlapped = (dwFlagsAndAttributes & FILE_FLAG_OVERLAPPED) != 0;
std::error_code statusEc;
std::filesystem::file_status status = std::filesystem::status(hostPath, statusEc);
bool pathExists = !statusEc && status.type() != std::filesystem::file_type::not_found;
bool isDirectory = pathExists && status.type() == std::filesystem::file_type::directory;
if ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && !isDirectory) {
wibo::lastError = ERROR_INVALID_PARAMETER;
DEBUG_LOG("-> ERROR_INVALID_PARAMETER (ENOTDIR)\n");
return INVALID_HANDLE_VALUE;
}
if (isDirectory && (!backupSemantics || deleteOnClose /* not currently implemented for dir */)) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED (EISDIR)\n");
return INVALID_HANDLE_VALUE;
}
// TODO: verify share mode against existing opens
bool allowCreate = false;
bool truncateExisting = false;
bool existedBefore = pathExists;
wibo::lastError = ERROR_SUCCESS;
bool fileExists = (access(path.c_str(), F_OK) == 0);
bool shouldTruncate = false;
switch (dwCreationDisposition) {
case CREATE_ALWAYS:
if (fileExists) {
wibo::lastError = ERROR_ALREADY_EXISTS;
shouldTruncate = true;
}
break;
case CREATE_NEW:
if (fileExists) {
allowCreate = true;
if (pathExists) {
wibo::lastError = ERROR_FILE_EXISTS;
DEBUG_LOG("-> ERROR_FILE_EXISTS (EEXIST)");
return INVALID_HANDLE_VALUE;
}
break;
case CREATE_ALWAYS:
allowCreate = true;
if (isDirectory) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED (EISDIR)");
return INVALID_HANDLE_VALUE;
}
truncateExisting = pathExists;
break;
case OPEN_ALWAYS:
if (fileExists) {
wibo::lastError = ERROR_ALREADY_EXISTS;
if (!pathExists) {
allowCreate = true;
} else if (isDirectory) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED (EISDIR)");
return INVALID_HANDLE_VALUE;
}
break;
case OPEN_EXISTING:
if (!fileExists) {
if (!pathExists) {
wibo::lastError = ERROR_FILE_NOT_FOUND;
DEBUG_LOG("-> ERROR_FILE_NOT_FOUND (ENOENT)");
return INVALID_HANDLE_VALUE;
}
break;
case TRUNCATE_EXISTING:
shouldTruncate = true;
if (!fileExists) {
if (!pathExists) {
wibo::lastError = ERROR_FILE_NOT_FOUND;
DEBUG_LOG("-> ERROR_FILE_NOT_FOUND (ENOENT)");
return INVALID_HANDLE_VALUE;
}
if (isDirectory) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED (EISDIR)");
return INVALID_HANDLE_VALUE;
}
truncateExisting = true;
break;
default:
assert(false);
}
int fd = -1;
if (dwDesiredAccess == GENERIC_READ) {
fd = open(path.c_str(), O_RDONLY);
} else if (dwDesiredAccess == GENERIC_WRITE) {
if (shouldTruncate || !fileExists) {
fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
} else {
fd = open(path.c_str(), O_RDWR);
}
} else if (dwDesiredAccess == (GENERIC_READ | GENERIC_WRITE)) {
if (shouldTruncate || !fileExists) {
fd = open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
} else {
fd = open(path.c_str(), O_RDWR);
}
} else {
assert(false);
const auto &genericMapping =
isDirectory ? wibo::access::kDirectoryGenericMapping : wibo::access::kFileGenericMapping;
uint32_t supportedMask = isDirectory ? (FILE_ALL_ACCESS | wibo::access::kDirectorySpecificRightsMask)
: (FILE_ALL_ACCESS | wibo::access::kFileSpecificRightsMask);
uint32_t defaultMask = FILE_READ_ATTRIBUTES;
auto normalized =
wibo::access::normalizeDesiredAccess(dwDesiredAccess, genericMapping, supportedMask, SYNCHRONIZE, defaultMask);
if (normalized.deniedMask != 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED: denied mask 0x%x\n", normalized.deniedMask);
return INVALID_HANDLE_VALUE;
}
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;
bool wantsRead = containsAny(normalized.grantedMask, isDirectory ? kDirectoryReadMask : kFileReadMask) ||
containsAny(normalized.grantedMask, FILE_EXECUTE);
bool wantsWrite = containsAny(normalized.grantedMask, isDirectory ? kDirectoryWriteMask : kFileWriteMask);
bool appendRequested = !isDirectory && containsAny(normalized.grantedMask, FILE_APPEND_DATA);
bool appendOnly = appendRequested && !containsAny(normalized.grantedMask, FILE_WRITE_DATA);
#ifdef CHECK_ACCESS
if (allowCreate && !containsAny(normalized.grantedMask, FILE_WRITE_DATA | FILE_APPEND_DATA)) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED: FILE_WRITE_DATA | FILE_APPEND_DATA required for creation");
return INVALID_HANDLE_VALUE;
}
setLastErrorFromErrno();
return INVALID_HANDLE_VALUE;
if (truncateExisting && !containsAny(normalized.grantedMask, FILE_WRITE_DATA)) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED: FILE_WRITE_DATA required for truncation");
return INVALID_HANDLE_VALUE;
}
if (deleteOnClose && !containsAny(normalized.grantedMask, DELETE)) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("-> ERROR_ACCESS_DENIED: DELETE required for delete-on-close");
return INVALID_HANDLE_VALUE;
}
#else
(void)allowCreate;
#endif
uint32_t shareMask = dwShareMode & (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
int openFlags = O_CLOEXEC;
mode_t createMode = 0666;
bool requestCreate = false;
if (dwCreationDisposition == CREATE_NEW || dwCreationDisposition == CREATE_ALWAYS ||
(dwCreationDisposition == OPEN_ALWAYS && !pathExists)) {
requestCreate = true;
openFlags |= O_CREAT;
}
if (dwCreationDisposition == CREATE_NEW) {
openFlags |= O_EXCL;
}
if (truncateExisting && !isDirectory) {
openFlags |= O_TRUNC;
}
if (isDirectory) {
openFlags |= O_RDONLY | O_DIRECTORY;
wantsRead = true;
} else {
bool needWrite = wantsWrite || truncateExisting || requestCreate;
bool needRead = wantsRead || !needWrite;
if (needWrite && needRead) {
openFlags |= O_RDWR;
} else if (needWrite) {
openFlags |= O_WRONLY;
} else {
openFlags |= O_RDONLY;
}
if (appendOnly) {
openFlags |= O_APPEND;
}
}
int fd = open(hostPathStr.c_str(), openFlags, createMode);
if (fd < 0) {
setLastErrorFromErrno();
DEBUG_LOG("-> errno: %d\n", errno);
return INVALID_HANDLE_VALUE;
}
struct stat st{};
if (fstat(fd, &st) == 0 && S_ISDIR(st.st_mode)) {
isDirectory = true;
}
bool createdNew = !existedBefore && requestCreate;
std::filesystem::path canonicalPath = files::canonicalPath(hostPath);
Pin<FsObject> fsObject;
if (isDirectory) {
fsObject = make_pin<DirectoryObject>(fd);
} else {
auto fileObj = make_pin<FileObject>(fd);
fileObj->overlapped = overlapped;
fileObj->appendOnly = appendOnly;
fsObject = std::move(fileObj);
}
fsObject->canonicalPath = std::move(canonicalPath);
fsObject->shareAccess = shareMask;
fsObject->deletePending = deleteOnClose;
uint32_t handleFlags = 0;
if (lpSecurityAttributes && lpSecurityAttributes->bInheritHandle) {
handleFlags |= HANDLE_FLAG_INHERIT;
}
HANDLE handle = wibo::handles().alloc(std::move(fsObject), normalized.grantedMask, handleFlags);
if ((dwCreationDisposition == OPEN_ALWAYS && existedBefore) ||
(dwCreationDisposition == CREATE_ALWAYS && existedBefore)) {
wibo::lastError = ERROR_ALREADY_EXISTS;
} else {
wibo::lastError = ERROR_SUCCESS;
}
DEBUG_LOG("-> %p (createdNew=%d, truncate=%d)\n", handle, createdNew ? 1 : 0, truncateExisting ? 1 : 0);
return handle;
}
HANDLE WIN_FUNC CreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode,
@ -737,19 +872,24 @@ DWORD WIN_FUNC SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistan
wibo::lastError = ERROR_INVALID_HANDLE;
return INVALID_SET_FILE_POINTER;
}
FILE *fp = files::fpFromHandle(hFile);
if (!fp) {
HandleMeta meta{};
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file) {
wibo::lastError = ERROR_INVALID_HANDLE;
return INVALID_SET_FILE_POINTER;
}
int origin = SEEK_SET;
if (dwMoveMethod == FILE_CURRENT) {
origin = SEEK_CUR;
// TODO access check
std::lock_guard lk(file->m);
off64_t position = 0;
off64_t offset = static_cast<off64_t>(lDistanceToMove);
if (dwMoveMethod == FILE_BEGIN) {
position = offset;
} else if (dwMoveMethod == FILE_CURRENT) {
position = file->filePos + offset;
} else if (dwMoveMethod == FILE_END) {
origin = SEEK_END;
position = lseek64(file->fd, offset, SEEK_END);
}
wibo::lastError = ERROR_SUCCESS;
if (fseek(fp, lDistanceToMove, origin) < 0) {
if (position < 0) {
if (errno == EINVAL) {
wibo::lastError = ERROR_NEGATIVE_SEEK;
} else {
@ -757,14 +897,11 @@ DWORD WIN_FUNC SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistan
}
return INVALID_SET_FILE_POINTER;
}
long position = ftell(fp);
if (position < 0) {
setLastErrorFromErrno();
return INVALID_SET_FILE_POINTER;
}
file->filePos = position;
if (lpDistanceToMoveHigh) {
*lpDistanceToMoveHigh = static_cast<LONG>(static_cast<uint64_t>(position) >> 32);
}
wibo::lastError = ERROR_SUCCESS;
return static_cast<DWORD>(static_cast<uint64_t>(position) & 0xFFFFFFFFu);
}
@ -774,29 +911,38 @@ BOOL WIN_FUNC SetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLA
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
FILE *fp = files::fpFromHandle(hFile);
if (!fp) {
HandleMeta meta{};
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
int origin = SEEK_SET;
if (dwMoveMethod == FILE_CURRENT) {
origin = SEEK_CUR;
// TODO access check
std::lock_guard lk(file->m);
off64_t position = 0;
off64_t offset = static_cast<off64_t>(liDistanceToMove);
if (dwMoveMethod == FILE_BEGIN) {
position = offset;
} else if (dwMoveMethod == FILE_CURRENT) {
position = file->filePos + offset;
} else if (dwMoveMethod == FILE_END) {
origin = SEEK_END;
position = lseek64(file->fd, offset, SEEK_END);
}
wibo::lastError = ERROR_SUCCESS;
if (fseeko(fp, static_cast<off_t>(liDistanceToMove), origin) < 0) {
if (position < 0) {
if (errno == EINVAL) {
wibo::lastError = ERROR_NEGATIVE_SEEK;
} else {
wibo::lastError = ERROR_INVALID_PARAMETER;
}
return FALSE;
return INVALID_SET_FILE_POINTER;
}
off_t position = ftello(fp);
file->filePos = position;
if (position < 0) {
setLastErrorFromErrno();
if (errno == EINVAL) {
wibo::lastError = ERROR_NEGATIVE_SEEK;
} else {
wibo::lastError = ERROR_INVALID_PARAMETER;
}
return FALSE;
}
if (lpNewFilePointer) {
@ -807,12 +953,19 @@ BOOL WIN_FUNC SetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLA
BOOL WIN_FUNC SetEndOfFile(HANDLE hFile) {
DEBUG_LOG("SetEndOfFile(%p)\n", hFile);
FILE *fp = files::fpFromHandle(hFile);
if (!fp) {
HandleMeta meta{};
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
if (fflush(fp) != 0 || ftruncate(fileno(fp), ftell(fp)) != 0) {
// TODO access check
std::lock_guard lk(file->m);
if (file->filePos < 0) {
setLastErrorFromErrno();
return FALSE;
}
if (ftruncate64(file->fd, file->filePos) != 0) {
setLastErrorFromErrno();
return FALSE;
}
@ -857,20 +1010,29 @@ BOOL WIN_FUNC SetFileAttributesA(LPCSTR lpFileName, DWORD dwFileAttributes) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
DEBUG_LOG("SetFileAttributesA(%s, %u)\n", lpFileName, dwFileAttributes);
DEBUG_LOG("STUB: SetFileAttributesA(%s, %u)\n", lpFileName, dwFileAttributes);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
DWORD WIN_FUNC GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
DEBUG_LOG("GetFileSize(%p, %p) ", hFile, lpFileSizeHigh);
int64_t size = getFileSizeFromHandle(hFile);
// TODO access check
auto file = wibo::handles().getAs<FileObject>(hFile);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
DEBUG_LOG("-> INVALID_FILE_SIZE (ERROR_INVALID_HANDLE)\n");
return INVALID_FILE_SIZE;
}
const auto size = lseek64(file->fd, 0, SEEK_END);
if (size < 0) {
if (lpFileSizeHigh) {
*lpFileSizeHigh = 0;
}
DEBUG_LOG("-> INVALID_FILE_SIZE\n");
return INVALID_FILE_SIZE;
}
DEBUG_LOG("-> %lld\n", size);
uint64_t uSize = static_cast<uint64_t>(size);
if (lpFileSizeHigh) {
*lpFileSizeHigh = static_cast<DWORD>(uSize >> 32);
@ -883,18 +1045,20 @@ BOOL WIN_FUNC GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lp
LPFILETIME lpLastWriteTime) {
DEBUG_LOG("GetFileTime(%p, %p, %p, %p)\n", hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
HandleMeta meta{};
auto file = wibo::handles().getAs<files::FileObject>(hFile, &meta);
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
#ifdef CHECK_ACCESS
if ((meta.grantedAccess & FILE_READ_ATTRIBUTES) == 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
return FALSE;
}
#endif
struct stat st{};
if (fstat(file->host_fd, &st) != 0) {
if (fstat(file->fd, &st) != 0) {
setLastErrorFromErrno();
return FALSE;
}
@ -923,15 +1087,17 @@ BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FI
const FILETIME *lpLastWriteTime) {
DEBUG_LOG("SetFileTime(%p, %p, %p, %p)\n", hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
HandleMeta meta{};
auto file = wibo::handles().getAs<files::FileObject>(hFile, &meta);
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
#ifdef CHECK_ACCESS
if ((meta.grantedAccess & FILE_WRITE_ATTRIBUTES) == 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
return FALSE;
}
#endif
bool changeAccess = !shouldIgnoreFileTimeParam(lpLastAccessTime);
bool changeWrite = !shouldIgnoreFileTimeParam(lpLastWriteTime);
@ -940,7 +1106,7 @@ BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FI
return TRUE;
}
struct stat st{};
if (fstat(file->host_fd, &st) != 0) {
if (fstat(file->fd, &st) != 0) {
setLastErrorFromErrno();
return FALSE;
}
@ -970,13 +1136,13 @@ BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FI
tv[0].tv_usec = accessSpec.tv_nsec / 1000L;
tv[1].tv_sec = writeSpec.tv_sec;
tv[1].tv_usec = writeSpec.tv_nsec / 1000L;
if (futimes(fd, tv) != 0) {
if (futimes(file->fd, tv) != 0) {
setLastErrorFromErrno();
return FALSE;
}
#else
struct timespec times[2] = {accessSpec, writeSpec};
if (futimens(file->host_fd, times) != 0) {
if (futimens(file->fd, times) != 0) {
setLastErrorFromErrno();
return FALSE;
}
@ -990,48 +1156,52 @@ BOOL WIN_FUNC SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FI
BOOL WIN_FUNC GetFileInformationByHandle(HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation) {
DEBUG_LOG("GetFileInformationByHandle(%p, %p)\n", hFile, lpFileInformation);
FILE *fp = files::fpFromHandle(hFile);
if (fp == nullptr) {
if (!lpFileInformation) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
HandleMeta meta{};
auto file = wibo::handles().getAs<FileObject>(hFile, &meta);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
// TODO access check
struct stat64 st{};
if (fstat64(fileno(fp), &st) != 0) {
if (fstat64(file->fd, &st) != 0) {
setLastErrorFromErrno();
return FALSE;
}
if (lpFileInformation) {
lpFileInformation->dwFileAttributes = 0;
if (S_ISDIR(st.st_mode)) {
lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
}
if (S_ISREG(st.st_mode)) {
lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
}
lpFileInformation->ftCreationTime = kDefaultFileInformationTime;
lpFileInformation->ftLastAccessTime = kDefaultFileInformationTime;
lpFileInformation->ftLastWriteTime = kDefaultFileInformationTime;
lpFileInformation->dwVolumeSerialNumber = 0;
lpFileInformation->nFileSizeHigh = static_cast<DWORD>(static_cast<uint64_t>(st.st_size) >> 32);
lpFileInformation->nFileSizeLow = static_cast<DWORD>(st.st_size & 0xFFFFFFFFULL);
lpFileInformation->nNumberOfLinks = 0;
lpFileInformation->nFileIndexHigh = 0;
lpFileInformation->nFileIndexLow = 0;
lpFileInformation->dwFileAttributes = 0;
if (S_ISDIR(st.st_mode)) {
lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
}
if (S_ISREG(st.st_mode)) {
lpFileInformation->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
}
lpFileInformation->ftCreationTime = kDefaultFileInformationTime;
lpFileInformation->ftLastAccessTime = kDefaultFileInformationTime;
lpFileInformation->ftLastWriteTime = kDefaultFileInformationTime;
lpFileInformation->dwVolumeSerialNumber = 0;
lpFileInformation->nFileSizeHigh = static_cast<DWORD>(static_cast<uint64_t>(st.st_size) >> 32);
lpFileInformation->nFileSizeLow = static_cast<DWORD>(st.st_size & 0xFFFFFFFFULL);
lpFileInformation->nNumberOfLinks = 0;
lpFileInformation->nFileIndexHigh = 0;
lpFileInformation->nFileIndexLow = 0;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
DWORD WIN_FUNC GetFileType(HANDLE hFile) {
DEBUG_LOG("GetFileType(%p) ", hFile);
auto file = wibo::handles().getAs<files::FileObject>(hFile);
auto file = wibo::handles().getAs<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->host_fd, &st) != 0) {
if (fstat(file->fd, &st) != 0) {
setLastErrorFromErrno();
DEBUG_LOG("-> fstat error\n");
return FILE_TYPE_UNKNOWN;

View File

@ -19,10 +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 FILE_SHARE_READ = 0x00000001;
constexpr DWORD FILE_SHARE_WRITE = 0x00000002;
constexpr DWORD FILE_SHARE_DELETE = 0x00000004;
constexpr DWORD CREATE_NEW = 1;
constexpr DWORD CREATE_ALWAYS = 2;
constexpr DWORD OPEN_EXISTING = 3;

View File

@ -1,11 +1,8 @@
#include "handleapi.h"
#include "dll/advapi32/internal.h"
#include "errors.h"
#include "files.h"
#include "handles.h"
#include "internal.h"
#include "processes.h"
#include <pthread.h>
#include <unistd.h>
@ -17,7 +14,6 @@ BOOL WIN_FUNC DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle,
DEBUG_LOG("DuplicateHandle(%p, %p, %p, %p, %x, %d, %x)\n", hSourceProcessHandle, hSourceHandle,
hTargetProcessHandle, lpTargetHandle, dwDesiredAccess, bInheritHandle, dwOptions);
(void)dwDesiredAccess;
(void)bInheritHandle;
(void)dwOptions;
if (!lpTargetHandle) {
wibo::lastError = ERROR_INVALID_PARAMETER;
@ -25,15 +21,10 @@ BOOL WIN_FUNC DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle,
}
auto validateProcessHandle = [&](HANDLE handle) -> bool {
uintptr_t raw = reinterpret_cast<uintptr_t>(handle);
if (raw == static_cast<uintptr_t>(-1)) {
if (reinterpret_cast<uintptr_t>(handle) == kPseudoCurrentProcessHandleValue) {
return true;
}
auto data = handles::dataFromHandle(handle, false);
if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) {
return false;
}
auto *proc = reinterpret_cast<processes::Process *>(data.ptr);
auto proc = wibo::handles().getAs<ProcessObject>(handle);
return proc && proc->pid == getpid();
};
@ -44,125 +35,35 @@ BOOL WIN_FUNC DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle,
return FALSE;
}
auto file = files::fileHandleFromHandle(hSourceHandle);
if (file && (file->fp == stdin || file->fp == stdout || file->fp == stderr)) {
HANDLE handle = files::duplicateFileHandle(file, false);
DEBUG_LOG("DuplicateHandle: duplicated std handle -> %p\n", handle);
*lpTargetHandle = handle;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
uintptr_t sourceHandleRaw = reinterpret_cast<uintptr_t>(hSourceHandle);
if (sourceHandleRaw == static_cast<uintptr_t>(-1)) {
HANDLE handle = processes::allocProcessHandle(getpid());
processes::Process *proc = processes::processFromHandle(handle, false);
if (proc) {
proc->exitCode = STILL_ACTIVE;
proc->forcedExitCode = STILL_ACTIVE;
proc->terminationRequested = false;
}
if (sourceHandleRaw == kPseudoCurrentProcessHandleValue) {
auto po = make_pin<ProcessObject>(getpid(), -1);
auto handle = wibo::handles().alloc(std::move(po), 0, 0);
DEBUG_LOG("DuplicateHandle: created process handle for current process -> %p\n", handle);
*lpTargetHandle = handle;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
if (sourceHandleRaw == kPseudoCurrentThreadHandleValue) {
ThreadObject *obj = ensureCurrentThreadObject();
if (obj) {
retainThreadObject(obj);
HANDLE handle = handles::allocDataHandle({handles::TYPE_THREAD, obj, 0});
DEBUG_LOG("DuplicateHandle: duplicated pseudo current thread -> %p\n", handle);
*lpTargetHandle = handle;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
ThreadObject *syntheticObj = new ThreadObject();
syntheticObj->thread = pthread_self();
syntheticObj->finished = false;
syntheticObj->joined = false;
syntheticObj->detached = true;
syntheticObj->synthetic = true;
syntheticObj->exitCode = 0;
syntheticObj->refCount = 1;
syntheticObj->suspendCount = 0;
pthread_mutex_init(&syntheticObj->mutex, nullptr);
pthread_cond_init(&syntheticObj->cond, nullptr);
HANDLE handle = handles::allocDataHandle({handles::TYPE_THREAD, syntheticObj, 0});
DEBUG_LOG("DuplicateHandle: created synthetic thread handle -> %p\n", handle);
} else if (sourceHandleRaw == kPseudoCurrentThreadHandleValue) {
auto th = make_pin<ThreadObject>(pthread_self());
auto handle = wibo::handles().alloc(std::move(th), 0, 0);
DEBUG_LOG("DuplicateHandle: created thread handle for current thread -> %p\n", handle);
*lpTargetHandle = handle;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
handles::Data data = handles::dataFromHandle(hSourceHandle, false);
if (data.type == handles::TYPE_PROCESS && data.ptr) {
auto *original = reinterpret_cast<processes::Process *>(data.ptr);
HANDLE handle = processes::allocProcessHandle(original->pid);
auto *copy = processes::processFromHandle(handle, false);
if (copy) {
*copy = *original;
}
DEBUG_LOG("DuplicateHandle: duplicated process handle -> %p\n", handle);
*lpTargetHandle = handle;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
if (!wibo::handles().duplicateTo(hSourceHandle, wibo::handles(), *lpTargetHandle, dwDesiredAccess, bInheritHandle,
dwOptions)) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
if (data.type == handles::TYPE_THREAD && data.ptr) {
auto *threadObj = reinterpret_cast<ThreadObject *>(data.ptr);
if (!retainThreadObject(threadObj)) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
HANDLE handle = handles::allocDataHandle({handles::TYPE_THREAD, threadObj, 0});
DEBUG_LOG("DuplicateHandle: duplicated thread handle -> %p\n", handle);
*lpTargetHandle = handle;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
DEBUG_LOG("DuplicateHandle: unsupported handle type for %p\n", hSourceHandle);
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
BOOL WIN_FUNC CloseHandle(HANDLE hObject) {
DEBUG_LOG("CloseHandle(%p)\n", hObject);
auto data = handles::dataFromHandle(hObject, true);
if (data.type == handles::TYPE_UNUSED || data.ptr == nullptr) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
bool success = true;
if (data.type == handles::TYPE_FILE) {
auto file = reinterpret_cast<files::FileHandle *>(data.ptr);
if (file) {
if (file->closeOnDestroy && file->fp && !(file->fp == stdin || file->fp == stdout || file->fp == stderr)) {
fclose(file->fp);
}
delete file;
} else {
success = false;
}
} else if (data.type == handles::TYPE_MAPPED) {
if (!closeFileMappingHandle(data.ptr)) {
success = false;
}
} else if (data.type == handles::TYPE_PROCESS) {
delete reinterpret_cast<processes::Process *>(data.ptr);
} else if (data.type == handles::TYPE_TOKEN) {
delete reinterpret_cast<TokenObject *>(data.ptr);
} else if (data.type == handles::TYPE_MUTEX) {
releaseMutexObject(reinterpret_cast<MutexObject *>(data.ptr));
} else if (data.type == handles::TYPE_EVENT) {
releaseEventObject(reinterpret_cast<EventObject *>(data.ptr));
} else if (data.type == handles::TYPE_THREAD) {
releaseThreadObject(reinterpret_cast<ThreadObject *>(data.ptr));
} else if (data.type == handles::TYPE_SEMAPHORE) {
releaseSemaphoreObject(reinterpret_cast<SemaphoreObject *>(data.ptr));
} else {
success = false;
}
if (!success) {
if (!wibo::handles().release(hObject)) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}

View File

@ -7,71 +7,36 @@
#include <algorithm>
#include <mimalloc.h>
#include <mutex>
#include <new>
#include <sys/mman.h>
using kernel32::HeapObject;
namespace {
struct HeapRecord {
mi_heap_t *heap = nullptr;
DWORD createFlags = 0;
SIZE_T initialSize = 0;
SIZE_T maximumSize = 0;
DWORD compatibility = 0;
bool isProcessHeap = false;
};
std::once_flag g_processHeapInitFlag;
HANDLE g_processHeapHandle = nullptr;
HeapRecord *g_processHeapRecord = nullptr;
HeapObject *g_processHeapRecord = nullptr;
void ensureProcessHeapInitialized() {
std::call_once(g_processHeapInitFlag, []() {
mi_heap_t *heap = mi_heap_get_default();
auto *record = new (std::nothrow) HeapRecord{};
auto record = make_pin<HeapObject>(heap);
if (!record) {
return;
}
record->heap = heap;
record->isProcessHeap = true;
g_processHeapRecord = record;
g_processHeapHandle = handles::allocDataHandle({handles::TYPE_HEAP, record, 0});
g_processHeapRecord = record.get();
g_processHeapHandle = wibo::handles().alloc(std::move(record), 0, 0);
});
}
HeapRecord *activeHeapRecord(HANDLE hHeap) {
if (!hHeap) {
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
ensureProcessHeapInitialized();
auto data = handles::dataFromHandle(hHeap, false);
if (data.type != handles::TYPE_HEAP || data.ptr == nullptr) {
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
wibo::lastError = ERROR_SUCCESS;
return static_cast<HeapRecord *>(data.ptr);
}
HeapRecord *popHeapRecord(HANDLE hHeap) {
ensureProcessHeapInitialized();
auto preview = handles::dataFromHandle(hHeap, false);
if (preview.type != handles::TYPE_HEAP || preview.ptr == nullptr) {
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
auto data = handles::dataFromHandle(hHeap, true);
wibo::lastError = ERROR_SUCCESS;
return static_cast<HeapRecord *>(data.ptr);
}
bool isExecutableHeap(const HeapRecord *record) {
bool isExecutableHeap(const HeapObject *record) {
return record && ((record->createFlags & HEAP_CREATE_ENABLE_EXECUTE) != 0);
}
LPVOID heapAllocFromRecord(HeapRecord *record, DWORD dwFlags, SIZE_T dwBytes) {
if (!record) {
LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) {
if (!record || !record->heap) {
return nullptr;
}
if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) {
@ -95,6 +60,19 @@ LPVOID heapAllocFromRecord(HeapRecord *record, DWORD dwFlags, SIZE_T dwBytes) {
} // namespace
HeapObject::~HeapObject() {
if (heap) {
if (!isProcessHeap) {
mi_heap_destroy(heap);
}
heap = nullptr;
}
if (isProcessHeap) {
g_processHeapHandle = nullptr;
g_processHeapRecord = nullptr;
}
}
namespace kernel32 {
HANDLE WIN_FUNC HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize) {
@ -110,40 +88,32 @@ HANDLE WIN_FUNC HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaxim
return nullptr;
}
auto *record = new (std::nothrow) HeapRecord{};
if (!record) {
mi_heap_delete(heap);
wibo::lastError = ERROR_NOT_ENOUGH_MEMORY;
return nullptr;
}
auto record = make_pin<HeapObject>(heap);
record->heap = heap;
record->createFlags = flOptions;
record->initialSize = dwInitialSize;
record->maximumSize = dwMaximumSize;
record->isProcessHeap = false;
HANDLE handle = handles::allocDataHandle({handles::TYPE_HEAP, record, 0});
HANDLE handle = wibo::handles().alloc(std::move(record), 0, 0);
wibo::lastError = ERROR_SUCCESS;
return handle;
}
BOOL WIN_FUNC HeapDestroy(HANDLE hHeap) {
DEBUG_LOG("HeapDestroy(%p)\n", hHeap);
HeapRecord *record = activeHeapRecord(hHeap);
auto record = wibo::handles().getAs<HeapObject>(hHeap);
if (!record) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
if (record->isProcessHeap) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
record = popHeapRecord(hHeap);
if (!record) {
std::lock_guard lk(record->m);
if (record->isProcessHeap || record->heap == nullptr) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
mi_heap_destroy(record->heap);
delete record;
record->heap = nullptr;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
@ -159,18 +129,23 @@ BOOL WIN_FUNC HeapSetInformation(HANDLE HeapHandle, HEAP_INFORMATION_CLASS HeapI
SIZE_T HeapInformationLength) {
DEBUG_LOG("HeapSetInformation(%p, %d, %p, %zu)\n", HeapHandle, static_cast<int>(HeapInformationClass),
HeapInformation, HeapInformationLength);
ensureProcessHeapInitialized();
auto record = wibo::handles().getAs<HeapObject>(HeapHandle);
if (!record) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
std::lock_guard lk(record->m);
if (!record->heap) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
switch (HeapInformationClass) {
case HeapCompatibilityInformation: {
if (!HeapInformation || HeapInformationLength < sizeof(ULONG)) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
HeapRecord *target = HeapHandle ? activeHeapRecord(HeapHandle) : g_processHeapRecord;
if (!target) {
return FALSE;
}
target->compatibility = *static_cast<ULONG *>(HeapInformation);
record->compatibility = *static_cast<ULONG *>(HeapInformation);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
@ -188,36 +163,51 @@ BOOL WIN_FUNC HeapSetInformation(HANDLE HeapHandle, HEAP_INFORMATION_CLASS HeapI
LPVOID WIN_FUNC HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) {
DEBUG_LOG("HeapAlloc(%p, 0x%x, %zu) ", hHeap, dwFlags, dwBytes);
HeapRecord *record = activeHeapRecord(hHeap);
auto record = wibo::handles().getAs<HeapObject>(hHeap);
if (!record) {
DEBUG_LOG("-> NULL\n");
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
void *mem = heapAllocFromRecord(record, dwFlags, dwBytes);
std::lock_guard lk(record->m);
if (!record->heap) {
DEBUG_LOG("-> NULL\n");
wibo::lastError = ERROR_INVALID_PARAMETER;
return nullptr;
}
void *mem = heapAllocFromRecord(record.get(), dwFlags, dwBytes);
DEBUG_LOG("-> %p\n", mem);
return mem;
}
LPVOID WIN_FUNC HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes) {
DEBUG_LOG("HeapReAlloc(%p, 0x%x, %p, %zu) ", hHeap, dwFlags, lpMem, dwBytes);
HeapRecord *record = activeHeapRecord(hHeap);
auto record = wibo::handles().getAs<HeapObject>(hHeap);
if (!record) {
DEBUG_LOG("-> NULL\n");
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
std::lock_guard lk(record->m);
if (!record->heap) {
DEBUG_LOG("-> NULL\n");
wibo::lastError = ERROR_INVALID_PARAMETER;
return nullptr;
}
if (lpMem == nullptr) {
void *alloc = heapAllocFromRecord(record, dwFlags, dwBytes);
void *alloc = heapAllocFromRecord(record.get(), dwFlags, dwBytes);
DEBUG_LOG("-> %p (alloc)\n", alloc);
wibo::lastError = ERROR_SUCCESS;
return alloc;
}
if (!mi_heap_check_owned(record->heap, lpMem)) {
wibo::lastError = ERROR_INVALID_PARAMETER;
DEBUG_LOG("-> NULL (not owned)\n");
return nullptr;
}
// if (!mi_heap_check_owned(record->heap, lpMem)) {
// DEBUG_LOG("-> NULL (not owned)\n");
// wibo::lastError = ERROR_INVALID_PARAMETER;
// return nullptr;
// }
if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) {
DEBUG_LOG("-> NULL (exceptions unsupported)\n");
wibo::lastError = ERROR_INVALID_PARAMETER;
wibo::lastError = ERROR_NOT_SUPPORTED;
return nullptr;
}
const bool inplaceOnly = (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY) != 0;
@ -225,12 +215,12 @@ LPVOID WIN_FUNC HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dw
if (dwBytes == 0) {
if (!inplaceOnly) {
mi_free(lpMem);
wibo::lastError = ERROR_SUCCESS;
DEBUG_LOG("-> NULL (freed)\n");
wibo::lastError = ERROR_SUCCESS;
return nullptr;
}
wibo::lastError = ERROR_NOT_ENOUGH_MEMORY;
DEBUG_LOG("-> NULL (zero size with in-place flag)\n");
wibo::lastError = ERROR_NOT_ENOUGH_MEMORY;
return nullptr;
}
@ -238,12 +228,12 @@ LPVOID WIN_FUNC HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dw
const SIZE_T oldSize = mi_usable_size(lpMem);
if (inplaceOnly || requestSize <= oldSize) {
if (requestSize > oldSize) {
wibo::lastError = ERROR_NOT_ENOUGH_MEMORY;
DEBUG_LOG("-> NULL (cannot grow in place)\n");
wibo::lastError = ERROR_NOT_ENOUGH_MEMORY;
return nullptr;
}
wibo::lastError = ERROR_SUCCESS;
DEBUG_LOG("-> %p (in-place)\n", lpMem);
wibo::lastError = ERROR_SUCCESS;
return lpMem;
}
@ -259,7 +249,7 @@ LPVOID WIN_FUNC HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dw
memset(static_cast<char *>(ret) + oldSize, 0, zeroLen);
}
}
if (isExecutableHeap(record)) {
if (isExecutableHeap(record.get())) {
tryMarkExecutable(ret);
}
wibo::lastError = ERROR_SUCCESS;
@ -270,18 +260,24 @@ LPVOID WIN_FUNC HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dw
SIZE_T WIN_FUNC HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem) {
DEBUG_LOG("HeapSize(%p, 0x%x, %p)\n", hHeap, dwFlags, lpMem);
(void)dwFlags;
HeapRecord *record = activeHeapRecord(hHeap);
auto record = wibo::handles().getAs<HeapObject>(hHeap);
if (!record) {
wibo::lastError = ERROR_INVALID_HANDLE;
return static_cast<SIZE_T>(-1);
}
std::lock_guard lk(record->m);
if (!record->heap) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return static_cast<SIZE_T>(-1);
}
if (!lpMem) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return static_cast<SIZE_T>(-1);
}
if (!mi_heap_check_owned(record->heap, const_cast<LPVOID>(lpMem))) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return static_cast<SIZE_T>(-1);
}
// if (!mi_heap_check_owned(record->heap, const_cast<LPVOID>(lpMem))) {
// wibo::lastError = ERROR_INVALID_PARAMETER;
// return static_cast<SIZE_T>(-1);
// }
size_t size = mi_usable_size(lpMem);
wibo::lastError = ERROR_SUCCESS;
return static_cast<SIZE_T>(size);
@ -294,15 +290,24 @@ BOOL WIN_FUNC HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) {
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
HeapRecord *record = activeHeapRecord(hHeap);
auto record = wibo::handles().getAs<HeapObject>(hHeap);
if (!record) {
DEBUG_LOG("-> INVALID_HANDLE\n");
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
if (!mi_heap_check_owned(record->heap, lpMem)) {
std::lock_guard lk(record->m);
if (!record->heap) {
DEBUG_LOG("-> INVALID_PARAMETER\n");
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
// if (!mi_heap_check_owned(record->heap, lpMem)) {
// wibo::lastError = ERROR_INVALID_PARAMETER;
// return FALSE;
// }
mi_free(lpMem);
DEBUG_LOG("-> SUCCESS\n");
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}

View File

@ -1,68 +1,172 @@
#pragma once
#include "common.h"
#include "minwinbase.h"
#include "handles.h"
#include "mimalloc.h"
#include <pthread.h>
#include <string>
namespace kernel32 {
struct ThreadObject {
pthread_t thread{};
bool finished = false;
bool joined = false;
bool detached = false;
bool synthetic = false;
DWORD exitCode = 0;
int refCount = 1;
pthread_mutex_t mutex{};
pthread_cond_t cond{};
struct FsObject : ObjectBase {
std::mutex m;
int fd = -1;
std::filesystem::path canonicalPath;
uint32_t shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE;
bool deletePending = false;
bool closeOnDestroy = true;
~FsObject() override;
[[nodiscard]] bool valid() const { return fd >= 0; }
protected:
explicit FsObject(ObjectType type, int fd) : ObjectBase(type), fd(fd) {}
};
struct FileObject final : FsObject {
static constexpr ObjectType kType = ObjectType::File;
off64_t filePos = 0;
bool overlapped = false;
bool appendOnly = false;
bool seekable = true;
explicit FileObject(int fd) : FsObject(kType, fd) {
if (fd >= 0) {
off64_t pos = lseek64(fd, 0, SEEK_CUR);
if (pos == -1 && errno == ESPIPE) {
seekable = false;
} else if (pos >= 0) {
filePos = pos;
}
}
}
};
struct DirectoryObject final : FsObject {
static constexpr ObjectType kType = ObjectType::Directory;
uint64_t enumCookie = 0;
explicit DirectoryObject(int dirfd) : FsObject(kType, dirfd) {}
};
struct ProcessObject final : WaitableObject {
static constexpr ObjectType kType = ObjectType::Process;
pid_t pid;
int pidfd;
DWORD exitCode = STILL_ACTIVE;
bool forcedExitCode = false;
explicit ProcessObject(pid_t pid, int pidfd) : WaitableObject(kType), pid(pid), pidfd(pidfd) {}
~ProcessObject() override {
std::lock_guard lk(m);
if (pidfd != -1) {
close(pidfd);
pidfd = -1;
}
}
};
struct ThreadObject final : WaitableObject {
static constexpr ObjectType kType = ObjectType::Thread;
pthread_t thread;
DWORD exitCode = STILL_ACTIVE;
unsigned int suspendCount = 0;
TIB *tib = nullptr;
explicit ThreadObject(pthread_t thread) : WaitableObject(kType), thread(thread) {}
~ThreadObject() override {
std::lock_guard lk(m);
// Threads are detached at creation; we can safely drop
if (tib) {
wibo::destroyTib(tib);
tib = nullptr;
}
}
};
struct MutexObject {
pthread_mutex_t mutex{};
struct MutexObject final : WaitableObject {
static constexpr ObjectType kType = ObjectType::Mutex;
bool ownerValid = false;
pthread_t owner = 0;
pthread_t owner{};
unsigned int recursionCount = 0;
std::u16string name;
int refCount = 1;
bool abandoned = false; // Owner exited without releasing
MutexObject() : WaitableObject(kType) { signaled.store(true, std::memory_order_relaxed); }
void noteOwnerExit(/*pthread_t tid or emu tid*/) {
std::lock_guard lk(m);
if (ownerValid /* && matches tid if you store emu id */) {
ownerValid = false;
recursionCount = 0;
abandoned = true;
signaled.store(true, std::memory_order_release);
cv.notify_one();
}
}
};
struct EventObject {
pthread_mutex_t mutex{};
pthread_cond_t cond{};
struct EventObject final : WaitableObject {
static constexpr ObjectType kType = ObjectType::Event;
bool manualReset = false;
bool signaled = false;
std::u16string name;
int refCount = 1;
explicit EventObject(bool manual) : WaitableObject(kType), manualReset(manual) {}
void set() {
bool resetAll = false;
{
std::lock_guard lk(m);
signaled.store(true, std::memory_order_release);
resetAll = manualReset;
}
if (resetAll) {
cv.notify_all();
} else {
cv.notify_one();
}
}
void reset() {
std::lock_guard lk(m);
signaled.store(false, std::memory_order_release);
}
};
struct SemaphoreObject {
pthread_mutex_t mutex{};
pthread_cond_t cond{};
struct SemaphoreObject final : WaitableObject {
static constexpr ObjectType kType = ObjectType::Semaphore;
LONG count = 0;
LONG maxCount = 0;
std::u16string name;
int refCount = 1;
SemaphoreObject(LONG initial, LONG maximum) : WaitableObject(kType), count(initial), maxCount(maximum) {}
};
struct HeapObject : public ObjectBase {
static constexpr ObjectType kType = ObjectType::Heap;
std::mutex m;
mi_heap_t *heap;
DWORD createFlags = 0;
SIZE_T initialSize = 0;
SIZE_T maximumSize = 0;
DWORD compatibility = 0;
bool isProcessHeap = false;
explicit HeapObject(mi_heap_t *heap) : ObjectBase(kType), heap(heap) {}
~HeapObject() override;
};
inline constexpr uintptr_t kPseudoCurrentProcessHandleValue = static_cast<uintptr_t>(-1);
inline constexpr uintptr_t kPseudoCurrentThreadHandleValue = static_cast<uintptr_t>(-2);
void releaseMutexObject(MutexObject *obj);
void releaseEventObject(EventObject *obj);
void releaseSemaphoreObject(SemaphoreObject *obj);
void resetOverlappedEvent(OVERLAPPED *ov);
void signalOverlappedEvent(OVERLAPPED *ov);
void tryMarkExecutable(void *mem);
void setLastErrorFromErrno();
bool closeFileMappingHandle(void *mappingPtr);
int64_t getFileSizeFromHandle(HANDLE hFile);
ThreadObject *ensureCurrentThreadObject();
ThreadObject *threadObjectFromHandle(HANDLE hThread);
ThreadObject *retainThreadObject(ThreadObject *obj);
void releaseThreadObject(ThreadObject *obj);
} // namespace kernel32

View File

@ -13,18 +13,13 @@ BOOL WIN_FUNC GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWO
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
if (bWait && lpOverlapped->Internal == STATUS_PENDING) {
if (lpOverlapped->hEvent) {
WaitForSingleObject(lpOverlapped->hEvent, 0xFFFFFFFF);
}
if (bWait && lpOverlapped->Internal == STATUS_PENDING && lpOverlapped->hEvent) {
WaitForSingleObject(lpOverlapped->hEvent, INFINITE);
}
const auto status = static_cast<NTSTATUS>(lpOverlapped->Internal);
if (status == STATUS_PENDING) {
wibo::lastError = ERROR_IO_INCOMPLETE;
if (lpNumberOfBytesTransferred) {
*lpNumberOfBytesTransferred = static_cast<DWORD>(lpOverlapped->InternalHigh);
}
return FALSE;
}
@ -36,7 +31,7 @@ BOOL WIN_FUNC GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWO
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
if (status == STATUS_END_OF_FILE || status == ERROR_HANDLE_EOF) {
if (status == STATUS_END_OF_FILE) {
wibo::lastError = ERROR_HANDLE_EOF;
return FALSE;
}

View File

@ -1,12 +1,10 @@
#include "memoryapi.h"
#include "common.h"
#include "errors.h"
#include "files.h"
#include "handles.h"
#include "internal.h"
#include "strutil.h"
#include <algorithm>
#include <cerrno>
#include <fcntl.h>
#include <iterator>
@ -23,21 +21,34 @@ namespace {
constexpr size_t kVirtualAllocationGranularity = 64 * 1024;
constexpr uintptr_t kProcessAddressLimit = 0x80000000;
struct MappingObject {
struct MappingObject : ObjectBase {
static constexpr ObjectType kType = ObjectType::Mapping;
std::mutex m;
int fd = -1;
size_t maxSize = 0;
DWORD protect = 0;
bool anonymous = false;
bool closed = false;
size_t refCount = 0;
explicit MappingObject() : ObjectBase(kType) {}
~MappingObject() override;
};
MappingObject::~MappingObject() {
std::lock_guard lk(m);
if (fd != -1) {
close(fd);
fd = -1;
}
}
struct ViewInfo {
uintptr_t viewBase = 0;
size_t viewLength = 0;
uintptr_t allocationBase = 0;
size_t allocationLength = 0;
MappingObject *owner = nullptr;
Pin<MappingObject> owner;
DWORD protect = PAGE_NOACCESS;
DWORD allocationProtect = PAGE_NOACCESS;
DWORD type = MEM_PRIVATE;
@ -46,26 +57,6 @@ struct ViewInfo {
std::map<uintptr_t, ViewInfo> g_viewInfo;
std::mutex g_viewInfoMutex;
void closeMappingIfPossible(MappingObject *mapping) {
if (!mapping) {
return;
}
if (mapping->fd != -1) {
close(mapping->fd);
mapping->fd = -1;
}
delete mapping;
}
void tryReleaseMapping(MappingObject *mapping) {
if (!mapping) {
return;
}
if (mapping->closed && mapping->refCount == 0) {
closeMappingIfPossible(mapping);
}
}
struct VirtualAllocation {
uintptr_t base = 0;
size_t size = 0;
@ -175,9 +166,8 @@ DWORD desiredAccessToProtect(DWORD desiredAccess, DWORD mappingProtect) {
if (wantCopy) {
wantWrite = true;
}
const bool supportsWrite =
mappingProtect == PAGE_READWRITE || mappingProtect == PAGE_EXECUTE_READWRITE || mappingProtect == PAGE_WRITECOPY ||
mappingProtect == PAGE_EXECUTE_WRITECOPY;
const bool supportsWrite = mappingProtect == PAGE_READWRITE || mappingProtect == PAGE_EXECUTE_READWRITE ||
mappingProtect == PAGE_WRITECOPY || mappingProtect == PAGE_EXECUTE_WRITECOPY;
const bool supportsCopy = mappingProtect == PAGE_WRITECOPY || mappingProtect == PAGE_EXECUTE_WRITECOPY;
if (wantCopy && !supportsCopy) {
@ -192,7 +182,7 @@ DWORD desiredAccessToProtect(DWORD desiredAccess, DWORD mappingProtect) {
}
}
if (!wantRead && (mappingProtect == PAGE_READONLY || mappingProtect == PAGE_EXECUTE_READ ||
mappingProtect == PAGE_WRITECOPY || mappingProtect == PAGE_EXECUTE_WRITECOPY)) {
mappingProtect == PAGE_WRITECOPY || mappingProtect == PAGE_EXECUTE_WRITECOPY)) {
wantRead = true;
}
@ -276,7 +266,7 @@ bool moduleRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info)
uintptr_t blockStart = sections[matchIndex].base;
uintptr_t blockEnd = sections[matchIndex].base + sections[matchIndex].size;
DWORD blockProtect = sections[matchIndex].protect;
for (size_t prev = matchIndex; prev > 0; ) {
for (size_t prev = matchIndex; prev > 0;) {
--prev;
const auto &section = sections[prev];
if (section.base + section.size != blockStart) {
@ -308,7 +298,7 @@ bool moduleRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info)
}
bool mappedViewRegionForAddress(uintptr_t request, uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) {
std::lock_guard<std::mutex> guard(g_viewInfoMutex);
std::lock_guard guard(g_viewInfoMutex);
if (g_viewInfo.empty()) {
return false;
}
@ -344,7 +334,7 @@ bool mappedViewRegionForAddress(uintptr_t request, uintptr_t pageBase, MEMORY_BA
bool virtualAllocationRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) {
const size_t pageSize = systemPageSize();
std::unique_lock<std::mutex> lock(g_virtualAllocMutex);
std::unique_lock lk(g_virtualAllocMutex);
VirtualAllocation *region = lookupRegion(pageBase);
if (!region) {
uintptr_t regionStart = pageBase;
@ -358,7 +348,7 @@ bool virtualAllocationRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMAT
if (regionEnd <= regionStart) {
regionEnd = regionStart + pageSize;
}
lock.unlock();
lk.unlock();
info.BaseAddress = reinterpret_cast<void *>(regionStart);
info.AllocationBase = nullptr;
info.AllocationProtect = 0;
@ -407,7 +397,7 @@ bool virtualAllocationRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMAT
uintptr_t allocationBase = region->base;
DWORD allocationProtect = region->allocationProtect != 0 ? region->allocationProtect : PAGE_NOACCESS;
DWORD finalProtect = committed ? pageProtect : PAGE_NOACCESS;
lock.unlock();
lk.unlock();
info.BaseAddress = reinterpret_cast<void *>(blockStart);
info.AllocationBase = reinterpret_cast<void *>(allocationBase);
info.AllocationProtect = allocationProtect;
@ -477,50 +467,39 @@ HANDLE WIN_FUNC CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMap
(void)lpFileMappingAttributes;
(void)lpName;
auto *mapping = new MappingObject();
mapping->protect = flProtect;
uint64_t size = (static_cast<uint64_t>(dwMaximumSizeHigh) << 32) | dwMaximumSizeLow;
if (flProtect != PAGE_READONLY && flProtect != PAGE_READWRITE && flProtect != PAGE_WRITECOPY) {
DEBUG_LOG("CreateFileMappingA: unsupported protection 0x%x\n", flProtect);
wibo::lastError = ERROR_INVALID_PARAMETER;
closeMappingIfPossible(mapping);
return nullptr;
}
auto mapping = make_pin<MappingObject>();
mapping->protect = flProtect;
if (hFile == INVALID_HANDLE_VALUE) {
mapping->anonymous = true;
mapping->fd = -1;
if (size == 0) {
wibo::lastError = ERROR_INVALID_PARAMETER;
closeMappingIfPossible(mapping);
return nullptr;
}
mapping->maxSize = size;
} else {
FILE *fp = files::fpFromHandle(hFile);
if (!fp) {
auto file = wibo::handles().getAs<FileObject>(hFile);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
closeMappingIfPossible(mapping);
return nullptr;
}
int originalFd = fileno(fp);
if (originalFd == -1) {
setLastErrorFromErrno();
closeMappingIfPossible(mapping);
return nullptr;
}
int dupFd = fcntl(originalFd, F_DUPFD_CLOEXEC, 0);
int dupFd = fcntl(file->fd, F_DUPFD_CLOEXEC, 0);
if (dupFd == -1) {
setLastErrorFromErrno();
closeMappingIfPossible(mapping);
return nullptr;
}
mapping->fd = dupFd;
if (size == 0) {
int64_t fileSize = getFileSizeFromHandle(hFile);
off64_t fileSize = lseek64(dupFd, 0, SEEK_END);
if (fileSize < 0) {
closeMappingIfPossible(mapping);
return nullptr;
}
size = static_cast<uint64_t>(fileSize);
@ -529,7 +508,7 @@ HANDLE WIN_FUNC CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMap
}
wibo::lastError = ERROR_SUCCESS;
return handles::allocDataHandle({handles::TYPE_MAPPED, mapping, static_cast<size_t>(mapping->maxSize)});
return wibo::handles().alloc(std::move(mapping), 0, 0);
}
HANDLE WIN_FUNC CreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect,
@ -540,7 +519,7 @@ HANDLE WIN_FUNC CreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMap
lpName ? name.c_str() : nullptr);
}
static LPVOID mapViewOfFileInternal(MappingObject *mapping, DWORD dwDesiredAccess, uint64_t offset,
static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredAccess, uint64_t offset,
SIZE_T dwNumberOfBytesToMap, LPVOID baseAddress) {
if (!mapping) {
wibo::lastError = ERROR_INVALID_HANDLE;
@ -660,20 +639,20 @@ static LPVOID mapViewOfFileInternal(MappingObject *mapping, DWORD dwDesiredAcces
if (alignedViewLength == std::numeric_limits<uintptr_t>::max()) {
alignedViewLength = viewLength;
}
DWORD protect = mapping->protect;
ViewInfo view{};
view.viewBase = reinterpret_cast<uintptr_t>(viewPtr);
view.viewLength = static_cast<size_t>(alignedViewLength);
view.allocationBase = reinterpret_cast<uintptr_t>(mapBase);
view.allocationLength = mapLength;
view.owner = mapping;
view.protect = desiredAccessToProtect(dwDesiredAccess, mapping->protect);
view.allocationProtect = mapping->protect;
view.owner = std::move(mapping);
view.protect = desiredAccessToProtect(dwDesiredAccess, protect);
view.allocationProtect = protect;
view.type = MEM_MAPPED;
{
std::lock_guard<std::mutex> guard(g_viewInfoMutex);
g_viewInfo[view.viewBase] = view;
std::lock_guard guard(g_viewInfoMutex);
g_viewInfo.emplace(view.viewBase, std::move(view));
}
mapping->refCount++;
wibo::lastError = ERROR_SUCCESS;
return viewPtr;
}
@ -683,14 +662,13 @@ LPVOID WIN_FUNC MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess,
DEBUG_LOG("MapViewOfFile(%p, 0x%x, %u, %u, %zu)\n", hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
dwFileOffsetLow, dwNumberOfBytesToMap);
handles::Data data = handles::dataFromHandle(hFileMappingObject, false);
if (data.type != handles::TYPE_MAPPED) {
auto mapping = wibo::handles().getAs<MappingObject>(hFileMappingObject);
if (!mapping) {
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
auto *mapping = reinterpret_cast<MappingObject *>(data.ptr);
uint64_t offset = (static_cast<uint64_t>(dwFileOffsetHigh) << 32) | dwFileOffsetLow;
return mapViewOfFileInternal(mapping, dwDesiredAccess, offset, dwNumberOfBytesToMap, nullptr);
return mapViewOfFileInternal(std::move(mapping), dwDesiredAccess, offset, dwNumberOfBytesToMap, nullptr);
}
LPVOID WIN_FUNC MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,
@ -698,49 +676,34 @@ LPVOID WIN_FUNC MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess
DEBUG_LOG("MapViewOfFileEx(%p, 0x%x, %u, %u, %zu, %p)\n", hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
dwFileOffsetLow, dwNumberOfBytesToMap, lpBaseAddress);
handles::Data data = handles::dataFromHandle(hFileMappingObject, false);
if (data.type != handles::TYPE_MAPPED) {
auto mapping = wibo::handles().getAs<MappingObject>(hFileMappingObject);
if (!mapping) {
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
auto *mapping = reinterpret_cast<MappingObject *>(data.ptr);
uint64_t offset = (static_cast<uint64_t>(dwFileOffsetHigh) << 32) | dwFileOffsetLow;
return mapViewOfFileInternal(mapping, dwDesiredAccess, offset, dwNumberOfBytesToMap, lpBaseAddress);
return mapViewOfFileInternal(std::move(mapping), dwDesiredAccess, offset, dwNumberOfBytesToMap, lpBaseAddress);
}
BOOL WIN_FUNC UnmapViewOfFile(LPCVOID lpBaseAddress) {
DEBUG_LOG("UnmapViewOfFile(%p)\n", lpBaseAddress);
std::unique_lock<std::mutex> lock(g_viewInfoMutex);
std::unique_lock lk(g_viewInfoMutex);
auto it = g_viewInfo.find(reinterpret_cast<uintptr_t>(lpBaseAddress));
if (it == g_viewInfo.end()) {
lock.unlock();
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
ViewInfo info = it->second;
void *base = reinterpret_cast<void *>(it->second.allocationBase);
size_t length = it->second.allocationLength;
g_viewInfo.erase(it);
lock.unlock();
if (info.allocationLength != 0) {
munmap(reinterpret_cast<void *>(info.allocationBase), info.allocationLength);
}
if (info.owner && info.owner->refCount > 0) {
info.owner->refCount--;
tryReleaseMapping(info.owner);
lk.unlock();
if (length != 0) {
munmap(base, length);
}
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
bool closeFileMappingHandle(void *mappingPtr) {
auto *mapping = reinterpret_cast<MappingObject *>(mappingPtr);
if (!mapping) {
return false;
}
mapping->closed = true;
tryReleaseMapping(mapping);
return true;
}
LPVOID WIN_FUNC VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) {
DEBUG_LOG("VirtualAlloc(%p, %zu, %u, %u)\n", lpAddress, dwSize, flAllocationType, flProtect);
@ -782,7 +745,7 @@ LPVOID WIN_FUNC VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocation
uintptr_t start = alignDown(request, pageSize);
uintptr_t end = alignUp(request + static_cast<uintptr_t>(dwSize), pageSize);
size_t length = static_cast<size_t>(end - start);
std::unique_lock<std::mutex> lock(g_virtualAllocMutex);
std::unique_lock lk(g_virtualAllocMutex);
VirtualAllocation *region = lookupRegion(start);
if (!region || !rangeWithinRegion(*region, start, length)) {
wibo::lastError = ERROR_INVALID_ADDRESS;
@ -807,7 +770,7 @@ LPVOID WIN_FUNC VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocation
}
const size_t pageSize = systemPageSize();
std::unique_lock<std::mutex> lock(g_virtualAllocMutex);
std::unique_lock lk(g_virtualAllocMutex);
if (reserve) {
uintptr_t base = 0;
@ -950,7 +913,7 @@ BOOL WIN_FUNC VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) {
}
const size_t pageSize = systemPageSize();
std::unique_lock<std::mutex> lock(g_virtualAllocMutex);
std::unique_lock lk(g_virtualAllocMutex);
if (release) {
uintptr_t base = reinterpret_cast<uintptr_t>(lpAddress);
@ -970,7 +933,7 @@ BOOL WIN_FUNC VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) {
}
size_t length = exact->second.size;
g_virtualAllocations.erase(exact);
lock.unlock();
lk.unlock();
if (munmap(lpAddress, length) != 0) {
wibo::lastError = wibo::winErrorFromErrno(errno);
return FALSE;
@ -1038,7 +1001,7 @@ BOOL WIN_FUNC VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect
return FALSE;
}
std::unique_lock<std::mutex> lock(g_virtualAllocMutex);
std::unique_lock lk(g_virtualAllocMutex);
VirtualAllocation *region = lookupRegion(start);
if (!region || !rangeWithinRegion(*region, start, static_cast<size_t>(end - start))) {
wibo::lastError = ERROR_INVALID_ADDRESS;
@ -1072,7 +1035,7 @@ BOOL WIN_FUNC VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect
for (size_t i = 0; i < pageCount; ++i) {
region->pageProtect[firstPage + i] = flNewProtect;
}
lock.unlock();
lk.unlock();
if (lpflOldProtect) {
*lpflOldProtect = previousProtect;

View File

@ -1,5 +1,6 @@
#include "namedpipeapi.h"
#include "common.h"
#include "errors.h"
#include "fileapi.h"
#include "files.h"
@ -58,39 +59,12 @@ BOOL WIN_FUNC CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRI
fcntl(pipeFds[1], F_SETPIPE_SZ, static_cast<int>(nSize));
}
FILE *readStream = fdopen(pipeFds[0], "rb");
if (!readStream) {
int savedErrno = errno ? errno : EINVAL;
close(pipeFds[0]);
close(pipeFds[1]);
errno = savedErrno;
setLastErrorFromErrno();
return FALSE;
}
FILE *writeStream = fdopen(pipeFds[1], "wb");
if (!writeStream) {
int savedErrno = errno ? errno : EINVAL;
fclose(readStream);
close(pipeFds[1]);
errno = savedErrno;
setLastErrorFromErrno();
return FALSE;
}
setvbuf(readStream, nullptr, _IONBF, 0);
setvbuf(writeStream, nullptr, _IONBF, 0);
HANDLE readHandle = files::allocFpHandle(readStream, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, true);
HANDLE writeHandle = files::allocFpHandle(writeStream, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, true);
if (!readHandle || !writeHandle) {
fclose(readStream);
fclose(writeStream);
wibo::lastError = ERROR_NOT_ENOUGH_MEMORY;
return FALSE;
}
*hReadPipe = readHandle;
*hWritePipe = writeHandle;
auto readObj = make_pin<FileObject>(pipeFds[0]);
readObj->shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE;
auto writeObj = make_pin<FileObject>(pipeFds[1]);
writeObj->shareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE;
*hReadPipe = wibo::handles().alloc(std::move(readObj), FILE_GENERIC_READ, 0);
*hWritePipe = wibo::handles().alloc(std::move(writeObj), FILE_GENERIC_WRITE, 0);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}

View File

@ -8,16 +8,20 @@
#include "strutil.h"
#include "timeutil.h"
#include <atomic>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <functional>
#include <limits>
#include <mutex>
#include <pthread.h>
#include <string>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
@ -40,12 +44,6 @@ constexpr DWORD STARTF_USESHOWWINDOW = 0x00000001;
constexpr DWORD STARTF_USESTDHANDLES = 0x00000100;
constexpr WORD SW_SHOWNORMAL = 1;
struct ThreadStartData {
LPTHREAD_START_ROUTINE startRoutine;
void *parameter;
ThreadObject *threadObject;
};
FILETIME fileTimeFromTimeval(const struct timeval &value) {
uint64_t total = 0;
if (value.tv_sec > 0 || value.tv_usec > 0) {
@ -62,19 +60,6 @@ FILETIME fileTimeFromTimespec(const struct timespec &value) {
return fileTimeFromDuration(total);
}
void destroyThreadObject(ThreadObject *obj) {
if (!obj) {
return;
}
if (obj->tib) {
wibo::destroyTib(obj->tib);
obj->tib = nullptr;
}
pthread_cond_destroy(&obj->cond);
pthread_mutex_destroy(&obj->mutex);
delete obj;
}
DWORD_PTR computeSystemAffinityMask() {
long reported = sysconf(_SC_NPROCESSORS_ONLN);
if (reported <= 0) {
@ -103,6 +88,80 @@ template <typename StartupInfo> void populateStartupInfo(StartupInfo *info) {
info->hStdError = files::getStdHandle(STD_ERROR_HANDLE);
}
thread_local ThreadObject *g_currentThreadObject = nullptr;
struct ThreadStartData {
ThreadObject *obj;
LPTHREAD_START_ROUTINE entry;
void *userData;
};
void threadCleanup(void *param) {
ThreadObject *obj = static_cast<ThreadObject *>(param);
if (!obj) {
return;
}
{
std::lock_guard lk(obj->m);
obj->signaled.store(true, std::memory_order_release);
// Exit code set before pthread_exit
}
g_currentThreadObject = nullptr;
wibo::notifyDllThreadDetach();
// TODO: mark mutexes owned by this thread as abandoned
obj->cv.notify_all();
detail::deref(obj);
}
void *threadTrampoline(void *param) {
// We ref'd the ThreadObject when constructing ThreadStartData,
// so we need to deref it when done. (Either normal exit or via pthread_cleanup)
ThreadStartData *dataPtr = static_cast<ThreadStartData *>(param);
ThreadStartData data = *dataPtr;
delete dataPtr;
g_currentThreadObject = data.obj;
// Install TIB
TIB *threadTib = nullptr;
uint16_t previousSegment = 0;
if (wibo::tibSelector) {
asm volatile("mov %%fs, %0" : "=r"(previousSegment));
threadTib = wibo::allocateTib();
if (threadTib) {
wibo::initializeTibStackInfo(threadTib);
if (wibo::installTibForCurrentThread(threadTib)) {
asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory");
} else {
fprintf(stderr, "!!! Failed to install TIB for new thread\n");
wibo::destroyTib(threadTib);
threadTib = nullptr;
}
}
}
// Wait until resumed (if suspended at start)
{
std::unique_lock lk(data.obj->m);
data.obj->tib = threadTib;
data.obj->cv.wait(lk, [&] { return data.obj->suspendCount == 0; });
}
wibo::notifyDllThreadAttach();
DWORD result = data.entry ? data.entry(data.userData) : 0;
{
std::lock_guard lk(data.obj->m);
data.obj->exitCode = result;
}
threadCleanup(data.obj);
return nullptr;
}
inline bool isPseudoCurrentThreadHandle(HANDLE h) {
uintptr_t rawHandle = reinterpret_cast<uintptr_t>(h);
return rawHandle == kernel32::kPseudoCurrentThreadHandleValue;
}
} // namespace
namespace kernel32 {
@ -122,152 +181,6 @@ BOOL WIN_FUNC IsProcessorFeaturePresent(DWORD ProcessorFeature) {
return TRUE;
}
thread_local ThreadObject *g_currentThreadObject = nullptr;
ThreadObject *ensureCurrentThreadObject() {
ThreadObject *obj = g_currentThreadObject;
if (obj) {
return obj;
}
obj = new ThreadObject();
obj->thread = pthread_self();
obj->finished = false;
obj->joined = false;
obj->detached = true;
obj->synthetic = false;
obj->exitCode = STILL_ACTIVE;
obj->refCount = 0;
obj->suspendCount = 0;
pthread_mutex_init(&obj->mutex, nullptr);
pthread_cond_init(&obj->cond, nullptr);
g_currentThreadObject = obj;
return obj;
}
ThreadObject *threadObjectFromHandle(HANDLE hThread) {
auto raw = reinterpret_cast<uintptr_t>(hThread);
if (raw == kPseudoCurrentThreadHandleValue) {
return ensureCurrentThreadObject();
}
if (raw == static_cast<uintptr_t>(-1) || raw == 0) {
return nullptr;
}
auto data = handles::dataFromHandle(hThread, false);
if (data.type != handles::TYPE_THREAD || data.ptr == nullptr) {
return nullptr;
}
return reinterpret_cast<ThreadObject *>(data.ptr);
}
ThreadObject *retainThreadObject(ThreadObject *obj) {
if (!obj) {
return nullptr;
}
pthread_mutex_lock(&obj->mutex);
obj->refCount++;
pthread_mutex_unlock(&obj->mutex);
return obj;
}
void releaseThreadObject(ThreadObject *obj) {
if (!obj) {
return;
}
pthread_t thread = 0;
pthread_mutex_lock(&obj->mutex);
obj->refCount--;
bool shouldDelete = false;
bool shouldDetach = false;
bool finished = obj->finished;
bool joined = obj->joined;
bool detached = obj->detached;
bool synthetic = obj->synthetic;
thread = obj->thread;
if (obj->refCount == 0) {
if (finished || synthetic) {
shouldDelete = true;
} else if (!detached) {
obj->detached = true;
shouldDetach = true;
detached = true;
}
}
pthread_mutex_unlock(&obj->mutex);
if (shouldDetach && !synthetic) {
pthread_detach(thread);
}
if (shouldDelete) {
if (!synthetic) {
if (!joined && !detached) {
pthread_join(thread, nullptr);
}
}
destroyThreadObject(obj);
}
}
static void *threadTrampoline(void *param) {
ThreadStartData *data = static_cast<ThreadStartData *>(param);
ThreadObject *obj = data->threadObject;
LPTHREAD_START_ROUTINE startRoutine = data->startRoutine;
void *userParam = data->parameter;
delete data;
g_currentThreadObject = obj;
pthread_mutex_lock(&obj->mutex);
while (obj->suspendCount > 0) {
pthread_cond_wait(&obj->cond, &obj->mutex);
}
pthread_mutex_unlock(&obj->mutex);
uint16_t previousSegment = 0;
bool tibInstalled = false;
TIB *threadTib = nullptr;
if (wibo::tibSelector) {
asm volatile("mov %%fs, %0" : "=r"(previousSegment));
threadTib = wibo::allocateTib();
if (threadTib) {
wibo::initializeTibStackInfo(threadTib);
if (wibo::installTibForCurrentThread(threadTib)) {
asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory");
tibInstalled = true;
obj->tib = threadTib;
} else {
fprintf(stderr, "!!! Failed to install TIB for new thread\n");
wibo::destroyTib(threadTib);
threadTib = nullptr;
}
}
}
wibo::notifyDllThreadAttach();
DWORD result = startRoutine ? startRoutine(userParam) : 0;
pthread_mutex_lock(&obj->mutex);
obj->finished = true;
obj->exitCode = result;
pthread_cond_broadcast(&obj->cond);
bool shouldDelete = (obj->refCount == 0);
bool detached = obj->detached;
pthread_mutex_unlock(&obj->mutex);
wibo::notifyDllThreadDetach();
if (tibInstalled) {
asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory");
}
if (threadTib) {
obj->tib = nullptr;
wibo::destroyTib(threadTib);
}
g_currentThreadObject = nullptr;
if (shouldDelete) {
assert(detached && "ThreadObject must be detached when refCount reaches zero before completion");
destroyThreadObject(obj);
}
return nullptr;
}
HANDLE WIN_FUNC GetCurrentProcess() {
DEBUG_LOG("GetCurrentProcess() -> %p\n", reinterpret_cast<void *>(static_cast<uintptr_t>(-1)));
return reinterpret_cast<HANDLE>(static_cast<uintptr_t>(-1));
@ -287,8 +200,6 @@ DWORD WIN_FUNC GetCurrentThreadId() {
}
HANDLE WIN_FUNC GetCurrentThread() {
ThreadObject *obj = ensureCurrentThreadObject();
(void)obj;
HANDLE pseudoHandle = reinterpret_cast<HANDLE>(kPseudoCurrentThreadHandleValue);
DEBUG_LOG("GetCurrentThread() -> %p\n", pseudoHandle);
return pseudoHandle;
@ -303,12 +214,12 @@ BOOL WIN_FUNC GetProcessAffinityMask(HANDLE hProcess, PDWORD_PTR lpProcessAffini
}
uintptr_t rawHandle = reinterpret_cast<uintptr_t>(hProcess);
bool isPseudoHandle = rawHandle == static_cast<uintptr_t>(-1);
bool isPseudoHandle = rawHandle == 0 || rawHandle == kPseudoCurrentProcessHandleValue;
if (!isPseudoHandle) {
auto data = handles::dataFromHandle(hProcess, false);
if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) {
auto obj = wibo::handles().getAs<ProcessObject>(hProcess);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
return 0;
}
}
@ -336,12 +247,12 @@ BOOL WIN_FUNC SetProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinit
}
uintptr_t rawHandle = reinterpret_cast<uintptr_t>(hProcess);
bool isPseudoHandle = rawHandle == static_cast<uintptr_t>(-1);
bool isPseudoHandle = rawHandle == 0 || rawHandle == kPseudoCurrentProcessHandleValue;
if (!isPseudoHandle) {
auto data = handles::dataFromHandle(hProcess, false);
if (data.type != handles::TYPE_PROCESS) {
auto obj = wibo::handles().getAs<ProcessObject>(hProcess);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
return 0;
}
}
@ -364,12 +275,9 @@ DWORD_PTR WIN_FUNC SetThreadAffinityMask(HANDLE hThread, DWORD_PTR dwThreadAffin
return 0;
}
uintptr_t rawThreadHandle = reinterpret_cast<uintptr_t>(hThread);
bool isPseudoHandle = rawThreadHandle == kPseudoCurrentThreadHandleValue || rawThreadHandle == 0 ||
rawThreadHandle == static_cast<uintptr_t>(-1);
if (!isPseudoHandle) {
auto data = handles::dataFromHandle(hThread, false);
if (data.type != handles::TYPE_THREAD) {
if (!isPseudoCurrentThreadHandle(hThread)) {
auto obj = wibo::handles().getAs<ThreadObject>(hThread);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return 0;
}
@ -391,23 +299,27 @@ DWORD_PTR WIN_FUNC SetThreadAffinityMask(HANDLE hThread, DWORD_PTR dwThreadAffin
void WIN_FUNC ExitProcess(UINT uExitCode) {
DEBUG_LOG("ExitProcess(%u)\n", uExitCode);
std::exit(static_cast<int>(uExitCode));
exit(static_cast<int>(uExitCode));
}
BOOL WIN_FUNC TerminateProcess(HANDLE hProcess, UINT uExitCode) {
DEBUG_LOG("TerminateProcess(%p, %u)\n", hProcess, uExitCode);
if (hProcess == reinterpret_cast<HANDLE>(static_cast<uintptr_t>(-1))) {
ExitProcess(uExitCode);
exit(static_cast<int>(uExitCode));
}
auto data = handles::dataFromHandle(hProcess, false);
if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) {
auto process = wibo::handles().getAs<ProcessObject>(hProcess);
if (!process) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
auto *process = reinterpret_cast<processes::Process *>(data.ptr);
if (kill(process->pid, SIGKILL) != 0) {
if (process->signaled.load(std::memory_order_acquire)) {
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
std::lock_guard lk(process->m);
if (syscall(SYS_pidfd_send_signal, process->pidfd, SIGKILL, nullptr, 0) != 0) {
int err = errno;
DEBUG_LOG("TerminateProcess: kill(%d) failed: %s\n", process->pid, strerror(err));
DEBUG_LOG("TerminateProcess: pidfd_send_signal(%d) failed: %s\n", process->pidfd, strerror(err));
switch (err) {
case ESRCH:
case EPERM:
@ -419,9 +331,8 @@ BOOL WIN_FUNC TerminateProcess(HANDLE hProcess, UINT uExitCode) {
}
return FALSE;
}
process->forcedExitCode = uExitCode;
process->terminationRequested = true;
process->exitCode = uExitCode;
process->forcedExitCode = true;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
@ -432,12 +343,17 @@ BOOL WIN_FUNC GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
auto *process = processes::processFromHandle(hProcess, false);
auto process = wibo::handles().getAs<ProcessObject>(hProcess);
if (!process) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
*lpExitCode = process->exitCode;
DWORD exitCode = STILL_ACTIVE;
if (process->signaled.load(std::memory_order_acquire)) {
std::lock_guard lk(process->m);
exitCode = process->exitCode;
}
*lpExitCode = exitCode;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
@ -491,20 +407,27 @@ BOOL WIN_FUNC TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue) {
DWORD WIN_FUNC ResumeThread(HANDLE hThread) {
DEBUG_LOG("ResumeThread(%p)\n", hThread);
ThreadObject *obj = threadObjectFromHandle(hThread);
// TODO: behavior with current thread handle?
auto obj = wibo::handles().getAs<ThreadObject>(hThread);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return static_cast<DWORD>(-1);
}
pthread_mutex_lock(&obj->mutex);
DWORD previous = obj->suspendCount;
if (obj->suspendCount > 0) {
obj->suspendCount--;
if (obj->suspendCount == 0) {
pthread_cond_broadcast(&obj->cond);
DWORD previous = 0;
bool notify = false;
{
std::lock_guard lk(obj->m);
previous = obj->suspendCount;
if (obj->suspendCount > 0) {
obj->suspendCount--;
if (obj->suspendCount == 0) {
notify = true;
}
}
}
pthread_mutex_unlock(&obj->mutex);
if (notify) {
obj->cv.notify_all();
}
wibo::lastError = ERROR_SUCCESS;
return previous;
}
@ -530,48 +453,30 @@ HANDLE WIN_FUNC CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dw
wibo::lastError = ERROR_NOT_SUPPORTED;
return nullptr;
}
bool startSuspended = (dwCreationFlags & CREATE_SUSPENDED) != 0;
ThreadObject *obj = new ThreadObject();
pthread_mutex_init(&obj->mutex, nullptr);
pthread_cond_init(&obj->cond, nullptr);
obj->finished = false;
obj->joined = false;
obj->detached = false;
obj->exitCode = 0;
obj->refCount = 1;
obj->suspendCount = startSuspended ? 1u : 0u;
obj->synthetic = false;
ThreadStartData *startData = new ThreadStartData{lpStartAddress, lpParameter, obj};
Pin<ThreadObject> obj = make_pin<ThreadObject>(0); // tid set during pthread_create
if ((dwCreationFlags & CREATE_SUSPENDED) != 0) {
obj->suspendCount = 1;
}
detail::ref(obj.get()); // Increment ref for the new thread to adopt
ThreadStartData *startData = new ThreadStartData{obj.get(), lpStartAddress, lpParameter};
pthread_attr_t attr;
pthread_attr_t *attrPtr = nullptr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (dwStackSize != 0) {
pthread_attr_init(&attr);
size_t stackSize = dwStackSize;
#ifdef PTHREAD_STACK_MIN
if (stackSize < static_cast<size_t>(PTHREAD_STACK_MIN)) {
stackSize = PTHREAD_STACK_MIN;
}
#endif
if (pthread_attr_setstacksize(&attr, stackSize) == 0) {
attrPtr = &attr;
} else {
pthread_attr_destroy(&attr);
}
// TODO: should we just ignore this?
pthread_attr_setstacksize(&attr, std::max(dwStackSize, static_cast<SIZE_T>(PTHREAD_STACK_MIN)));
}
int rc = pthread_create(&obj->thread, attrPtr, threadTrampoline, startData);
if (attrPtr) {
pthread_attr_destroy(attrPtr);
}
int rc = pthread_create(&obj->thread, &attr, threadTrampoline, startData);
pthread_attr_destroy(&attr);
if (rc != 0) {
// Clean up
delete startData;
destroyThreadObject(obj);
errno = rc;
setLastErrorFromErrno();
return nullptr;
detail::deref(obj.get());
wibo::lastError = wibo::winErrorFromErrno(rc);
return INVALID_HANDLE_VALUE;
}
if (lpThreadId) {
@ -580,46 +485,19 @@ HANDLE WIN_FUNC CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dw
}
wibo::lastError = ERROR_SUCCESS;
return handles::allocDataHandle({handles::TYPE_THREAD, obj, 0});
return wibo::handles().alloc(std::move(obj), 0 /* TODO */, 0);
}
void WIN_FUNC ExitThread(DWORD dwExitCode) {
[[noreturn]] void WIN_FUNC ExitThread(DWORD dwExitCode) {
DEBUG_LOG("ExitThread(%u)\n", dwExitCode);
ThreadObject *obj = g_currentThreadObject;
TIB *threadTib = obj ? obj->tib : nullptr;
uint16_t previousSegment = 0;
bool tibInstalled = false;
if (wibo::tibSelector) {
asm volatile("mov %%fs, %0" : "=r"(previousSegment));
asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory");
tibInstalled = true;
}
bool shouldDelete = false;
bool detached = false;
if (obj) {
pthread_mutex_lock(&obj->mutex);
obj->finished = true;
{
std::lock_guard lk(obj->m);
obj->exitCode = dwExitCode;
pthread_cond_broadcast(&obj->cond);
shouldDelete = (obj->refCount == 0);
detached = obj->detached;
pthread_mutex_unlock(&obj->mutex);
}
wibo::notifyDllThreadDetach();
if (tibInstalled) {
asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory");
}
if (obj && threadTib) {
obj->tib = nullptr;
wibo::destroyTib(threadTib);
}
if (obj) {
g_currentThreadObject = nullptr;
if (shouldDelete) {
assert(detached && "ThreadObject must be detached when refCount reaches zero before completion");
destroyThreadObject(obj);
}
}
// Can't use pthread_cleanup_push/pop because it can't unwind the Windows stack
// So call the cleanup function directly before pthread_exit
threadCleanup(obj);
pthread_exit(nullptr);
}
@ -629,15 +507,18 @@ BOOL WIN_FUNC GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
ThreadObject *obj = threadObjectFromHandle(hThread);
if (isPseudoCurrentThreadHandle(hThread)) {
*lpExitCode = STILL_ACTIVE;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
auto obj = wibo::handles().getAs<ThreadObject>(hThread);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
pthread_mutex_lock(&obj->mutex);
DWORD code = obj->finished ? obj->exitCode : STILL_ACTIVE;
pthread_mutex_unlock(&obj->mutex);
*lpExitCode = code;
std::lock_guard lk(obj->m);
*lpExitCode = obj->exitCode;
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
@ -673,7 +554,7 @@ BOOL WIN_FUNC GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME
return FALSE;
}
bool isPseudoCurrentThread = reinterpret_cast<uintptr_t>(hThread) == kPseudoCurrentThreadHandleValue ||
bool isPseudoCurrentThread = reinterpret_cast<uintptr_t>(hThread) == kernel32::kPseudoCurrentThreadHandleValue ||
hThread == nullptr || hThread == reinterpret_cast<HANDLE>(static_cast<uintptr_t>(-1));
if (!isPseudoCurrentThread) {
DEBUG_LOG("GetThreadTimes: unsupported handle %p\n", hThread);
@ -689,7 +570,7 @@ BOOL WIN_FUNC GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME
lpExitTime->dwHighDateTime = 0;
}
struct rusage usage;
struct rusage usage{};
if (getrusage(RUSAGE_THREAD, &usage) == 0) {
*lpKernelTime = fileTimeFromTimeval(usage.ru_stime);
*lpUserTime = fileTimeFromTimeval(usage.ru_utime);
@ -697,7 +578,7 @@ BOOL WIN_FUNC GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME
return TRUE;
}
struct timespec cpuTime;
struct timespec cpuTime{};
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpuTime) == 0) {
*lpKernelTime = fileTimeFromDuration(0);
*lpUserTime = fileTimeFromTimespec(cpuTime);
@ -705,7 +586,7 @@ BOOL WIN_FUNC GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME
return TRUE;
}
setLastErrorFromErrno();
kernel32::setLastErrorFromErrno();
*lpKernelTime = fileTimeFromDuration(0);
*lpUserTime = fileTimeFromDuration(0);
return FALSE;
@ -726,7 +607,7 @@ BOOL WIN_FUNC CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSE
if (lpApplicationName) {
application = lpApplicationName;
} else {
std::vector<std::string> arguments = processes::splitCommandLine(commandLine.c_str());
std::vector<std::string> arguments = wibo::splitCommandLine(commandLine.c_str());
if (arguments.empty()) {
wibo::lastError = ERROR_FILE_NOT_FOUND;
return FALSE;
@ -734,21 +615,22 @@ BOOL WIN_FUNC CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSE
application = arguments.front();
}
auto resolved = processes::resolveExecutable(application, useSearchPath);
auto resolved = wibo::resolveExecutable(application, useSearchPath);
if (!resolved) {
wibo::lastError = ERROR_FILE_NOT_FOUND;
return FALSE;
}
pid_t pid = -1;
int spawnResult = processes::spawnWithCommandLine(*resolved, commandLine, &pid);
Pin<ProcessObject> obj;
int spawnResult = wibo::spawnWithCommandLine(*resolved, commandLine, obj);
if (spawnResult != 0) {
wibo::lastError = (spawnResult == ENOENT) ? ERROR_FILE_NOT_FOUND : ERROR_ACCESS_DENIED;
return FALSE;
}
pid_t pid = obj->pid;
if (lpProcessInformation) {
lpProcessInformation->hProcess = processes::allocProcessHandle(pid);
lpProcessInformation->hProcess = wibo::handles().alloc(std::move(obj), 0 /* TODO: access */, 0);
lpProcessInformation->hThread = nullptr;
lpProcessInformation->dwProcessId = static_cast<DWORD>(pid);
lpProcessInformation->dwThreadId = 0;

View File

@ -88,7 +88,7 @@ BOOL WIN_FUNC TlsSetValue(DWORD dwTlsIndex, LPVOID lpTlsValue);
HANDLE WIN_FUNC CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags,
LPDWORD lpThreadId);
void WIN_FUNC ExitThread(DWORD dwExitCode);
[[noreturn]] void WIN_FUNC ExitThread(DWORD dwExitCode);
BOOL WIN_FUNC GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);
BOOL WIN_FUNC SetThreadPriority(HANDLE hThread, int nPriority);
int WIN_FUNC GetThreadPriority(HANDLE hThread);

View File

@ -1,24 +1,23 @@
#include "synchapi.h"
#include "common.h"
#include "errors.h"
#include "handles.h"
#include "internal.h"
#include "processes.h"
#include "strutil.h"
#include <cerrno>
#include <chrono>
#include <cstring>
#include <mutex>
#include <pthread.h>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
#include <unordered_map>
#include <vector>
namespace {
std::u16string makeMutexName(LPCWSTR name) {
std::u16string makeU16String(LPCWSTR name) {
if (!name) {
return {};
}
@ -43,155 +42,34 @@ void WIN_FUNC Sleep(DWORD dwMilliseconds) {
usleep(static_cast<useconds_t>(dwMilliseconds) * 1000);
}
namespace {
std::mutex mutexRegistryLock;
std::unordered_map<std::u16string, MutexObject *> namedMutexes;
std::mutex eventRegistryLock;
std::unordered_map<std::u16string, EventObject *> namedEvents;
std::mutex semaphoreRegistryLock;
std::unordered_map<std::u16string, SemaphoreObject *> namedSemaphores;
EventObject *eventObjectFromHandle(HANDLE hEvent) {
auto data = handles::dataFromHandle(hEvent, false);
if (data.type != handles::TYPE_EVENT || data.ptr == nullptr) {
return nullptr;
}
return reinterpret_cast<EventObject *>(data.ptr);
}
SemaphoreObject *semaphoreObjectFromHandle(HANDLE hSemaphore) {
auto data = handles::dataFromHandle(hSemaphore, false);
if (data.type != handles::TYPE_SEMAPHORE || data.ptr == nullptr) {
return nullptr;
}
return reinterpret_cast<SemaphoreObject *>(data.ptr);
}
MutexObject *mutexObjectFromHandle(HANDLE hMutex) {
auto data = handles::dataFromHandle(hMutex, false);
if (data.type != handles::TYPE_MUTEX || data.ptr == nullptr) {
return nullptr;
}
return reinterpret_cast<MutexObject *>(data.ptr);
}
bool setEventSignaledState(HANDLE hEvent, bool signaled) {
EventObject *obj = eventObjectFromHandle(hEvent);
if (!obj) {
return false;
}
pthread_mutex_lock(&obj->mutex);
obj->signaled = signaled;
if (signaled) {
if (obj->manualReset) {
pthread_cond_broadcast(&obj->cond);
} else {
pthread_cond_signal(&obj->cond);
}
}
pthread_mutex_unlock(&obj->mutex);
return true;
}
} // namespace
void releaseMutexObject(MutexObject *obj) {
if (!obj) {
return;
}
std::lock_guard<std::mutex> lock(mutexRegistryLock);
obj->refCount--;
if (obj->refCount == 0) {
if (!obj->name.empty()) {
namedMutexes.erase(obj->name);
}
pthread_mutex_destroy(&obj->mutex);
delete obj;
}
}
void releaseEventObject(EventObject *obj) {
if (!obj) {
return;
}
std::lock_guard<std::mutex> lock(eventRegistryLock);
obj->refCount--;
if (obj->refCount == 0) {
if (!obj->name.empty()) {
namedEvents.erase(obj->name);
}
pthread_cond_destroy(&obj->cond);
pthread_mutex_destroy(&obj->mutex);
delete obj;
}
}
void releaseSemaphoreObject(SemaphoreObject *obj) {
if (!obj) {
return;
}
std::lock_guard<std::mutex> lock(semaphoreRegistryLock);
obj->refCount--;
if (obj->refCount == 0) {
if (!obj->name.empty()) {
namedSemaphores.erase(obj->name);
}
pthread_cond_destroy(&obj->cond);
pthread_mutex_destroy(&obj->mutex);
delete obj;
}
}
HANDLE WIN_FUNC CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName) {
std::string nameLog;
if (lpName) {
nameLog = wideStringToString(reinterpret_cast<const uint16_t *>(lpName));
} else {
nameLog = "<unnamed>";
DEBUG_LOG("CreateMutexW(%p, %d, %ls)\n", lpMutexAttributes, static_cast<int>(bInitialOwner),
wideStringToString(lpName).c_str());
std::u16string name = makeU16String(lpName);
const uint32_t grantedAccess = MUTEX_ALL_ACCESS;
uint32_t handleFlags = 0;
if (lpMutexAttributes && lpMutexAttributes->bInheritHandle) {
handleFlags |= HANDLE_FLAG_INHERIT;
}
DEBUG_LOG("CreateMutexW(%p, %d, %s)\n", lpMutexAttributes, bInitialOwner, nameLog.c_str());
(void)lpMutexAttributes;
std::u16string name = makeMutexName(lpName);
MutexObject *obj = nullptr;
bool alreadyExists = false;
{
std::lock_guard<std::mutex> lock(mutexRegistryLock);
if (!name.empty()) {
auto it = namedMutexes.find(name);
if (it != namedMutexes.end()) {
obj = it->second;
obj->refCount++;
alreadyExists = true;
}
}
if (!obj) {
obj = new MutexObject();
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&obj->mutex, &attr);
pthread_mutexattr_destroy(&attr);
obj->name = name;
if (!name.empty()) {
namedMutexes[name] = obj;
}
auto [mu, created] = wibo::g_namespace.getOrCreate(name, [&]() {
auto *mu = new MutexObject();
if (bInitialOwner) {
std::lock_guard lk(mu->m);
mu->owner = pthread_self();
mu->ownerValid = true;
mu->recursionCount = 1;
mu->signaled.store(false, std::memory_order_release);
}
return mu;
});
if (!mu) {
// Name exists but isn't a mutex
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
if (!alreadyExists && bInitialOwner) {
pthread_mutex_lock(&obj->mutex);
obj->owner = pthread_self();
obj->ownerValid = true;
obj->recursionCount = 1;
}
HANDLE handle = handles::allocDataHandle({handles::TYPE_MUTEX, obj, 0});
wibo::lastError = alreadyExists ? ERROR_ALREADY_EXISTS : ERROR_SUCCESS;
return handle;
HANDLE h = wibo::handles().alloc(std::move(mu), grantedAccess, handleFlags);
wibo::lastError = created ? ERROR_SUCCESS : ERROR_ALREADY_EXISTS;
return h;
}
HANDLE WIN_FUNC CreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName) {
@ -204,67 +82,58 @@ HANDLE WIN_FUNC CreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInit
BOOL WIN_FUNC ReleaseMutex(HANDLE hMutex) {
DEBUG_LOG("ReleaseMutex(%p)\n", hMutex);
MutexObject *obj = mutexObjectFromHandle(hMutex);
if (!obj) {
auto mu = wibo::handles().getAs<MutexObject>(hMutex);
if (!mu) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
pthread_t self = pthread_self();
pthread_mutex_lock(&obj->mutex);
if (!obj->ownerValid || !pthread_equal(obj->owner, self) || obj->recursionCount == 0) {
pthread_mutex_unlock(&obj->mutex);
wibo::lastError = ERROR_NOT_OWNER;
return FALSE;
const pthread_t self = pthread_self();
bool notify = false;
{
std::lock_guard lk(mu->m);
if (!mu->ownerValid || !pthread_equal(mu->owner, self) || mu->recursionCount == 0) {
wibo::lastError = ERROR_NOT_OWNER;
return FALSE;
}
if (--mu->recursionCount == 0) {
mu->ownerValid = false;
mu->signaled.store(true, std::memory_order_release);
notify = true;
}
}
obj->recursionCount--;
if (obj->recursionCount == 0) {
obj->ownerValid = false;
if (notify) {
mu->cv.notify_one();
}
pthread_mutex_unlock(&obj->mutex);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
HANDLE WIN_FUNC CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,
LPCWSTR lpName) {
std::string nameLog;
if (lpName) {
nameLog = wideStringToString(reinterpret_cast<const uint16_t *>(lpName));
} else {
nameLog = "<unnamed>";
DEBUG_LOG("CreateEventW(%p, %d, %d, %ls)\n", lpEventAttributes, static_cast<int>(bManualReset),
static_cast<int>(bInitialState), wideStringToString(lpName).c_str());
std::u16string name = makeU16String(lpName);
const uint32_t grantedAccess = EVENT_ALL_ACCESS;
uint32_t handleFlags = 0;
if (lpEventAttributes && lpEventAttributes->bInheritHandle) {
handleFlags |= HANDLE_FLAG_INHERIT;
}
DEBUG_LOG("CreateEventW(%p, %d, %d, %s)\n", lpEventAttributes, bManualReset, bInitialState, nameLog.c_str());
(void)lpEventAttributes;
std::u16string name = makeMutexName(lpName);
EventObject *obj = nullptr;
bool alreadyExists = false;
{
std::lock_guard<std::mutex> lock(eventRegistryLock);
if (!name.empty()) {
auto it = namedEvents.find(name);
if (it != namedEvents.end()) {
obj = it->second;
obj->refCount++;
alreadyExists = true;
}
}
if (!obj) {
obj = new EventObject();
pthread_mutex_init(&obj->mutex, nullptr);
pthread_cond_init(&obj->cond, nullptr);
obj->manualReset = bManualReset;
obj->signaled = bInitialState;
obj->name = name;
if (!name.empty()) {
namedEvents[name] = obj;
}
auto [ev, alreadyExists] = wibo::g_namespace.getOrCreate(name, [&]() {
auto e = new EventObject(!!bManualReset);
if (bInitialState) {
std::lock_guard lk(e->m);
e->signaled.store(true, std::memory_order_relaxed);
}
return e;
});
if (!ev) {
// Name exists but isn't an event
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
HANDLE handle = handles::allocDataHandle({handles::TYPE_EVENT, obj, 0});
HANDLE h = wibo::handles().alloc(std::move(ev), grantedAccess, handleFlags);
wibo::lastError = alreadyExists ? ERROR_ALREADY_EXISTS : ERROR_SUCCESS;
return handle;
return h;
}
HANDLE WIN_FUNC CreateEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState,
@ -278,43 +147,28 @@ HANDLE WIN_FUNC CreateEventA(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManu
HANDLE WIN_FUNC CreateSemaphoreW(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount,
LPCWSTR lpName) {
DEBUG_LOG("CreateSemaphoreW(%p, %ld, %ld, %ls)\n", lpSemaphoreAttributes, static_cast<long>(lInitialCount),
static_cast<long>(lMaximumCount), lpName ? reinterpret_cast<const wchar_t *>(lpName) : L"<null>");
(void)lpSemaphoreAttributes;
std::u16string name = makeMutexName(lpName);
SemaphoreObject *obj = nullptr;
bool alreadyExists = false;
{
std::lock_guard<std::mutex> lock(semaphoreRegistryLock);
if (!name.empty()) {
auto it = namedSemaphores.find(name);
if (it != namedSemaphores.end()) {
obj = it->second;
obj->refCount++;
alreadyExists = true;
}
}
if (!obj) {
if (lMaximumCount <= 0 || lInitialCount < 0 || lInitialCount > lMaximumCount) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return nullptr;
}
obj = new SemaphoreObject();
pthread_mutex_init(&obj->mutex, nullptr);
pthread_cond_init(&obj->cond, nullptr);
obj->count = lInitialCount;
obj->maxCount = lMaximumCount;
obj->name = name;
if (!name.empty()) {
namedSemaphores[name] = obj;
}
}
DEBUG_LOG("CreateSemaphoreW(%p, %ld, %ld, %ls)\n", lpSemaphoreAttributes, lInitialCount, lMaximumCount,
wideStringToString(lpName).c_str());
auto name = makeU16String(lpName);
const uint32_t granted = SEMAPHORE_ALL_ACCESS;
uint32_t hflags = 0;
if (lpSemaphoreAttributes && lpSemaphoreAttributes->bInheritHandle) {
hflags |= HANDLE_FLAG_INHERIT;
}
HANDLE handle = handles::allocDataHandle({handles::TYPE_SEMAPHORE, obj, 0});
wibo::lastError = alreadyExists ? ERROR_ALREADY_EXISTS : ERROR_SUCCESS;
return handle;
auto [sem, created] = wibo::g_namespace.getOrCreate(name, [&]() -> SemaphoreObject * {
if (lMaximumCount <= 0 || lInitialCount < 0 || lInitialCount > lMaximumCount) {
return nullptr;
}
return new SemaphoreObject(lInitialCount, lMaximumCount);
});
if (!sem) {
// Name exists but isn't an event
wibo::lastError = ERROR_INVALID_HANDLE;
return nullptr;
}
HANDLE h = wibo::handles().alloc(std::move(sem), granted, hflags);
wibo::lastError = created ? ERROR_SUCCESS : ERROR_ALREADY_EXISTS;
return h;
}
HANDLE WIN_FUNC CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount,
@ -327,169 +181,165 @@ HANDLE WIN_FUNC CreateSemaphoreA(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LO
}
BOOL WIN_FUNC ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, PLONG lpPreviousCount) {
DEBUG_LOG("ReleaseSemaphore(%p, %ld, %p)\n", hSemaphore, static_cast<long>(lReleaseCount), lpPreviousCount);
DEBUG_LOG("ReleaseSemaphore(%p, %ld, %p)\n", hSemaphore, lReleaseCount, lpPreviousCount);
if (lReleaseCount <= 0) {
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
SemaphoreObject *obj = semaphoreObjectFromHandle(hSemaphore);
if (!obj) {
auto sem = wibo::handles().getAs<SemaphoreObject>(hSemaphore);
if (!sem) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
pthread_mutex_lock(&obj->mutex);
LONG prev = 0;
{
std::lock_guard lk(sem->m);
if (lpPreviousCount) {
prev = sem->count;
}
if (sem->count > sem->maxCount - lReleaseCount) {
wibo::lastError = ERROR_TOO_MANY_POSTS;
return FALSE;
}
sem->count += lReleaseCount;
sem->signaled.store(sem->count > 0, std::memory_order_release);
}
for (LONG i = 0; i < lReleaseCount; ++i) {
sem->cv.notify_one();
}
if (lpPreviousCount) {
*lpPreviousCount = obj->count;
*lpPreviousCount = prev;
}
if (lReleaseCount > obj->maxCount - obj->count) {
pthread_mutex_unlock(&obj->mutex);
wibo::lastError = ERROR_INVALID_PARAMETER;
return FALSE;
}
obj->count += lReleaseCount;
pthread_mutex_unlock(&obj->mutex);
pthread_cond_broadcast(&obj->cond);
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
BOOL WIN_FUNC SetEvent(HANDLE hEvent) {
DEBUG_LOG("SetEvent(%p)\n", hEvent);
if (!setEventSignaledState(hEvent, true)) {
auto ev = wibo::handles().getAs<EventObject>(hEvent);
if (!ev) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
ev->set();
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
BOOL WIN_FUNC ResetEvent(HANDLE hEvent) {
DEBUG_LOG("ResetEvent(%p)\n", hEvent);
if (!setEventSignaledState(hEvent, false)) {
auto ev = wibo::handles().getAs<EventObject>(hEvent);
if (!ev) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
ev->reset();
wibo::lastError = ERROR_SUCCESS;
return TRUE;
}
DWORD WIN_FUNC WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) {
DEBUG_LOG("WaitForSingleObject(%p, %u)\n", hHandle, dwMilliseconds);
handles::Data data = handles::dataFromHandle(hHandle, false);
switch (data.type) {
case handles::TYPE_PROCESS: {
if (dwMilliseconds != INFINITE) {
DEBUG_LOG("WaitForSingleObject: timeout for process not supported\n");
wibo::lastError = ERROR_NOT_SUPPORTED;
return WAIT_FAILED;
}
auto *process = reinterpret_cast<processes::Process *>(data.ptr);
int status = 0;
for (;;) {
if (waitpid(process->pid, &status, 0) == -1) {
if (errno == EINTR) {
continue;
}
if (errno == ECHILD && process->terminationRequested) {
process->exitCode = process->forcedExitCode;
break;
}
DEBUG_LOG("WaitForSingleObject: waitpid(%d) failed: %s\n", process->pid, strerror(errno));
wibo::lastError = ERROR_INVALID_HANDLE;
return WAIT_FAILED;
}
break;
}
if (process->terminationRequested) {
process->exitCode = process->forcedExitCode;
} else if (WIFEXITED(status)) {
process->exitCode = static_cast<DWORD>(WEXITSTATUS(status));
HandleMeta meta{};
Pin<> obj = wibo::handles().get(hHandle, &meta);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
DEBUG_LOG("-> ERROR_INVALID_HANDLE\n");
return WAIT_FAILED;
}
#ifdef CHECK_ACCESS
if ((meta.grantedAccess & SYNCHRONIZE) == 0) {
wibo::lastError = ERROR_ACCESS_DENIED;
DEBUG_LOG("!!! DENIED: 0x%x\n", meta.grantedAccess);
return WAIT_FAILED;
}
#endif
auto doWait = [&](auto &lk, auto &cv, auto pred) -> bool {
if (dwMilliseconds == INFINITE) {
cv.wait(lk, pred);
return true;
} else {
DEBUG_LOG("WaitForSingleObject: child process exited abnormally - returning exit code 1\n");
process->exitCode = 1;
return cv.wait_for(lk, std::chrono::milliseconds(dwMilliseconds), pred);
}
};
switch (obj->type) {
case ObjectType::Event: {
auto ev = std::move(obj).downcast<EventObject>();
std::unique_lock lk(ev->m);
bool ok = doWait(lk, ev->cv, [&] { return ev->signaled.load(std::memory_order_acquire); });
if (!ok) {
return WAIT_TIMEOUT;
}
if (!ev->manualReset) {
ev->signaled.store(false, std::memory_order_release);
}
process->terminationRequested = false;
wibo::lastError = ERROR_SUCCESS;
return WAIT_OBJECT_0;
}
case handles::TYPE_EVENT: {
EventObject *obj = reinterpret_cast<EventObject *>(data.ptr);
if (dwMilliseconds != INFINITE) {
DEBUG_LOG("WaitForSingleObject: timeout for event not supported\n");
wibo::lastError = ERROR_NOT_SUPPORTED;
return WAIT_FAILED;
case ObjectType::Semaphore: {
auto sem = std::move(obj).downcast<SemaphoreObject>();
std::unique_lock lk(sem->m);
bool ok = doWait(lk, sem->cv, [&] { return sem->count > 0; });
if (!ok) {
return WAIT_TIMEOUT;
}
pthread_mutex_lock(&obj->mutex);
while (!obj->signaled) {
pthread_cond_wait(&obj->cond, &obj->mutex);
--sem->count;
if (sem->count == 0) {
sem->signaled.store(false, std::memory_order_release);
}
if (!obj->manualReset) {
obj->signaled = false;
}
pthread_mutex_unlock(&obj->mutex);
wibo::lastError = ERROR_SUCCESS;
return WAIT_OBJECT_0;
}
case handles::TYPE_THREAD: {
ThreadObject *obj = reinterpret_cast<ThreadObject *>(data.ptr);
if (dwMilliseconds != INFINITE) {
DEBUG_LOG("WaitForSingleObject: timeout for thread not supported\n");
wibo::lastError = ERROR_NOT_SUPPORTED;
return WAIT_FAILED;
}
pthread_mutex_lock(&obj->mutex);
while (!obj->finished) {
pthread_cond_wait(&obj->cond, &obj->mutex);
}
bool needJoin = !obj->joined && !obj->detached;
pthread_t thread = obj->thread;
if (needJoin) {
obj->joined = true;
}
pthread_mutex_unlock(&obj->mutex);
if (needJoin) {
pthread_join(thread, nullptr);
}
wibo::lastError = ERROR_SUCCESS;
return WAIT_OBJECT_0;
}
case handles::TYPE_SEMAPHORE: {
SemaphoreObject *obj = reinterpret_cast<SemaphoreObject *>(data.ptr);
if (dwMilliseconds != INFINITE) {
DEBUG_LOG("WaitForSingleObject: timeout for semaphore not supported\n");
wibo::lastError = ERROR_NOT_SUPPORTED;
return WAIT_FAILED;
}
pthread_mutex_lock(&obj->mutex);
while (obj->count == 0) {
pthread_cond_wait(&obj->cond, &obj->mutex);
}
obj->count--;
pthread_mutex_unlock(&obj->mutex);
wibo::lastError = ERROR_SUCCESS;
return WAIT_OBJECT_0;
}
case handles::TYPE_MUTEX: {
MutexObject *obj = reinterpret_cast<MutexObject *>(data.ptr);
if (dwMilliseconds != INFINITE) {
DEBUG_LOG("WaitForSingleObject: timeout for mutex not supported\n");
wibo::lastError = ERROR_NOT_SUPPORTED;
return WAIT_FAILED;
}
pthread_mutex_lock(&obj->mutex);
case ObjectType::Mutex: {
auto mu = std::move(obj).downcast<MutexObject>();
pthread_t self = pthread_self();
if (obj->ownerValid && pthread_equal(obj->owner, self)) {
obj->recursionCount++;
} else {
obj->owner = self;
obj->ownerValid = true;
obj->recursionCount = 1;
std::unique_lock lk(mu->m);
// Recursive acquisition
if (mu->ownerValid && pthread_equal(mu->owner, self)) {
++mu->recursionCount;
return WAIT_OBJECT_0;
}
wibo::lastError = ERROR_SUCCESS;
return WAIT_OBJECT_0;
bool ok = doWait(lk, mu->cv, [&] { return !mu->ownerValid || mu->abandoned; });
if (!ok) {
return WAIT_TIMEOUT;
}
DWORD ret = WAIT_OBJECT_0;
if (std::exchange(mu->abandoned, false)) {
// Acquire and report abandoned
ret = WAIT_ABANDONED;
}
mu->owner = self;
mu->ownerValid = true;
mu->recursionCount = 1;
mu->signaled.store(false, std::memory_order_release);
return ret;
}
case ObjectType::Thread: {
auto th = std::move(obj).downcast<ThreadObject>();
pthread_t self = pthread_self();
std::unique_lock lk(th->m);
if (pthread_equal(th->thread, self)) {
// Cannot wait on self
wibo::lastError = ERROR_INVALID_HANDLE;
return WAIT_FAILED;
}
bool ok = doWait(lk, th->cv, [&] { return th->signaled.load(std::memory_order_acquire); });
return ok ? WAIT_OBJECT_0 : WAIT_TIMEOUT;
}
case ObjectType::Process: {
auto po = std::move(obj).downcast<ProcessObject>();
std::unique_lock lk(po->m);
if (po->pidfd == -1) {
// Cannot wait on self
wibo::lastError = ERROR_INVALID_HANDLE;
return WAIT_FAILED;
}
bool ok = doWait(lk, po->cv, [&] { return po->signaled.load(std::memory_order_acquire); });
return ok ? WAIT_OBJECT_0 : WAIT_TIMEOUT;
}
default:
DEBUG_LOG("WaitForSingleObject: unsupported handle type %d\n", data.type);
wibo::lastError = ERROR_INVALID_HANDLE;
return WAIT_FAILED;
}
@ -594,18 +444,4 @@ BOOLEAN WIN_FUNC TryAcquireSRWLockExclusive(PSRWLOCK SRWLock) {
return TRUE;
}
void resetOverlappedEvent(OVERLAPPED *ov) {
if (!ov || !ov->hEvent) {
return;
}
setEventSignaledState(ov->hEvent, false);
}
void signalOverlappedEvent(OVERLAPPED *ov) {
if (!ov || !ov->hEvent) {
return;
}
setEventSignaledState(ov->hEvent, true);
}
} // namespace kernel32

View File

@ -168,7 +168,7 @@ namespace kernel32 {
UINT WIN_FUNC SetHandleCount(UINT uNumber) {
DEBUG_LOG("SetHandleCount(%u)\n", uNumber);
(void)uNumber;
return handles::MAX_HANDLES;
return (1u << 17) - 1;
}
DWORD WIN_FUNC FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPSTR lpBuffer,

View File

@ -74,10 +74,10 @@ BOOL WIN_FUNC WriteConsoleW(HANDLE hConsoleOutput, LPCVOID lpBuffer, DWORD nNumb
return FALSE;
}
FILE *fp = files::fpFromHandle(hConsoleOutput);
if (fp == stdout || fp == stderr) {
auto file = wibo::handles().getAs<FileObject>(hConsoleOutput);
if (file->fd == STDOUT_FILENO || file->fd == STDERR_FILENO) {
auto str = wideStringToString(static_cast<const uint16_t *>(lpBuffer), nNumberOfCharsToWrite);
fprintf(fp, "%s", str.c_str());
dprintf(file->fd, "%s", str.c_str());
if (lpNumberOfCharsWritten) {
*lpNumberOfCharsWritten = nNumberOfCharsToWrite;
}

View File

@ -1,5 +1,6 @@
#include "wow64apiset.h"
#include "common.h"
#include "dll/kernel32/internal.h"
#include "errors.h"
#include "handles.h"
@ -29,14 +30,14 @@ BOOL WIN_FUNC IsWow64Process(HANDLE hProcess, PBOOL Wow64Process) {
}
uintptr_t rawHandle = reinterpret_cast<uintptr_t>(hProcess);
bool isPseudoHandle = rawHandle == static_cast<uintptr_t>(-1);
bool isPseudoHandle = rawHandle == kPseudoCurrentProcessHandleValue;
if (!isPseudoHandle) {
if (!hProcess) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}
auto data = handles::dataFromHandle(hProcess, false);
if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) {
auto obj = wibo::handles().getAs<ProcessObject>(hProcess);
if (!obj) {
wibo::lastError = ERROR_INVALID_HANDLE;
return FALSE;
}

View File

@ -1,4 +1,5 @@
#include "common.h"
#include "dll/kernel32/internal.h"
#include <algorithm>
#include <array>
#include <cerrno>
@ -2687,7 +2688,7 @@ namespace msvcrt {
argStorage.emplace_back(wideStringToString(*cursor));
}
auto resolved = processes::resolveExecutable(command, false);
auto resolved = wibo::resolveExecutable(command, false);
if (!resolved) {
errno = ENOENT;
DEBUG_LOG("-> failed to resolve executable for %s\n", command.c_str());
@ -2695,31 +2696,22 @@ namespace msvcrt {
}
DEBUG_LOG("-> resolved to %s\n", resolved->c_str());
pid_t pid = -1;
int spawnResult = processes::spawnWithArgv(*resolved, argStorage, &pid);
Pin<ProcessObject> po;
int spawnResult = wibo::spawnWithArgv(*resolved, argStorage, po);
if (spawnResult != 0) {
errno = spawnResult;
DEBUG_LOG("-> spawnWithArgv failed: %d\n", spawnResult);
return -1;
}
DEBUG_LOG("-> spawned pid %d\n", pid);
DEBUG_LOG("-> spawned pid %d\n", po->pid);
constexpr int P_WAIT = 0;
constexpr int P_DETACH = 2;
if (mode == P_WAIT) {
int status = 0;
if (waitpid(pid, &status, 0) == -1) {
DEBUG_LOG("\twaitpid failed: %d\n", errno);
return -1;
}
if (WIFEXITED(status)) {
return static_cast<intptr_t>(WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
errno = EINTR;
}
return -1;
std::unique_lock lk(po->m);
po->cv.wait(lk, [&] { return po->signaled.load(); });
return static_cast<intptr_t>(po->exitCode);
}
if (mode == P_DETACH) {
@ -2727,7 +2719,7 @@ namespace msvcrt {
}
// _P_NOWAIT and unknown flags: return process id
return static_cast<intptr_t>(pid);
return static_cast<intptr_t>(po->pid);
}
intptr_t WIN_ENTRY _spawnvp(int mode, const char *cmdname, const char * const *argv) {
@ -2744,7 +2736,7 @@ namespace msvcrt {
argStorage.emplace_back(*cursor);
}
auto resolved = processes::resolveExecutable(command, false);
auto resolved = wibo::resolveExecutable(command, false);
if (!resolved) {
errno = ENOENT;
DEBUG_LOG("-> failed to resolve executable for %s\n", command.c_str());
@ -2752,37 +2744,29 @@ namespace msvcrt {
}
DEBUG_LOG("-> resolved to %s\n", resolved->c_str());
pid_t pid = -1;
int spawnResult = processes::spawnWithArgv(*resolved, argStorage, &pid);
Pin<ProcessObject> po;
int spawnResult = wibo::spawnWithArgv(*resolved, argStorage, po);
if (spawnResult != 0) {
errno = spawnResult;
DEBUG_LOG("-> spawnWithArgv failed: %d\n", spawnResult);
return -1;
}
DEBUG_LOG("-> spawned pid %d\n", pid);
DEBUG_LOG("-> spawned pid %d\n", po->pid);
constexpr int P_WAIT = 0;
constexpr int P_DETACH = 2;
if (mode == P_WAIT) {
int status = 0;
if (waitpid(pid, &status, 0) == -1) {
return -1;
}
if (WIFEXITED(status)) {
return static_cast<intptr_t>(WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) {
errno = EINTR;
}
return -1;
std::unique_lock lk(po->m);
po->cv.wait(lk, [&] { return po->signaled.load(); });
return static_cast<intptr_t>(po->exitCode);
}
if (mode == P_DETACH) {
return 0;
}
return static_cast<intptr_t>(pid);
return static_cast<intptr_t>(po->pid);
}
int WIN_ENTRY _wunlink(const uint16_t *filename){

View File

@ -1,4 +1,5 @@
#include "common.h"
#include "dll/kernel32/internal.h"
#include "errors.h"
#include "files.h"
#include "handles.h"
@ -71,10 +72,10 @@ using PRTL_OSVERSIONINFOEXW = RTL_OSVERSIONINFOEXW *;
constexpr ULONG kOsMajorVersion = 6;
constexpr ULONG kOsMinorVersion = 2;
constexpr ULONG kOsBuildNumber = 0;
constexpr ULONG kOsPlatformId = 2; // VER_PLATFORM_WIN32_NT
constexpr ULONG kOsPlatformId = 2; // VER_PLATFORM_WIN32_NT
constexpr BYTE kProductTypeWorkstation = 1; // VER_NT_WORKSTATION
static bool resolveProcessDetails(HANDLE processHandle, ProcessHandleDetails &details) {
bool resolveProcessDetails(HANDLE processHandle, ProcessHandleDetails &details) {
uintptr_t rawHandle = reinterpret_cast<uintptr_t>(processHandle);
if (rawHandle == static_cast<uintptr_t>(-1)) {
details.pid = getpid();
@ -84,20 +85,19 @@ static bool resolveProcessDetails(HANDLE processHandle, ProcessHandleDetails &de
return true;
}
auto data = handles::dataFromHandle(processHandle, false);
if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) {
auto po = wibo::handles().getAs<ProcessObject>(processHandle);
if (!po) {
return false;
}
auto *process = reinterpret_cast<processes::Process *>(data.ptr);
details.pid = process->pid;
details.exitCode = process->exitCode;
details.isCurrentProcess = (process->pid == getpid());
details.pid = po->pid;
details.exitCode = po->exitCode;
details.isCurrentProcess = po->pid == getpid();
details.peb = details.isCurrentProcess ? wibo::processPeb : nullptr;
return true;
}
static std::string windowsImagePathFor(const ProcessHandleDetails &details) {
std::string windowsImagePathFor(const ProcessHandleDetails &details) {
if (details.isCurrentProcess && !wibo::guestExecutablePath.empty()) {
return files::pathToWindows(files::canonicalPath(wibo::guestExecutablePath));
}
@ -134,15 +134,15 @@ NTSTATUS WIN_FUNC NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE Ap
return STATUS_INVALID_PARAMETER;
}
auto file = files::fileHandleFromHandle(FileHandle);
if (!file || !file->fp) {
auto file = wibo::handles().getAs<FileObject>(FileHandle);
if (!file || !file->valid()) {
wibo::lastError = ERROR_INVALID_HANDLE;
IoStatusBlock->Status = STATUS_INVALID_HANDLE;
IoStatusBlock->Information = 0;
return STATUS_INVALID_HANDLE;
}
bool handleOverlapped = (file->flags & FILE_FLAG_OVERLAPPED) != 0;
bool handleOverlapped = file->overlapped;
std::optional<uint64_t> offset;
bool updateFilePointer = !handleOverlapped;
if (ByteOffset) {
@ -155,7 +155,7 @@ NTSTATUS WIN_FUNC NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE Ap
kernel32::ResetEvent(Event);
}
auto io = files::read(file, Buffer, Length, offset, updateFilePointer);
auto io = files::read(file.get(), Buffer, Length, offset, updateFilePointer);
DWORD winError = ERROR_SUCCESS;
NTSTATUS status = STATUS_SUCCESS;
if (io.unixError != 0) {
@ -286,10 +286,10 @@ NTSTATUS WIN_FUNC RtlGetVersion(PRTL_OSVERSIONINFOW lpVersionInformation) {
}
NTSTATUS WIN_FUNC NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation, ULONG ProcessInformationLength,
PULONG ReturnLength) {
PVOID ProcessInformation, ULONG ProcessInformationLength,
PULONG ReturnLength) {
DEBUG_LOG("NtQueryInformationProcess(%p, %u, %p, %u, %p) ", ProcessHandle, ProcessInformationClass,
ProcessInformation, ProcessInformationLength, ReturnLength);
ProcessInformation, ProcessInformationLength, ReturnLength);
if (!ProcessInformation) {
DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER);
return STATUS_INVALID_PARAMETER;
@ -371,7 +371,8 @@ NTSTATUS WIN_FUNC NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLA
}
auto *unicode = reinterpret_cast<UNICODE_STRING *>(ProcessInformation);
auto *buffer = reinterpret_cast<uint16_t *>(reinterpret_cast<uint8_t *>(ProcessInformation) + sizeof(UNICODE_STRING));
auto *buffer =
reinterpret_cast<uint16_t *>(reinterpret_cast<uint8_t *>(ProcessInformation) + sizeof(UNICODE_STRING));
std::memcpy(buffer, widePath.data(), stringBytes);
size_t characterCount = widePath.empty() ? 0 : widePath.size() - 1;
unicode->Length = static_cast<unsigned short>(characterCount * sizeof(uint16_t));

View File

@ -37,6 +37,8 @@
#define ERROR_DLL_INIT_FAILED 1114
#define ERROR_ALREADY_EXISTS 183
#define ERROR_NOT_OWNER 288
#define ERROR_TOO_MANY_POSTS 298
#define ERROR_SEM_TIMEOUT 121
#define INVALID_SET_FILE_POINTER ((DWORD) - 1)
#define INVALID_HANDLE_VALUE ((HANDLE) - 1)

125
files.cpp
View File

@ -6,12 +6,28 @@
#include <algorithm>
#include <cerrno>
#include <climits>
#include <cstddef>
#include <cstdio>
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <strings.h>
#include <system_error>
#include <unistd.h>
#include <utility>
kernel32::FsObject::~FsObject() {
std::lock_guard lk(m);
int fd = std::exchange(this->fd, -1);
if (fd >= 0 && closeOnDestroy) {
close(fd);
}
if (deletePending && !canonicalPath.empty()) {
if (unlink(canonicalPath.c_str()) != 0) {
perror("Failed to delete file on close");
}
}
}
namespace files {
@ -59,9 +75,9 @@ static std::string toHostPathEntry(const std::string &entry) {
return normalized;
}
static void *stdinHandle;
static void *stdoutHandle;
static void *stderrHandle;
static HANDLE stdinHandle;
static HANDLE stdoutHandle;
static HANDLE stderrHandle;
std::filesystem::path pathFromWindows(const char *inStr) {
// Convert to forward slashes
@ -126,36 +142,6 @@ std::string pathToWindows(const std::filesystem::path &path) {
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{};
@ -176,7 +162,7 @@ IOResult read(FileObject *file, void *buffer, size_t bytesToRead, const std::opt
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);
ssize_t rc = pread64(file->fd, in + total, chunk, pos);
if (rc == -1) {
if (errno == EINTR) {
continue;
@ -198,11 +184,11 @@ IOResult read(FileObject *file, void *buffer, size_t bytesToRead, const std::opt
};
if (updateFilePointer || !offset.has_value()) {
std::unique_lock<std::mutex> lock(file->pos_mu);
off64_t pos = offset.value_or(file->file_pos);
std::lock_guard lk(file->m);
off64_t pos = offset.value_or(file->filePos);
doRead(pos);
if (updateFilePointer) {
file->file_pos = pos + static_cast<off64_t>(result.bytesTransferred);
file->filePos = pos + static_cast<off64_t>(result.bytesTransferred);
}
} else {
doRead(*offset);
@ -211,10 +197,10 @@ IOResult read(FileObject *file, void *buffer, size_t bytesToRead, const std::opt
return result;
}
IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, const std::optional<off64_t> &offset,
IOResult write(FileObject *file, const void *buffer, size_t bytesToWrite, const std::optional<off64_t> &offset,
bool updateFilePointer) {
IOResult result{};
if (!handle || handle->host_fd < 0) {
if (!file || !file->valid()) {
result.unixError = EBADF;
return result;
}
@ -225,24 +211,55 @@ IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, cons
// Sanity check: if no offset is given, we must update the file pointer
assert(offset.has_value() || updateFilePointer);
if (file->appendOnly || !file->seekable) {
std::lock_guard lk(file->m);
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 = ::write(file->fd, in + total, chunk);
if (rc == -1) {
if (errno == EINTR) {
continue;
}
result.unixError = errno ? errno : EIO;
break;
}
if (rc == 0) {
break;
}
total += static_cast<size_t>(rc);
remaining -= static_cast<size_t>(rc);
}
result.bytesTransferred = total;
if (updateFilePointer) {
off64_t pos = file->seekable ? lseek64(file->fd, 0, SEEK_CUR) : 0;
if (pos >= 0) {
file->filePos = pos;
} else if (result.unixError == 0) {
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);
ssize_t rc = pwrite64(file->fd, in + total, chunk, pos);
if (rc == -1) {
if (errno == EINTR) {
continue;
}
result.bytesTransferred = total;
result.unixError = errno ? errno : EIO;
return;
break;
}
if (rc == 0) {
result.bytesTransferred = total;
return;
break;
}
total += static_cast<size_t>(rc);
remaining -= static_cast<size_t>(rc);
@ -252,11 +269,11 @@ IOResult write(FileObject *handle, const void *buffer, size_t bytesToWrite, cons
};
if (updateFilePointer || !offset.has_value()) {
std::unique_lock<std::mutex> lock(handle->pos_mu);
const off64_t pos = handle->file_pos;
std::lock_guard lk(file->m);
const off64_t pos = offset.value_or(file->filePos);
doWrite(pos);
if (updateFilePointer) {
handle->file_pos = pos + static_cast<off64_t>(result.bytesTransferred);
file->filePos = pos + static_cast<off64_t>(result.bytesTransferred);
}
} else {
doWrite(*offset);
@ -297,9 +314,15 @@ BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle) {
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);
auto stdinObject = make_pin<FileObject>(STDIN_FILENO);
stdinObject->closeOnDestroy = false;
stdinHandle = handles.alloc(std::move(stdinObject), FILE_GENERIC_READ, 0);
auto stdoutObject = make_pin<FileObject>(STDOUT_FILENO);
stdoutObject->closeOnDestroy = false;
stdoutHandle = handles.alloc(std::move(stdoutObject), FILE_GENERIC_WRITE, 0);
auto stderrObject = make_pin<FileObject>(STDERR_FILENO);
stderrObject->closeOnDestroy = false;
stderrHandle = handles.alloc(std::move(stderrObject), FILE_GENERIC_WRITE, 0);
}
std::optional<std::filesystem::path> findCaseInsensitiveFile(const std::filesystem::path &directory,

62
files.h
View File

@ -1,55 +1,39 @@
#pragma once
#include "common.h"
#include "handles.h"
#include "dll/kernel32/internal.h"
#include <cstdio>
#include <filesystem>
#include <mutex>
#include <optional>
#include <string>
using kernel32::FileObject;
namespace files {
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;
}
}
struct IOResult {
size_t bytesTransferred = 0;
int unixError = 0;
bool reachedEnd = false;
};
[[nodiscard]] bool valid() const { return host_fd >= 0; }
};
void init();
std::filesystem::path pathFromWindows(const char *inStr);
std::string pathToWindows(const std::filesystem::path &path);
IOResult read(FileObject *file, void *buffer, size_t bytesToRead, const std::optional<off64_t> &offset,
bool updateFilePointer);
IOResult write(FileObject *file, const void *buffer, size_t bytesToWrite, const std::optional<off64_t> &offset,
bool updateFilePointer);
HANDLE getStdHandle(DWORD nStdHandle);
BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle);
std::optional<std::filesystem::path> findCaseInsensitiveFile(const std::filesystem::path &directory,
const std::string &filename);
std::filesystem::path canonicalPath(const std::filesystem::path &path);
std::string hostPathListToWindows(const std::string &value);
std::string windowsPathListToHost(const std::string &value);
struct IOResult {
size_t bytesTransferred = 0;
int unixError = 0;
bool reachedEnd = false;
};
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);
FILE *fpFromHandle(void *handle, bool pop = false);
// 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();
std::optional<std::filesystem::path> findCaseInsensitiveFile(const std::filesystem::path &directory, const std::string &filename);
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
} // 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,121 +1,144 @@
#include "handles.h"
#include <cassert>
#include <cstdint>
namespace {
constexpr uint32_t kIndexBits = 17;
constexpr uint32_t kIndexMask = (1u << kIndexBits) - 1; // 0x1FFFF
constexpr uint32_t kGenerationMask = (1u << 15) - 1; // 0x7FFF
constexpr unsigned kGenerationShift = kIndexBits; // 17
constexpr uint32_t kHandleAlignShift = 2;
// Max index that still yields HANDLE < 0x10000 with (index + 1) << 2
constexpr uint32_t kCompatMaxIndex = (0xFFFFu >> kHandleAlignShift) - 1;
// Delay reuse of small handles to avoid accidental stale aliasing
constexpr uint32_t kQuarantineLen = 64;
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 uint32_t indexOf(HANDLE h) {
uint32_t v = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h));
if (v == 0 || (v & ((1U << kHandleAlignShift) - 1)) != 0) {
return UINT32_MAX;
}
return (v >> kHandleAlignShift) - 1;
}
inline HANDLE makeHandle(uint32_t index) {
uint32_t v = (index + 1) << kHandleAlignShift;
return reinterpret_cast<HANDLE>(static_cast<uintptr_t>(v));
}
inline bool isPseudo(HANDLE h) { return reinterpret_cast<int32_t>(h) < 0; }
} // namespace
HANDLE HandleTable::create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags) {
std::unique_lock lk(mu_);
HANDLE Handles::alloc(Pin<> obj, uint32_t grantedAccess, uint32_t flags) {
std::unique_lock lk(m);
// Attempt to, in order:
// 1) use a fresh index in the compat range (0..kCompatMaxIndex)
// 2) reuse a recently-freed index in the compat range
// 3) reuse a recently-freed index above the compat range
// 4) use a fresh index above the compat range
uint32_t idx;
if (!freeList_.empty()) {
idx = freeList_.back();
freeList_.pop_back();
if (nextIndex <= kCompatMaxIndex) {
idx = nextIndex++;
if (idx >= mSlots.size()) {
mSlots.emplace_back();
}
} else if (!mFreeBelow.empty()) {
idx = mFreeBelow.back();
mFreeBelow.pop_back();
} else if (!mFreeAbove.empty()) {
idx = mFreeAbove.back();
mFreeAbove.pop_back();
} else {
idx = static_cast<uint32_t>(slots_.size());
slots_.push_back(HandleEntry{});
idx = static_cast<uint32_t>(mSlots.size());
mSlots.emplace_back();
}
auto &e = slots_[idx];
// Initialize generation if needed
// Initialize entry
auto &e = mSlots[idx];
e.obj = obj.release(); // Transfer ownership
e.meta.grantedAccess = grantedAccess;
e.meta.flags = flags;
e.meta.typeCache = e.obj->type;
if (e.meta.generation == 0) {
e.meta.generation = 1;
}
const uint16_t gen = e.meta.generation;
// Table owns one pointer ref for this entry
detail::ref(obj);
// Initialize entry
e.obj = obj;
e.meta.grantedAccess = grantedAccess;
e.meta.flags = flags;
e.meta.typeCache = obj->type;
HANDLE h = makeHandle(idx, gen);
obj->handleCount.fetch_add(1, std::memory_order_acq_rel);
HANDLE h = makeHandle(idx);
e.obj->handleCount.fetch_add(1, std::memory_order_acq_rel);
return h;
}
bool HandleTable::get(HANDLE h, Pin<ObjectHeader> &pinOut, HandleMeta *metaOut) {
Pin<> Handles::get(HANDLE h, HandleMeta *metaOut) {
if (isPseudo(h)) {
return false; // pseudo-handles have no entries
return {}; // pseudo-handles have no entries
}
std::shared_lock lk(mu_);
std::shared_lock lk(m);
const auto idx = indexOf(h);
if (idx >= slots_.size()) {
return false;
}
const auto &e = slots_[idx];
if (e.meta.generation != generationOf(h) || !e.obj) {
return false;
if (idx >= mSlots.size()) {
return {};
}
detail::ref(e.obj); // pin under the lock
pinOut = Pin<ObjectHeader>::adopt(e.obj); // dtor will deref
const auto &e = mSlots[idx];
if (!e.obj) {
return {};
}
if (metaOut) {
*metaOut = e.meta;
}
return true;
return Pin<>::acquire(e.obj);
}
bool HandleTable::close(HANDLE h) {
bool Handles::release(HANDLE h) {
if (isPseudo(h)) {
return true; // no-op, success
}
std::unique_lock lk(mu_);
std::unique_lock lk(m);
const auto idx = indexOf(h);
if (idx >= slots_.size()) {
if (idx >= mSlots.size()) {
return false;
}
auto &e = slots_[idx];
if (e.meta.generation != generationOf(h) || !e.obj || e.meta.flags & HANDLE_FLAG_PROTECT_FROM_CLOSE) {
auto &e = mSlots[idx];
if (!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*/;
ObjectBase *obj = e.obj;
const auto generation = e.meta.generation + 1;
e = {}; // Clear entry
e.meta.generation = generation;
uint32_t handleCount = obj->handleCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
// bump generation & recycle while still holding the lock
e.meta.generation = static_cast<uint16_t>((e.meta.generation + 1) & kGenerationMask);
freeList_.push_back(idx);
if (idx <= kCompatMaxIndex) {
mQuarantine.push_back(idx);
if (mQuarantine.size() > kQuarantineLen) {
mFreeBelow.push_back(mQuarantine.front());
mQuarantine.pop_front();
}
} else {
mFreeAbove.push_back(idx);
}
lk.unlock();
// if (newHandleCnt == 0) {
// namespaceOnHandleCountZero(obj);
// }
if (handleCount == 0 && mOnHandleZero) {
mOnHandleZero(obj);
}
detail::deref(obj);
return true;
}
bool HandleTable::setInformation(HANDLE h, uint32_t mask, uint32_t value) {
bool Handles::setInformation(HANDLE h, uint32_t mask, uint32_t value) {
if (isPseudo(h)) {
return true; // no-op, success
}
std::unique_lock lk(mu_);
std::unique_lock lk(m);
const auto idx = indexOf(h);
if (idx >= slots_.size()) {
if (idx >= mSlots.size()) {
return false;
}
auto &e = slots_[idx];
if (e.meta.generation != generationOf(h) || !e.obj) {
auto &e = mSlots[idx];
if (!e.obj) {
return false;
}
@ -126,7 +149,7 @@ bool HandleTable::setInformation(HANDLE h, uint32_t mask, uint32_t value) {
return true;
}
bool HandleTable::getInformation(HANDLE h, uint32_t *outFlags) const {
bool Handles::getInformation(HANDLE h, uint32_t *outFlags) const {
if (!outFlags) {
return false;
}
@ -134,37 +157,24 @@ bool HandleTable::getInformation(HANDLE h, uint32_t *outFlags) const {
*outFlags = 0;
return true;
}
std::shared_lock lk(mu_);
std::shared_lock lk(m);
const auto idx = indexOf(h);
if (idx >= slots_.size()) {
if (idx >= mSlots.size()) {
return false;
}
const auto &e = slots_[idx];
if (e.meta.generation != generationOf(h) || !e.obj) {
const auto &e = mSlots[idx];
if (!e.obj) {
return false;
}
*outFlags = e.meta.flags;
return true;
}
bool HandleTable::duplicateTo(HANDLE src, HandleTable &dst, HANDLE *out, uint32_t desiredAccess, bool inherit,
uint32_t options) {
if (!out)
return false;
// Pseudo-handles: resolve to a Borrow of the live object
// if (isPseudo(src)) {
// Pin pin = resolvePseudoBorrow(src);
// if (!pin)
// return false;
// const uint32_t granted = desiredAccess; // or compute from type; pseudo has full rights to self
// *out = dst.create(pin.obj, granted, inherit ? HANDLE_FLAG_INHERIT : 0);
// return true;
// }
bool Handles::duplicateTo(HANDLE src, Handles &dst, HANDLE &out, uint32_t desiredAccess, bool inherit,
uint32_t options) {
HandleMeta meta{};
Pin<ObjectHeader> pin;
if (!get(src, pin, &meta)) {
Pin<> obj = get(src, &meta);
if (!obj) {
return false;
}
@ -176,10 +186,53 @@ bool HandleTable::duplicateTo(HANDLE src, HandleTable &dst, HANDLE *out, uint32_
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);
out = dst.alloc(std::move(obj), effAccess, flags);
if (closeSource) {
close(src);
release(src);
}
return true;
}
bool Namespace::insert(const std::u16string &name, ObjectBase *obj, bool permanent) {
if (name.empty() || !obj) {
return false;
}
std::unique_lock lk(m);
// Namespace holds a weak ref
const auto [_, inserted] = mTable.try_emplace(name, obj, permanent);
return inserted;
}
void Namespace::remove(ObjectBase *obj) {
std::unique_lock lk(m);
for (auto it = mTable.begin(); it != mTable.end(); ++it) {
if (it->second.obj == obj && !it->second.permanent) {
mTable.erase(it);
break;
}
}
}
Pin<> Namespace::get(const std::u16string &name) {
if (name.empty()) {
return {};
}
std::shared_lock lk(m);
auto it = mTable.find(name);
if (it == mTable.end()) {
return {};
}
assert(it->second.obj);
return Pin<>::acquire(it->second.obj);
}
namespace wibo {
Namespace g_namespace;
Handles &handles() {
static Handles table([](ObjectBase *obj) { g_namespace.remove(obj); });
return table;
}
} // namespace wibo

192
handles.h
View File

@ -3,15 +3,20 @@
#include "common.h"
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <cstdint>
#include <cstdio>
#include <deque>
#include <functional>
#include <shared_mutex>
#include <utility>
#include <vector>
enum class ObjectType : uint16_t {
File,
Mapped,
Directory,
Mapping,
Process,
Token,
Mutex,
@ -22,58 +27,60 @@ enum class ObjectType : uint16_t {
RegistryKey,
};
struct ObjectHeader {
struct ObjectBase {
const ObjectType type;
std::atomic<uint32_t> pointerCount{1};
std::atomic<uint32_t> pointerCount{0};
std::atomic<uint32_t> handleCount{0};
explicit ObjectHeader(ObjectType t) : type(t) {}
virtual ~ObjectHeader() = default;
explicit ObjectBase(ObjectType t) : type(t) {}
virtual ~ObjectBase() = default;
[[nodiscard]] virtual bool isWaitable() const { return false; }
virtual void onDestroy() {}
};
namespace detail {
inline void ref(ObjectHeader *o) { o->pointerCount.fetch_add(1, std::memory_order_acq_rel); }
inline void deref(ObjectHeader *o) {
inline void ref(ObjectBase *o) { o->pointerCount.fetch_add(1, std::memory_order_acq_rel); }
inline void deref(ObjectBase *o) {
if (o->pointerCount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
o->onDestroy();
delete o;
}
}
} // namespace detail
struct WaitableObject : ObjectHeader {
struct WaitableObject : ObjectBase {
std::atomic<bool> signaled{false};
std::mutex m;
std::condition_variable_any cv;
using ObjectHeader::ObjectHeader;
using ObjectBase::ObjectBase;
[[nodiscard]] bool isWaitable() const override { return true; }
};
template <class T> struct Pin {
enum class Tag { Acquire, Adopt };
template <class T = ObjectBase> struct Pin {
static_assert(std::is_base_of_v<ObjectBase, T> || std::is_same_v<ObjectBase, T>,
"Pin<T>: T must be ObjectBase or derive from it");
T *obj = nullptr;
Pin() = default;
Pin(T *o, Tag t) : obj(o) {
enum class Tag { Acquire, Adopt };
template <class U, class = std::enable_if_t<std::is_convertible<U *, T *>::value>>
explicit Pin(U *p, Tag t) : obj(static_cast<T *>(p)) {
if (obj && t == Tag::Acquire) {
detail::ref(obj);
}
}
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;
Pin(Pin &&other) noexcept : obj(std::exchange(other.obj, nullptr)) {}
template <class U, class = std::enable_if_t<std::is_base_of<T, U>::value>> Pin &operator=(Pin<U> &&other) noexcept {
reset();
obj = std::exchange(other.obj, nullptr);
return *this;
}
template <class U, class = std::enable_if_t<std::is_base_of<T, U>::value>>
Pin(Pin<U> &&other) noexcept : obj(std::exchange(other.obj, nullptr)) {} // NOLINT(google-explicit-constructor)
Pin &operator=(Pin &&other) noexcept {
if (this != &other) {
reset();
@ -84,7 +91,10 @@ template <class T> struct Pin {
~Pin() { reset(); }
ObjectHeader *release() { return std::exchange(obj, nullptr); }
static Pin acquire(T *o) { return Pin{o, Tag::Acquire}; }
static Pin adopt(T *o) { return Pin{o, Tag::Adopt}; }
[[nodiscard]] T *release() { return std::exchange(obj, nullptr); }
void reset() {
if (obj) {
detail::deref(obj);
@ -92,12 +102,23 @@ template <class T> struct Pin {
}
}
T *operator->() const { return obj; }
T &operator*() const { return *obj; }
T *operator->() const {
assert(obj);
return obj;
}
T &operator*() const {
assert(obj);
return *obj;
}
[[nodiscard]] T *get() const { return obj; }
[[nodiscard]] Pin<T> clone() const { return Pin<T>::acquire(obj); }
explicit operator bool() const { return obj != nullptr; }
template <typename U> Pin<U> downcast() && {
static_assert(std::is_base_of_v<ObjectBase, U>, "U must derive from ObjectBase");
if constexpr (std::is_same_v<T, U>) {
return std::move(*this);
}
if (obj && obj->type == U::kType) {
auto *u = static_cast<U *>(obj);
obj = nullptr;
@ -107,6 +128,12 @@ template <class T> struct Pin {
}
};
template <class T, class... Args>
Pin<T> make_pin(Args &&...args) noexcept(std::is_nothrow_constructible_v<T, Args...>) {
T *p = new T(std::forward<Args>(args)...);
return Pin<T>::acquire(p);
}
constexpr DWORD HANDLE_FLAG_INHERIT = 0x1;
constexpr DWORD HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x2;
@ -114,62 +141,113 @@ constexpr DWORD DUPLICATE_CLOSE_SOURCE = 0x1;
constexpr DWORD DUPLICATE_SAME_ACCESS = 0x2;
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
uint32_t grantedAccess;
uint32_t flags;
ObjectType typeCache;
uint16_t generation;
};
// template <typename T>
// struct HandleRef {
// Pin<T> obj;
// HandleMeta meta;
// We have to stay under a HANDLE value of 0xFFFF for legacy applications,
// and handles values are aligned to 4.
constexpr DWORD MAX_HANDLES = 0x4000;
// HandleRef() = default;
// HandleRef(Pin<T> o, HandleMeta m) : obj(std::move(o)), meta(m) {}
// explicit operator bool() const { return obj.operator bool(); }
// };
class HandleTable {
class Handles {
public:
HANDLE create(ObjectHeader *obj, uint32_t grantedAccess, uint32_t flags);
bool close(HANDLE h);
bool get(HANDLE h, Pin<ObjectHeader> &pinOut, HandleMeta *metaOut = nullptr);
using OnHandleZeroFn = void (*)(ObjectBase *);
explicit Handles(OnHandleZeroFn cb) : mOnHandleZero(cb) {}
HANDLE alloc(Pin<> obj, uint32_t grantedAccess, uint32_t flags);
bool release(HANDLE h);
Pin<> get(HANDLE h, 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");
Pin<ObjectHeader> pin;
static_assert(std::is_base_of_v<ObjectBase, T>, "T must derive from ObjectBase");
HandleMeta metaOutLocal{};
if (!metaOut) {
metaOut = &metaOutLocal;
}
if (!get(h, pin, metaOut)) {
auto obj = get(h, metaOut);
if (!obj) {
return {};
}
if constexpr (std::is_same_v<T, ObjectHeader>) {
return std::move(pin);
} else if (metaOut->typeCache != T::kType || pin->type != T::kType) {
if constexpr (std::is_same_v<T, ObjectBase>) {
return std::move(obj);
} else if (metaOut->typeCache != T::kType || obj->type != T::kType) {
return {};
} else {
// Cast directly to T* and transfer ownership to Pin<T>
return Pin<T>::adopt(static_cast<T *>(pin.release()));
return Pin<T>::adopt(static_cast<T *>(obj.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 duplicateTo(HANDLE src, Handles &dst, HANDLE &out, uint32_t desiredAccess, bool inherit, uint32_t options);
private:
struct HandleEntry {
struct ObjectHeader *obj;
struct Entry {
ObjectBase *obj;
HandleMeta meta;
};
std::vector<HandleEntry> slots_;
std::vector<uint32_t> freeList_;
mutable std::shared_mutex mu_;
mutable std::shared_mutex m;
std::vector<Entry> mSlots;
OnHandleZeroFn mOnHandleZero = nullptr;
std::vector<uint32_t> mFreeBelow;
std::vector<uint32_t> mFreeAbove;
std::deque<uint32_t> mQuarantine;
uint32_t nextIndex = 0;
};
class Namespace {
public:
bool insert(const std::u16string &name, ObjectBase *obj, bool permanent = false);
void remove(ObjectBase *obj);
Pin<> get(const std::u16string &name);
template <typename T> Pin<T> getAs(const std::u16string &name) {
if (auto pin = get(name)) {
return std::move(pin).downcast<T>();
}
return {};
}
template <typename F, typename Ptr = std::invoke_result_t<F &>,
typename T = std::remove_pointer_t<std::decay_t<Ptr>>,
std::enable_if_t<std::is_pointer<std::decay_t<Ptr>>::value, int> = 0>
std::pair<Pin<T>, bool> getOrCreate(const std::u16string &name, F &&make) {
if (name.empty()) {
// No name: create unconditionally
T *raw = std::invoke(std::forward<F>(make));
return {Pin<T>::acquire(raw), true};
}
if (auto existing = get(name)) {
// Return even if downcast fails (don't use getAs<T>)
return {std::move(existing).downcast<T>(), false};
}
T *raw = std::invoke(std::forward<F>(make));
Pin<T> newObj = Pin<T>::acquire(raw);
if (!newObj) {
return {Pin<T>{}, false};
}
if (!insert(name, newObj.get())) {
// Race: someone else inserted it first
return {getAs<T>(name), false};
}
return {std::move(newObj), true};
}
private:
struct Entry {
ObjectBase *obj;
bool permanent;
Entry(ObjectBase *o, bool p) : obj(o), permanent(p) {}
};
mutable std::shared_mutex m;
std::unordered_map<std::u16string, Entry> mTable;
};
namespace wibo {
extern HandleTable &handles();
}
extern Namespace g_namespace;
extern Handles &handles();
} // namespace wibo

View File

@ -333,6 +333,7 @@ int main(int argc, char **argv) {
blockUpper2GB();
files::init();
wibo::processes().init();
// Create TIB
memset(&tib, 0, sizeof(tib));
@ -347,7 +348,7 @@ int main(int argc, char **argv) {
}
// Determine the guest program name
auto guestArgs = processes::splitCommandLine(cmdLine.c_str());
auto guestArgs = wibo::splitCommandLine(cmdLine.c_str());
std::string programName;
if (programIndex != -1) {
programName = argv[programIndex];
@ -360,7 +361,7 @@ int main(int argc, char **argv) {
}
// Resolve the guest program path
std::filesystem::path resolvedGuestPath = processes::resolveExecutable(programName, true).value_or({});
std::filesystem::path resolvedGuestPath = wibo::resolveExecutable(programName, true).value_or({});
if (resolvedGuestPath.empty()) {
fprintf(stderr, "Failed to resolve path to guest program %s\n", programName.c_str());
return 1;

View File

@ -154,7 +154,7 @@ void registerBuiltinModule(ModuleRegistry &reg, const wibo::Module *module);
LockedRegistry registry() {
static ModuleRegistry reg;
std::unique_lock<std::recursive_mutex> guard(reg.mutex);
std::unique_lock guard(reg.mutex);
if (!reg.initialized) {
reg.initialized = true;
const wibo::Module *builtins[] = {

View File

@ -1,5 +1,6 @@
#include "processes.h"
#include "common.h"
#include "dll/kernel32/internal.h"
#include "files.h"
#include "handles.h"
#include <algorithm>
@ -7,300 +8,484 @@
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <spawn.h>
#include <strings.h>
#include <string>
#include <strings.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
extern "C" char **environ;
namespace {
namespace processes {
void *allocProcessHandle(pid_t pid) {
auto* process = new Process;
process->pid = pid;
process->exitCode = STILL_ACTIVE;
process->forcedExitCode = STILL_ACTIVE;
process->terminationRequested = false;
inline DWORD decodeExitCode(const siginfo_t &si) {
switch (si.si_code) {
case CLD_EXITED:
return static_cast<DWORD>(si.si_status);
case CLD_KILLED:
case CLD_DUMPED:
return 0xC0000000u | static_cast<DWORD>(si.si_status);
default:
return 0;
}
}
return handles::allocDataHandle(handles::Data{handles::TYPE_PROCESS, (void*)process, 0});
} // namespace
namespace wibo {
ProcessManager::~ProcessManager() { shutdown(); }
bool ProcessManager::init() {
if (mRunning.load(std::memory_order_acquire)) {
return true;
}
Process* processFromHandle(void *handle, bool pop) {
handles::Data data = handles::dataFromHandle(handle, pop);
if (data.type == handles::TYPE_PROCESS) {
return (Process*)data.ptr;
} else {
printf("Invalid file handle %p\n", handle);
assert(0);
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
if (mEpollFd < 0) {
perror("epoll_create1");
return false;
}
mWakeFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (mWakeFd < 0) {
perror("eventfd");
close(mEpollFd);
mEpollFd = -1;
return false;
}
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = mWakeFd;
if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeFd, &ev) < 0) {
perror("epoll_ctl");
close(mWakeFd);
mWakeFd = -1;
close(mEpollFd);
mEpollFd = -1;
return false;
}
mRunning.store(true, std::memory_order_release);
mThread = std::thread(&ProcessManager::runLoop, this);
return true;
}
void ProcessManager::shutdown() {
if (!mRunning.exchange(false, std::memory_order_acq_rel)) {
return;
}
wake();
if (mThread.joinable()) {
mThread.join();
}
std::lock_guard lk(m);
mReg.clear();
if (mWakeFd >= 0) {
close(mWakeFd);
mWakeFd = -1;
}
if (mEpollFd >= 0) {
close(mEpollFd);
mEpollFd = -1;
}
}
bool ProcessManager::addProcess(Pin<ProcessObject> po) {
if (!po) {
return false;
}
int pidfd;
{
std::lock_guard lk(po->m);
pidfd = po->pidfd;
if (pidfd < 0) {
return false;
}
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = pidfd;
if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, pidfd, &ev) < 0) {
perror("epoll_ctl");
close(pidfd);
po->pidfd = -1;
return false;
}
}
static bool hasDirectoryComponent(const std::string &command) {
return command.find('/') != std::string::npos || command.find('\\') != std::string::npos ||
command.find(':') != std::string::npos;
{
std::lock_guard lk(m);
mReg.emplace(pidfd, std::move(po));
}
wake();
return true;
}
static bool hasExtension(const std::string &command) {
auto pos = command.find_last_of('.');
auto slash = command.find_last_of("/\\");
return pos != std::string::npos && (slash == std::string::npos || pos > slash + 1);
}
static std::vector<std::string> pathextValues() {
const char *envValue = std::getenv("PATHEXT");
std::string raw = envValue ? envValue : ".COM;.EXE;.BAT;.CMD";
std::vector<std::string> exts;
size_t start = 0;
while (start <= raw.size()) {
size_t end = raw.find(';', start);
if (end == std::string::npos) {
end = raw.size();
void ProcessManager::runLoop() {
constexpr int kMaxEvents = 64;
std::array<epoll_event, kMaxEvents> events{};
while (mRunning.load(std::memory_order_acquire)) {
int n = epoll_wait(mEpollFd, events.data(), kMaxEvents, -1);
if (n < 0) {
if (errno == EINTR) {
continue;
}
std::string part = raw.substr(start, end - start);
if (!part.empty()) {
if (part[0] != '.') {
part.insert(part.begin(), '.');
perror("epoll_wait");
break;
}
for (int i = 0; i < n; ++i) {
const auto &ev = events[i];
if (ev.data.fd == mWakeFd) {
// Drain eventfd
uint64_t n;
while (read(mWakeFd, &n, sizeof(n)) == sizeof(n)) {
}
exts.push_back(part);
continue;
}
if (end == raw.size()) {
break;
}
start = end + 1;
checkPidfd(ev.data.fd);
}
if (exts.empty()) {
exts = {".COM", ".EXE", ".BAT", ".CMD"};
}
}
void ProcessManager::wake() const {
if (mWakeFd < 0) {
return;
}
uint64_t n = 1;
write(mWakeFd, &n, sizeof(n));
}
void ProcessManager::checkPidfd(int pidfd) {
siginfo_t si{};
si.si_code = CLD_DUMPED;
if (pidfd >= 0) {
int rc = waitid(P_PIDFD, pidfd, &si, WEXITED | WNOHANG);
if (rc < 0) {
// TODO: what to do here?
perror("waitid");
} else if (rc == 0 && si.si_pid == 0) {
return;
}
return exts;
epoll_ctl(mEpollFd, EPOLL_CTL_DEL, pidfd, nullptr);
close(pidfd);
}
static std::vector<std::filesystem::path> parseHostPath(const std::string &value) {
std::vector<std::filesystem::path> paths;
const char *delims = strchr(value.c_str(), ';') ? ";" : ":";
size_t start = 0;
while (start <= value.size()) {
size_t end = value.find_first_of(delims, start);
if (end == std::string::npos) {
end = value.size();
}
std::string entry = value.substr(start, end - start);
if (!entry.empty()) {
bool looksWindows = entry.find('\\') != std::string::npos ||
(entry.size() >= 2 && entry[1] == ':' && entry[0] != '/');
std::filesystem::path candidate;
if (looksWindows) {
auto converted = files::pathFromWindows(entry.c_str());
if (!converted.empty()) {
candidate = converted;
}
}
if (candidate.empty()) {
candidate = std::filesystem::path(entry);
}
paths.push_back(std::move(candidate));
}
if (end == value.size()) {
break;
}
start = end + 1;
Pin<ProcessObject> po;
{
std::shared_lock lk(m);
auto it = mReg.find(pidfd);
if (it == mReg.end()) {
return;
}
return paths;
po = it->second.clone();
}
static std::vector<std::filesystem::path> buildSearchDirectories() {
std::vector<std::filesystem::path> dirs;
if (wibo::guestExecutablePath.has_parent_path()) {
dirs.push_back(wibo::guestExecutablePath.parent_path());
{
std::lock_guard lk(po->m);
po->signaled.store(true, std::memory_order_release);
po->pidfd = -1;
if (!po->forcedExitCode) {
po->exitCode = decodeExitCode(si);
}
dirs.push_back(std::filesystem::current_path());
if (const char *envPath = std::getenv("PATH")) {
auto parsed = parseHostPath(envPath);
dirs.insert(dirs.end(), parsed.begin(), parsed.end());
}
return dirs;
}
po->cv.notify_all();
std::optional<std::filesystem::path> resolveExecutable(const std::string &command, bool searchPath) {
if (command.empty()) {
return std::nullopt;
{
std::lock_guard lk(m);
auto it = mReg.find(pidfd);
if (it != mReg.end()) {
mReg.erase(it);
}
}
}
std::vector<std::string> candidates;
candidates.push_back(command);
if (!hasExtension(command)) {
for (const auto &ext : pathextValues()) {
candidates.push_back(command + ext);
}
ProcessManager &processes() {
static ProcessManager mgr;
return mgr;
}
static bool hasDirectoryComponent(const std::string &command) {
return command.find('/') != std::string::npos || command.find('\\') != std::string::npos ||
command.find(':') != std::string::npos;
}
static bool hasExtension(const std::string &command) {
auto pos = command.find_last_of('.');
auto slash = command.find_last_of("/\\");
return pos != std::string::npos && (slash == std::string::npos || pos > slash + 1);
}
static std::vector<std::string> pathextValues() {
const char *envValue = std::getenv("PATHEXT");
std::string raw = envValue ? envValue : ".COM;.EXE;.BAT;.CMD";
std::vector<std::string> exts;
size_t start = 0;
while (start <= raw.size()) {
size_t end = raw.find(';', start);
if (end == std::string::npos) {
end = raw.size();
}
std::string part = raw.substr(start, end - start);
if (!part.empty()) {
if (part[0] != '.') {
part.insert(part.begin(), '.');
}
exts.push_back(part);
}
if (end == raw.size()) {
break;
}
start = end + 1;
}
if (exts.empty()) {
exts = {".COM", ".EXE", ".BAT", ".CMD"};
}
return exts;
}
auto tryResolveDirect = [&](const std::string &name) -> std::optional<std::filesystem::path> {
auto host = files::pathFromWindows(name.c_str());
if (host.empty()) {
std::string normalized = name;
std::replace(normalized.begin(), normalized.end(), '\\', '/');
host = std::filesystem::path(normalized);
}
std::filesystem::path parent = host.parent_path().empty() ? std::filesystem::current_path() : host.parent_path();
std::string filename = host.filename().string();
auto resolved = files::findCaseInsensitiveFile(parent, filename);
if (resolved) {
return files::canonicalPath(*resolved);
}
std::error_code ec;
if (!filename.empty() && std::filesystem::exists(host, ec)) {
return files::canonicalPath(host);
}
return std::nullopt;
};
if (hasDirectoryComponent(command)) {
for (const auto &name : candidates) {
auto resolved = tryResolveDirect(name);
if (resolved) {
return resolved;
static std::vector<std::filesystem::path> parseHostPath(const std::string &value) {
std::vector<std::filesystem::path> paths;
const char *delims = strchr(value.c_str(), ';') ? ";" : ":";
size_t start = 0;
while (start <= value.size()) {
size_t end = value.find_first_of(delims, start);
if (end == std::string::npos) {
end = value.size();
}
std::string entry = value.substr(start, end - start);
if (!entry.empty()) {
bool looksWindows =
entry.find('\\') != std::string::npos || (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/');
std::filesystem::path candidate;
if (looksWindows) {
auto converted = files::pathFromWindows(entry.c_str());
if (!converted.empty()) {
candidate = converted;
}
}
return std::nullopt;
}
if (searchPath) {
auto dirs = buildSearchDirectories();
for (const auto &dir : dirs) {
for (const auto &name : candidates) {
auto resolved = files::findCaseInsensitiveFile(dir, name);
if (resolved) {
return files::canonicalPath(*resolved);
}
}
if (candidate.empty()) {
candidate = std::filesystem::path(entry);
}
paths.push_back(std::move(candidate));
}
if (end == value.size()) {
break;
}
start = end + 1;
}
return paths;
}
static std::vector<std::filesystem::path> buildSearchDirectories() {
std::vector<std::filesystem::path> dirs;
if (wibo::guestExecutablePath.has_parent_path()) {
dirs.push_back(wibo::guestExecutablePath.parent_path());
}
dirs.push_back(std::filesystem::current_path());
if (const char *envPath = std::getenv("PATH")) {
auto parsed = parseHostPath(envPath);
dirs.insert(dirs.end(), parsed.begin(), parsed.end());
}
return dirs;
}
std::optional<std::filesystem::path> resolveExecutable(const std::string &command, bool searchPath) {
if (command.empty()) {
return std::nullopt;
}
static int spawnInternal(const std::vector<std::string> &args, pid_t *pidOut) {
std::vector<char *> argv;
argv.reserve(args.size() + 2);
argv.push_back(const_cast<char *>(wibo::executableName.c_str()));
for (auto &arg : args) {
argv.push_back(const_cast<char *>(arg.c_str()));
std::vector<std::string> candidates;
candidates.push_back(command);
if (!hasExtension(command)) {
for (const auto &ext : pathextValues()) {
candidates.push_back(command + ext);
}
argv.push_back(nullptr);
DEBUG_LOG("Spawning process: %s, args: [", wibo::executableName.c_str());
for (size_t i = 0; i < args.size(); ++i) {
if (i != 0) {
DEBUG_LOG(", ");
}
DEBUG_LOG("'%s'", args[i].c_str());
}
DEBUG_LOG("]\n");
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
std::string indent = std::to_string(wibo::debugIndent + 1);
setenv("WIBO_DEBUG_INDENT", indent.c_str(), 1);
pid_t pid = -1;
int spawnResult = posix_spawn(&pid, wibo::executableName.c_str(), &actions, nullptr, argv.data(), environ);
posix_spawn_file_actions_destroy(&actions);
if (spawnResult != 0) {
return spawnResult;
}
if (pidOut) {
*pidOut = pid;
}
return 0;
}
int spawnWithCommandLine(const std::string &applicationName, const std::string &commandLine, pid_t *pidOut) {
if (wibo::executableName.empty() || (applicationName.empty() && commandLine.empty())) {
return ENOENT;
auto tryResolveDirect = [&](const std::string &name) -> std::optional<std::filesystem::path> {
auto host = files::pathFromWindows(name.c_str());
if (host.empty()) {
std::string normalized = name;
std::replace(normalized.begin(), normalized.end(), '\\', '/');
host = std::filesystem::path(normalized);
}
std::filesystem::path parent =
host.parent_path().empty() ? std::filesystem::current_path() : host.parent_path();
std::string filename = host.filename().string();
auto resolved = files::findCaseInsensitiveFile(parent, filename);
if (resolved) {
return files::canonicalPath(*resolved);
}
std::error_code ec;
if (!filename.empty() && std::filesystem::exists(host, ec)) {
return files::canonicalPath(host);
}
return std::nullopt;
};
std::vector<std::string> args;
args.reserve(3);
if (!commandLine.empty()) {
args.emplace_back("--cmdline");
args.push_back(commandLine);
if (hasDirectoryComponent(command)) {
for (const auto &name : candidates) {
auto resolved = tryResolveDirect(name);
if (resolved) {
return resolved;
}
}
if (!applicationName.empty()) {
args.push_back(applicationName);
}
return spawnInternal(args, pidOut);
return std::nullopt;
}
int spawnWithArgv(const std::string &applicationName, const std::vector<std::string> &argv, pid_t *pidOut) {
if (wibo::executableName.empty() || (applicationName.empty() && argv.empty())) {
return ENOENT;
if (searchPath) {
auto dirs = buildSearchDirectories();
for (const auto &dir : dirs) {
for (const auto &name : candidates) {
auto resolved = files::findCaseInsensitiveFile(dir, name);
if (resolved) {
return files::canonicalPath(*resolved);
}
}
}
std::vector<std::string> args;
args.reserve(argv.size() + 1);
if (!applicationName.empty()) {
args.push_back(applicationName);
}
args.emplace_back("--");
for (const auto &arg : argv) {
args.push_back(arg);
}
return spawnInternal(args, pidOut);
}
std::vector<std::string> splitCommandLine(const char *commandLine) {
std::vector<std::string> result;
if (!commandLine) {
return result;
return std::nullopt;
}
static int spawnInternal(const std::vector<std::string> &args, Pin<kernel32::ProcessObject> &pinOut) {
std::vector<char *> argv;
argv.reserve(args.size() + 2);
argv.push_back(const_cast<char *>(wibo::executableName.c_str()));
for (auto &arg : args) {
argv.push_back(const_cast<char *>(arg.c_str()));
}
argv.push_back(nullptr);
DEBUG_LOG("Spawning process: %s, args: [", wibo::executableName.c_str());
for (size_t i = 0; i < args.size(); ++i) {
if (i != 0) {
DEBUG_LOG(", ");
}
std::string input(commandLine);
size_t i = 0;
size_t len = input.size();
while (i < len) {
while (i < len && (input[i] == ' ' || input[i] == '\t')) {
++i;
}
if (i >= len) {
break;
}
std::string arg;
bool inQuotes = false;
int backslashes = 0;
for (; i < len; ++i) {
char c = input[i];
if (c == '\\') {
++backslashes;
continue;
}
if (c == '"') {
if ((backslashes % 2) == 0) {
arg.append(backslashes / 2, '\\');
inQuotes = !inQuotes;
} else {
arg.append(backslashes / 2, '\\');
arg.push_back('"');
}
backslashes = 0;
continue;
}
arg.append(backslashes, '\\');
backslashes = 0;
if (!inQuotes && (c == ' ' || c == '\t')) {
break;
}
arg.push_back(c);
}
arg.append(backslashes, '\\');
result.push_back(std::move(arg));
while (i < len && (input[i] == ' ' || input[i] == '\t')) {
++i;
}
DEBUG_LOG("'%s'", args[i].c_str());
}
DEBUG_LOG("]\n");
std::vector<char *> childEnv;
for (char **e = environ; *e != nullptr; ++e) {
if (strncmp(*e, "WIBO_DEBUG_INDENT=", 18) != 0) {
childEnv.push_back(*e);
}
}
std::string indent = "WIBO_DEBUG_INDENT=" + std::to_string(wibo::debugIndent + 1);
childEnv.push_back(strdup(indent.c_str()));
childEnv.push_back(nullptr);
pid_t pid = -1;
int spawnResult = posix_spawn(&pid, wibo::executableName.c_str(), nullptr, nullptr, argv.data(), childEnv.data());
if (spawnResult != 0) {
return spawnResult;
}
DEBUG_LOG("Spawned process with PID %d\n", pid);
int pidfd = static_cast<int>(syscall(SYS_pidfd_open, pid, 0));
if (pidfd < 0) {
perror("pidfd_open");
return false;
}
pinOut = make_pin<kernel32::ProcessObject>(pid, pidfd);
return 0;
}
int spawnWithCommandLine(const std::string &applicationName, const std::string &commandLine,
Pin<kernel32::ProcessObject> &pinOut) {
if (wibo::executableName.empty() || (applicationName.empty() && commandLine.empty())) {
return ENOENT;
}
std::vector<std::string> args;
args.reserve(3);
if (!commandLine.empty()) {
args.emplace_back("--cmdline");
args.push_back(commandLine);
}
if (!applicationName.empty()) {
args.push_back(applicationName);
}
return spawnInternal(args, pinOut);
}
int spawnWithArgv(const std::string &applicationName, const std::vector<std::string> &argv,
Pin<kernel32::ProcessObject> &pinOut) {
if (wibo::executableName.empty() || (applicationName.empty() && argv.empty())) {
return ENOENT;
}
std::vector<std::string> args;
args.reserve(argv.size() + 1);
if (!applicationName.empty()) {
args.push_back(applicationName);
}
args.emplace_back("--");
for (const auto &arg : argv) {
args.push_back(arg);
}
return spawnInternal(args, pinOut);
}
std::vector<std::string> splitCommandLine(const char *commandLine) {
std::vector<std::string> result;
if (!commandLine) {
return result;
}
std::string input(commandLine);
size_t i = 0;
size_t len = input.size();
while (i < len) {
while (i < len && (input[i] == ' ' || input[i] == '\t')) {
++i;
}
if (i >= len) {
break;
}
std::string arg;
bool inQuotes = false;
int backslashes = 0;
for (; i < len; ++i) {
char c = input[i];
if (c == '\\') {
++backslashes;
continue;
}
if (c == '"') {
if ((backslashes % 2) == 0) {
arg.append(backslashes / 2, '\\');
inQuotes = !inQuotes;
} else {
arg.append(backslashes / 2, '\\');
arg.push_back('"');
}
backslashes = 0;
continue;
}
arg.append(backslashes, '\\');
backslashes = 0;
if (!inQuotes && (c == ' ' || c == '\t')) {
break;
}
arg.push_back(c);
}
arg.append(backslashes, '\\');
result.push_back(std::move(arg));
while (i < len && (input[i] == ' ' || input[i] == '\t')) {
++i;
}
}
return result;
}
} // namespace wibo

View File

@ -1,25 +1,45 @@
#pragma once
#include <cstdint>
#include "dll/kernel32/internal.h"
#include <filesystem>
#include <optional>
#include <sched.h>
#include <string>
#include <thread>
#include <vector>
namespace processes {
struct Process {
pid_t pid;
uint32_t exitCode;
uint32_t forcedExitCode;
bool terminationRequested;
};
using kernel32::ProcessObject;
void *allocProcessHandle(pid_t pid);
Process* processFromHandle(void* hHandle, bool pop);
namespace wibo {
std::optional<std::filesystem::path> resolveExecutable(const std::string &command, bool searchPath);
int spawnWithCommandLine(const std::string &applicationName, const std::string &commandLine, pid_t *pidOut);
int spawnWithArgv(const std::string &applicationName, const std::vector<std::string> &argv, pid_t *pidOut);
std::vector<std::string> splitCommandLine(const char *commandLine);
}
class ProcessManager {
public:
~ProcessManager();
bool init();
void shutdown();
bool addProcess(Pin<ProcessObject> po);
bool running() const { return mRunning.load(std::memory_order_acquire); }
private:
void runLoop();
void wake() const;
void checkPidfd(int pidfd);
mutable std::shared_mutex m;
std::atomic<bool> mRunning = false;
std::thread mThread;
int mEpollFd = -1;
int mWakeFd = -1;
std::unordered_map<int, Pin<ProcessObject>> mReg;
};
ProcessManager &processes();
std::optional<std::filesystem::path> resolveExecutable(const std::string &command, bool searchPath);
int spawnWithCommandLine(const std::string &applicationName, const std::string &commandLine,
Pin<kernel32::ProcessObject> &pinOut);
int spawnWithArgv(const std::string &applicationName, const std::vector<std::string> &argv,
Pin<kernel32::ProcessObject> &pinOut);
std::vector<std::string> splitCommandLine(const char *commandLine);
} // namespace wibo

View File

@ -50,7 +50,7 @@ int main(void) {
SetLastError(0);
TEST_CHECK(!HeapDestroy(processHeap));
TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError());
TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError());
return EXIT_SUCCESS;
}

View File

@ -109,9 +109,7 @@ static void test_getoverlappedresult_pending(void) {
DWORD transferred = 0;
TEST_CHECK(!GetOverlappedResult(NULL, &ov, &transferred, FALSE));
TEST_CHECK_EQ(ERROR_IO_INCOMPLETE, GetLastError());
// Wine leaves the caller-supplied transfer count untouched for the
// pending case, so we avoid asserting on the value here.
// TEST_CHECK_EQ(42U, transferred);
TEST_CHECK_EQ(0U, transferred); // No update if the operation is still pending
}
static void test_overlapped_write(void) {

View File

@ -93,6 +93,7 @@ static void test_file_mapping(void) {
TEST_CHECK(UnmapViewOfFile(view));
TEST_CHECK(CloseHandle(mapping));
TEST_CHECK(CloseHandle(file));
TEST_CHECK(GetFileAttributesA("test_virtualquery.tmp") == INVALID_FILE_ATTRIBUTES);
}
int main(void) {