diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index f3fc490..b3052c2 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -7,34 +7,34 @@ namespace advapi32 { return 1; // screw them for now } - bool WIN_FUNC CryptReleaseContext(void* hProv, unsigned int dwFlags) { + BOOL WIN_FUNC CryptReleaseContext(void* hProv, unsigned int dwFlags) { DEBUG_LOG("STUB: CryptReleaseContext %p %u\n", hProv, dwFlags); - return true; + return TRUE; } - bool WIN_FUNC CryptAcquireContextW(void** phProv, const wchar_t* pszContainer, const wchar_t* pszProvider, unsigned int dwProvType, unsigned int dwFlags){ + BOOL WIN_FUNC CryptAcquireContextW(void** phProv, const wchar_t* pszContainer, const wchar_t* pszProvider, unsigned int dwProvType, unsigned int dwFlags){ DEBUG_LOG("STUB: CryptAcquireContextW(%p)\n", phProv); // to quote the guy above me: screw them for now static int lmao = 42; if (phProv) { *phProv = &lmao; - return true; + return TRUE; } - return false; + return FALSE; } - bool WIN_FUNC CryptGenRandom(void* hProv, unsigned int dwLen, unsigned char* pbBuffer){ + BOOL WIN_FUNC CryptGenRandom(void* hProv, unsigned int dwLen, unsigned char* pbBuffer){ DEBUG_LOG("STUB: CryptGenRandom(%p)\n", hProv); - if (!pbBuffer || dwLen == 0) return false; + if (!pbBuffer || dwLen == 0) return FALSE; ssize_t ret = getrandom(pbBuffer, dwLen, 0); if (ret < 0 || (size_t)ret != dwLen) { - return false; + return FALSE; } - return true; + return TRUE; } } diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 91f38a5..ba3409d 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "strutil.h" #include #include @@ -305,90 +306,73 @@ namespace kernel32 { }; - BOOL WIN_FUNC CreateProcessA( + BOOL WIN_FUNC CreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, - void *lpProcessAttributes, - void *lpThreadAttributes, + void *lpProcessAttributes, + void *lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, - void *lpStartupInfo, + void *lpStartupInfo, PROCESS_INFORMATION *lpProcessInformation - ) { + ) { DEBUG_LOG("CreateProcessA %s \"%s\" %p %p %d 0x%x %p %s %p %p\n", - lpApplicationName, - lpCommandLine, - lpProcessAttributes, + lpApplicationName ? lpApplicationName : "", + lpCommandLine ? lpCommandLine : "", + lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory ? lpCurrentDirectory : "", - lpStartupInfo, - lpProcessInformation - ); + lpStartupInfo, + lpProcessInformation + ); - // Argument parsing - // First: how many arguments do we have? - size_t argc = 2; - - for (size_t i = 1; i < strlen(lpCommandLine); i++) { - if (isspace(lpCommandLine[i]) && !isspace(lpCommandLine[i - 1])) - argc++; - } - - char **argv = (char **) calloc(argc + 1, sizeof(char*)); - argv[0] = wibo::executableName; - std::string pathStr = files::pathFromWindows(lpApplicationName).string(); - argv[1] = (char *) pathStr.c_str(); - - char* arg = strtok(lpCommandLine, " "); - size_t current_arg_index = 2; - - while (arg != NULL) { - // We're deliberately discarding the first token here - // to prevent from doubling up on the target executable name - // (it appears as lpApplicationName, and as the first token in lpCommandLine) - arg = strtok(NULL, " "); - - if (arg) { - // Trim all quotation marks from the start and the end of the string - while(*arg == '\"') { - arg++; - } - - char* end = arg + strlen(arg) - 1; - while(end > arg && *end == '\"') { - *end = '\0'; - end--; - } + std::string application = lpApplicationName ? lpApplicationName : ""; + std::vector arguments = processes::splitCommandLine(lpCommandLine); + if (application.empty()) { + if (arguments.empty()) { + wibo::lastError = ERROR_FILE_NOT_FOUND; + return 0; } - - argv[current_arg_index++] = arg; + application = arguments.front(); + } + if (arguments.empty()) { + arguments.push_back(application); } - argv[argc] = NULL; // Last element in argv should be a null pointer - - // YET TODO: take into account process / thread attributes, environment variables - // working directory, etc. - setenv("WIBO_DEBUG_INDENT", std::to_string(wibo::debugIndent + 1).c_str(), true); - - pid_t pid; - if (posix_spawn(&pid, wibo::executableName, NULL, NULL, argv, environ)) { + auto resolved = processes::resolveExecutable(application, true); + if (!resolved) { + wibo::lastError = ERROR_FILE_NOT_FOUND; return 0; - }; + } - *lpProcessInformation = { - .hProcess = processes::allocProcessHandle(pid), - .hThread = nullptr, - .dwProcessId = (DWORD) pid, - .dwThreadId = 42 - }; + pid_t pid = -1; + int spawnResult = processes::spawnViaWibo(*resolved, arguments, &pid); + if (spawnResult != 0) { + wibo::lastError = (spawnResult == ENOENT) ? ERROR_FILE_NOT_FOUND : ERROR_ACCESS_DENIED; + return 0; + } + if (lpProcessInformation) { + lpProcessInformation->hProcess = processes::allocProcessHandle(pid); + lpProcessInformation->hThread = nullptr; + lpProcessInformation->dwProcessId = static_cast(pid); + lpProcessInformation->dwThreadId = 0; + } + wibo::lastError = ERROR_SUCCESS; + (void)lpProcessAttributes; + (void)lpThreadAttributes; + (void)bInheritHandles; + (void)dwCreationFlags; + (void)lpEnvironment; + (void)lpCurrentDirectory; + (void)lpStartupInfo; return 1; - } + } unsigned int WIN_FUNC WaitForSingleObject(void *hHandle, unsigned int dwMilliseconds) { DEBUG_LOG("WaitForSingleObject (%u)\n", dwMilliseconds); @@ -1918,6 +1902,15 @@ namespace kernel32 { return wibo::loadModule(lpLibFileName); } + HMODULE WIN_FUNC LoadLibraryW(LPCWSTR lpLibFileName) { + DEBUG_LOG("LoadLibraryW\n"); + if (!lpLibFileName) { + return nullptr; + } + auto filename = wideStringToString(lpLibFileName); + return LoadLibraryA(filename.c_str()); + } + HMODULE WIN_FUNC LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { assert(!hFile); DEBUG_LOG("LoadLibraryExW(%x) -> ", dwFlags); @@ -2554,35 +2547,74 @@ namespace kernel32 { return 0; // fail } + + static std::string convertEnvValueForWindows(const std::string &name, const char *rawValue) { + if (!rawValue) { + return std::string(); + } + if (strcasecmp(name.c_str(), "PATH") != 0) { + return rawValue; + } + std::string converted = files::hostPathListToWindows(rawValue); + return converted.empty() ? std::string(rawValue) : converted; + } + + static std::string convertEnvValueToHost(const std::string &name, const char *rawValue) { + if (!rawValue) { + return std::string(); + } + if (strcasecmp(name.c_str(), "PATH") != 0) { + return rawValue; + } + std::string converted = files::windowsPathListToHost(rawValue); + return converted.empty() ? std::string(rawValue) : converted; + } + DWORD WIN_FUNC GetEnvironmentVariableA(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize) { DEBUG_LOG("GetEnvironmentVariableA: %s\n", lpName); - const char *value = getenv(lpName); - if (!value) { + if (!lpName) { return 0; } - unsigned int len = strlen(value); + const char *rawValue = getenv(lpName); + if (!rawValue) { + return 0; + } + std::string converted = convertEnvValueForWindows(lpName, rawValue); + const std::string &finalValue = converted.empty() ? std::string(rawValue) : converted; + unsigned int len = finalValue.size(); if (nSize == 0) { return len + 1; } - if (nSize < len) { + if (nSize <= len) { return len; } - memcpy(lpBuffer, value, len + 1); + memcpy(lpBuffer, finalValue.c_str(), len + 1); return len; } unsigned int WIN_FUNC SetEnvironmentVariableA(const char *lpName, const char *lpValue) { - DEBUG_LOG("SetEnvironmentVariableA: %s=%s\n", lpName, lpValue); - return setenv(lpName, lpValue, 1 /* OVERWRITE */); + DEBUG_LOG("SetEnvironmentVariableA: %s=%s\n", lpName, lpValue ? lpValue : ""); + if (!lpName) { + return 0; + } + if (!lpValue) { + return unsetenv(lpName); + } + std::string hostValue = convertEnvValueToHost(lpName, lpValue); + const char *valuePtr = hostValue.empty() ? lpValue : hostValue.c_str(); + return setenv(lpName, valuePtr, 1 /* OVERWRITE */); } DWORD WIN_FUNC GetEnvironmentVariableW(LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize) { - DEBUG_LOG("GetEnvironmentVariableW: %s\n", wideStringToString(lpName).c_str()); - const char *value = getenv(wideStringToString(lpName).c_str()); - if (!value) { + std::string name = wideStringToString(lpName); + DEBUG_LOG("GetEnvironmentVariableW: %s\n", name.c_str()); + const char *rawValue = getenv(name.c_str()); + if (!rawValue) { return 0; } - auto wideValue = stringToWideString(value); + std::string converted = convertEnvValueForWindows(name, rawValue); + const std::string &finalValue = converted.empty() ? std::string(rawValue) : converted; + auto wideValue = stringToWideString(finalValue.c_str()); const auto len = wideValue.size(); if (nSize < len) { return len; @@ -2908,6 +2940,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "LockResource") == 0) return (void *) kernel32::LockResource; if (strcmp(name, "SizeofResource") == 0) return (void *) kernel32::SizeofResource; if (strcmp(name, "LoadLibraryA") == 0) return (void *) kernel32::LoadLibraryA; + if (strcmp(name, "LoadLibraryW") == 0) return (void *) kernel32::LoadLibraryW; if (strcmp(name, "LoadLibraryExW") == 0) return (void *) kernel32::LoadLibraryExW; if (strcmp(name, "DisableThreadLibraryCalls") == 0) return (void *) kernel32::DisableThreadLibraryCalls; if (strcmp(name, "FreeLibrary") == 0) return (void *) kernel32::FreeLibrary; diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 57c8498..7dbd1ec 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -11,19 +11,27 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include +#include +#include +#include "files.h" +#include "processes.h" #include "strutil.h" typedef void (*_PVFV)(); typedef int (*_PIFV)(); using _onexit_t = _PIFV; +extern "C" char **environ; + namespace msvcrt { int _commode; int _fmode; @@ -66,6 +74,60 @@ namespace msvcrt { return tables.back(); } + std::string normalizeEnvStringForWindows(const char *src) { + if (!src) { + return std::string(); + } + std::string entry(src); + auto pos = entry.find('='); + if (pos == std::string::npos) { + return entry; + } + std::string name = entry.substr(0, pos); + std::string value = entry.substr(pos + 1); + if (strcasecmp(name.c_str(), "PATH") == 0) { + std::string converted = files::hostPathListToWindows(value); + std::string result = converted.empty() ? value : converted; + std::string exeDir; + if (wibo::argv && wibo::argv[0]) { + std::filesystem::path exePath = std::filesystem::absolute(std::filesystem::path(wibo::argv[0])).parent_path(); + if (!exePath.empty()) { + exeDir = files::pathToWindows(exePath); + } + } + if (!exeDir.empty()) { + std::string loweredResult = result; + std::string loweredExe = exeDir; + std::transform(loweredResult.begin(), loweredResult.end(), loweredResult.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::transform(loweredExe.begin(), loweredExe.end(), loweredExe.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + bool present = false; + size_t start = 0; + while (start <= loweredResult.size()) { + size_t end = loweredResult.find(';', start); + if (end == std::string::npos) { + end = loweredResult.size(); + } + if (loweredResult.substr(start, end - start) == loweredExe) { + present = true; + break; + } + if (end == loweredResult.size()) { + break; + } + start = end + 1; + } + if (!present) { + if (!result.empty() && result.back() != ';') { + result.push_back(';'); + } + result += exeDir; + } + } + entry = name + "=" + result; + } + return entry; + } + template struct StringListStorage { std::vector> strings; @@ -103,23 +165,19 @@ namespace msvcrt { }; std::vector copyNarrowString(const char *src) { - if (!src) { - src = ""; - } - size_t len = std::strlen(src); + std::string normalized = normalizeEnvStringForWindows(src); + size_t len = normalized.size(); std::vector result(len + 1); if (len > 0) { - std::memcpy(result.data(), src, len); + std::memcpy(result.data(), normalized.data(), len); } result[len] = '\0'; return result; } std::vector copyWideString(const char *src) { - if (!src) { - src = ""; - } - return stringToWideString(src); + std::string normalized = normalizeEnvStringForWindows(src); + return stringToWideString(normalized.c_str()); } template @@ -427,6 +485,90 @@ namespace msvcrt { std::free(ptr); } + void* WIN_ENTRY memcpy(void *dest, const void *src, size_t count) { + return std::memcpy(dest, src, count); + } + + void* WIN_ENTRY memmove(void *dest, const void *src, size_t count) { + return std::memmove(dest, src, count); + } + + int WIN_ENTRY fflush(FILE *stream) { + return std::fflush(stream); + } + + FILE *WIN_ENTRY fopen(const char *filename, const char *mode) { + return std::fopen(filename, mode); + } + + int WIN_ENTRY _dup2(int fd1, int fd2) { + return dup2(fd1, fd2); + } + + int WIN_ENTRY _isatty(int fd) { + return isatty(fd); + } + + int WIN_ENTRY fseek(FILE *stream, long offset, int origin) { + return std::fseek(stream, offset, origin); + } + + long WIN_ENTRY ftell(FILE *stream) { + return std::ftell(stream); + } + + int WIN_ENTRY feof(FILE *stream) { + return std::feof(stream); + } + + int WIN_ENTRY fputws(const uint16_t *str, FILE *stream) { + std::wstring temp; + if (str) { + for (const uint16_t *cursor = str; *cursor; ++cursor) { + temp.push_back(static_cast(*cursor)); + } + } + return std::fputws(temp.c_str(), stream); + } + + uint16_t* WIN_ENTRY fgetws(uint16_t *buffer, int size, FILE *stream) { + if (!buffer || size <= 0) { + return nullptr; + } + std::vector temp(static_cast(size)); + wchar_t *res = std::fgetws(temp.data(), size, stream); + if (!res) { + return nullptr; + } + for (int i = 0; i < size; ++i) { + buffer[i] = static_cast(temp[i]); + if (temp[i] == L'\0') { + break; + } + } + return buffer; + } + + wint_t WIN_ENTRY fgetwc(FILE *stream) { + return std::fgetwc(stream); + } + + int WIN_ENTRY _wfopen_s(FILE **stream, const uint16_t *filename, const uint16_t *mode) { + if (!stream || !filename || !mode) { + errno = EINVAL; + return EINVAL; + } + std::string narrowName = wideStringToString(filename); + std::string narrowMode = wideStringToString(mode); + FILE *handle = std::fopen(narrowName.c_str(), narrowMode.c_str()); + if (!handle) { + *stream = nullptr; + return errno ? errno : EINVAL; + } + *stream = handle; + return 0; + } + static uint16_t toLower(uint16_t ch) { if (ch >= 'A' && ch <= 'Z') { return static_cast(ch + ('a' - 'A')); @@ -463,6 +605,234 @@ namespace msvcrt { return static_cast(a) - static_cast(b); } + int WIN_ENTRY _wmakepath_s(uint16_t *path, size_t sizeInWords, const uint16_t *drive, const uint16_t *dir, + const uint16_t *fname, const uint16_t *ext) { + if (!path || sizeInWords == 0) { + return EINVAL; + } + + path[0] = 0; + std::u16string result; + + auto append = [&](const uint16_t *src) { + if (!src || !*src) { + return; + } + for (const uint16_t *cursor = src; *cursor; ++cursor) { + result.push_back(static_cast(*cursor)); + } + }; + + if (drive && *drive) { + result.push_back(static_cast(drive[0])); + if (drive[1] == u':') { + result.push_back(u':'); + append(drive + 2); + } else { + result.push_back(u':'); + append(drive + 1); + } + } + + auto appendDir = [&](const uint16_t *directory) { + if (!directory || !*directory) { + return; + } + append(directory); + if (result.empty()) { + return; + } + char16_t last = result.back(); + if (last != u'/' && last != u'\\') { + result.push_back(u'\\'); + } + }; + + appendDir(dir); + append(fname); + + if (ext && *ext) { + if (*ext != u'.') { + result.push_back(u'.'); + append(ext); + } else { + append(ext); + } + } + + size_t required = result.size() + 1; + if (required > sizeInWords) { + path[0] = 0; + return ERANGE; + } + + for (size_t i = 0; i < result.size(); ++i) { + path[i] = static_cast(result[i]); + } + path[result.size()] = 0; + return 0; + } + + int WIN_ENTRY _wputenv_s(const uint16_t *varname, const uint16_t *value) { + if (!varname || !value) { + errno = EINVAL; + return EINVAL; + } + + if (!*varname) { + errno = EINVAL; + return EINVAL; + } + + for (const uint16_t *cursor = varname; *cursor; ++cursor) { + if (*cursor == static_cast('=')) { + errno = EINVAL; + return EINVAL; + } + } + + std::string name = wideStringToString(varname); + if (name.empty()) { + errno = EINVAL; + return EINVAL; + } + + int resultCode = 0; + if (!*value) { + if (unsetenv(name.c_str()) != 0) { + resultCode = errno != 0 ? errno : EINVAL; + } + } else { + std::string narrowValue = wideStringToString(value); + if (setenv(name.c_str(), narrowValue.c_str(), 1) != 0) { + resultCode = errno != 0 ? errno : EINVAL; + } + } + + if (resultCode != 0) { + errno = resultCode; + return resultCode; + } + + getMainArgsCommon(nullptr, nullptr, nullptr, copyNarrowString); + getMainArgsCommon(nullptr, nullptr, nullptr, copyWideString); + return 0; + } + + unsigned long WIN_ENTRY wcsspn(const uint16_t *str1, const uint16_t *str2) { + if (!str1 || !str2) { + return 0; + } + unsigned long count = 0; + for (const uint16_t *p = str1; *p; ++p) { + bool match = false; + for (const uint16_t *q = str2; *q; ++q) { + if (*p == *q) { + match = true; + break; + } + } + if (!match) { + break; + } + ++count; + } + return count; + } + + long WIN_ENTRY _wtol(const uint16_t *str) { + return wstrtol(str, nullptr, 10); + } + + int WIN_ENTRY _wcsupr_s(uint16_t *str, size_t size) { + if (!str || size == 0) { + return EINVAL; + } + size_t len = wstrnlen(str, size); + if (len >= size) { + return ERANGE; + } + for (size_t i = 0; i < len; ++i) { + wchar_t ch = static_cast(str[i]); + str[i] = static_cast(std::towupper(ch)); + } + return 0; + } + + int WIN_ENTRY _wcslwr_s(uint16_t *str, size_t size) { + if (!str || size == 0) { + return EINVAL; + } + size_t len = wstrnlen(str, size); + if (len >= size) { + return ERANGE; + } + for (size_t i = 0; i < len; ++i) { + wchar_t ch = static_cast(str[i]); + str[i] = static_cast(std::towlower(ch)); + } + return 0; + } + + wint_t WIN_ENTRY towlower(wint_t ch) { + return static_cast(std::towlower(static_cast(ch))); + } + + int WIN_ENTRY _ftime64_s(void *timeb) { + DEBUG_LOG("STUB: _ftime64_s(%p)\n", timeb); + return 0; + } + + int WIN_ENTRY _crt_debugger_hook(int value) { + DEBUG_LOG("_crt_debugger_hook(%d)\n", value); + (void)value; + return 0; + } + + int WIN_ENTRY _configthreadlocale(int mode) { + static int currentMode = 0; + int previous = currentMode; + if (mode == -1) { + return previous; + } + if (mode == 0 || mode == 1 || mode == 2) { + currentMode = mode; + return previous; + } + errno = EINVAL; + return -1; + } + + static void abort_and_log(const char *reason) { + DEBUG_LOG("Runtime abort: %s\n", reason ? reason : ""); + std::abort(); + } + + int WIN_ENTRY _amsg_exit(int reason) { + DEBUG_LOG("_amsg_exit(%d)\n", reason); + abort_and_log("_amsg_exit"); + return reason; + } + + void WIN_ENTRY _invoke_watson(const uint16_t *, const uint16_t *, const uint16_t *, unsigned int, uintptr_t) { + DEBUG_LOG("_invoke_watson\n"); + abort_and_log("_invoke_watson"); + } + + void WIN_ENTRY terminateShim() { + abort_and_log("terminate"); + } + + int WIN_ENTRY _except_handler4_common(void *, void *, void *, void *) { + DEBUG_LOG("_except_handler4_common\n"); + return 0; + } + + long WIN_ENTRY _XcptFilter(unsigned long code, void *) { + DEBUG_LOG("_XcptFilter(%lu)\n", code); + return 0; + } + int WIN_ENTRY _get_wpgmptr(uint16_t** pValue){ DEBUG_LOG("_get_wpgmptr(%p)\n", pValue); if(!pValue) return 22; @@ -576,9 +946,17 @@ namespace msvcrt { } int WIN_ENTRY _waccess_s(const uint16_t* path, int mode){ - std::string str = wideStringToString(path); - DEBUG_LOG("_waccess_s %s\n", str.c_str()); - return access(str.c_str(), mode); + std::string original = wideStringToString(path); + DEBUG_LOG("_waccess_s %s\n", original.c_str()); + std::filesystem::path host = files::pathFromWindows(original.c_str()); + std::string candidate; + if (!host.empty()) { + candidate = host.string(); + } else { + candidate = original; + std::replace(candidate.begin(), candidate.end(), '\\', '/'); + } + return access(candidate.c_str(), mode); } void* WIN_ENTRY memset(void *s, int c, size_t n){ @@ -744,29 +1122,16 @@ namespace msvcrt { return wstrtoul(strSource, endptr, base); } - int WIN_ENTRY _dup2(int fd1, int fd2){ - return dup2(fd1, fd2); - } - FILE* WIN_ENTRY _wfsopen(const uint16_t* filename, const uint16_t* mode, int shflag){ if (!filename || !mode) return nullptr; std::string fname_str = wideStringToString(filename); std::string mode_str = wideStringToString(mode); DEBUG_LOG("_wfsopen file %s, mode %s\n", fname_str.c_str(), mode_str.c_str()); + (void)shflag; return fopen(fname_str.c_str(), mode_str.c_str()); } - int WIN_ENTRY fputws(const uint16_t* str, FILE* stream){ - if(!str || !stream) return EOF; - - std::string fname_str = wideStringToString(str); - DEBUG_LOG("fputws %s\n", fname_str.c_str()); - - if(fputs(fname_str.c_str(), stream) < 0) return EOF; - else return 0; - } - int WIN_ENTRY puts(const char *str) { if (!str) { str = "(null)"; @@ -787,9 +1152,9 @@ namespace msvcrt { DEBUG_LOG("flushall\n"); int count = 0; - if (fflush(stdin) == 0) count++; - if (fflush(stdout) == 0) count++; - if (fflush(stderr) == 0) count++; + if (msvcrt::fflush(stdin) == 0) count++; + if (msvcrt::fflush(stdout) == 0) count++; + if (msvcrt::fflush(stderr) == 0) count++; return count; } @@ -798,10 +1163,62 @@ namespace msvcrt { return &errno; } - intptr_t WIN_ENTRY _wspawnvp(int mode, const uint16_t* cmdname, const uint16_t* const * argv){ - std::string str_cmd = wideStringToString(cmdname); - DEBUG_LOG("STUB: _wspawnvp %s\n", str_cmd.c_str()); - return -1; + intptr_t WIN_ENTRY _wspawnvp(int mode, const uint16_t* cmdname, const uint16_t* const * argv) { + if (!cmdname || !argv) { + errno = EINVAL; + return -1; + } + + std::string command = wideStringToString(cmdname); + DEBUG_LOG("_wspawnvp(mode=%d, cmd=%s)\n", mode, command.c_str()); + + std::vector argStorage; + for (const uint16_t *const *cursor = argv; *cursor; ++cursor) { + argStorage.emplace_back(wideStringToString(*cursor)); + } + if (argStorage.empty()) { + argStorage.emplace_back(command); + } + + auto resolved = processes::resolveExecutable(command, true); + if (!resolved) { + errno = ENOENT; + DEBUG_LOG("\tfailed to resolve executable for %s\n", command.c_str()); + return -1; + } + + pid_t pid = -1; + int spawnResult = processes::spawnViaWibo(*resolved, argStorage, &pid); + if (spawnResult != 0) { + errno = spawnResult; + DEBUG_LOG("\tspawnViaWibo failed: %d\n", spawnResult); + return -1; + } + + 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(WEXITSTATUS(status)); + } + if (WIFSIGNALED(status)) { + errno = EINTR; + } + return -1; + } + + if (mode == P_DETACH) { + return 0; + } + + // _P_NOWAIT and unknown flags: return process id + return static_cast(pid); } int WIN_ENTRY _wunlink(const uint16_t *filename){ @@ -889,11 +1306,39 @@ static void *resolveByName(const char *name) { if (strcmp(name, "__dllonexit") == 0) return (void*)msvcrt::__dllonexit; if (strcmp(name, "free") == 0) return (void*)msvcrt::free; if (strcmp(name, "_wcsicmp") == 0) return (void*)msvcrt::_wcsicmp; + if (strcmp(name, "_wmakepath_s") == 0) return (void*)msvcrt::_wmakepath_s; + if (strcmp(name, "_wputenv_s") == 0) return (void*)msvcrt::_wputenv_s; if (strcmp(name, "_get_wpgmptr") == 0) return (void*)msvcrt::_get_wpgmptr; if (strcmp(name, "_wsplitpath_s") == 0) return (void*)msvcrt::_wsplitpath_s; if (strcmp(name, "wcscat_s") == 0) return (void*)msvcrt::wcscat_s; if (strcmp(name, "_wcsdup") == 0) return (void*)msvcrt::_wcsdup; if (strcmp(name, "memset") == 0) return (void*)msvcrt::memset; + if (strcmp(name, "memcpy") == 0) return (void*)msvcrt::memcpy; + if (strcmp(name, "memmove") == 0) return (void*)msvcrt::memmove; + if (strcmp(name, "fflush") == 0) return (void*)msvcrt::fflush; + if (strcmp(name, "fopen") == 0) return (void*)msvcrt::fopen; + if (strcmp(name, "fseek") == 0) return (void*)msvcrt::fseek; + if (strcmp(name, "ftell") == 0) return (void*)msvcrt::ftell; + if (strcmp(name, "feof") == 0) return (void*)msvcrt::feof; + if (strcmp(name, "fgetws") == 0) return (void*)msvcrt::fgetws; + if (strcmp(name, "fgetwc") == 0) return (void*)msvcrt::fgetwc; + if (strcmp(name, "fputws") == 0) return (void*)msvcrt::fputws; + if (strcmp(name, "_wfopen_s") == 0) return (void*)msvcrt::_wfopen_s; + if (strcmp(name, "wcsspn") == 0) return (void*)msvcrt::wcsspn; + if (strcmp(name, "_wtol") == 0) return (void*)msvcrt::_wtol; + if (strcmp(name, "_wcsupr_s") == 0) return (void*)msvcrt::_wcsupr_s; + if (strcmp(name, "_wcslwr_s") == 0) return (void*)msvcrt::_wcslwr_s; + if (strcmp(name, "_dup2") == 0) return (void*)msvcrt::_dup2; + if (strcmp(name, "_isatty") == 0) return (void*)msvcrt::_isatty; + if (strcmp(name, "towlower") == 0) return (void*)msvcrt::towlower; + if (strcmp(name, "_ftime64_s") == 0) return (void*)msvcrt::_ftime64_s; + if (strcmp(name, "_crt_debugger_hook") == 0) return (void*)msvcrt::_crt_debugger_hook; + if (strcmp(name, "_configthreadlocale") == 0) return (void*)msvcrt::_configthreadlocale; + if (strcmp(name, "_amsg_exit") == 0) return (void*)msvcrt::_amsg_exit; + if (strcmp(name, "_invoke_watson") == 0) return (void*)msvcrt::_invoke_watson; + if (strcmp(name, "_except_handler4_common") == 0) return (void*)msvcrt::_except_handler4_common; + if (strcmp(name, "_XcptFilter") == 0) return (void*)msvcrt::_XcptFilter; + if (strcmp(name, "?terminate@@YAXXZ") == 0) return (void*)msvcrt::terminateShim; if (strcmp(name, "wcsncpy_s") == 0) return (void*)msvcrt::wcsncpy_s; if (strcmp(name, "wcsncat_s") == 0) return (void*)msvcrt::wcsncat_s; if (strcmp(name, "_itow_s") == 0) return (void*)msvcrt::_itow_s; diff --git a/files.cpp b/files.cpp index 2701d30..579538b 100644 --- a/files.cpp +++ b/files.cpp @@ -2,10 +2,58 @@ #include "files.h" #include "handles.h" #include +#include #include +#include +#include +#include namespace files { + static std::vector splitList(const std::string &value, char delimiter) { + std::vector entries; + size_t start = 0; + while (start <= value.size()) { + size_t end = value.find(delimiter, start); + if (end == std::string::npos) { + end = value.size(); + } + entries.emplace_back(value.substr(start, end - start)); + if (end == value.size()) { + break; + } + start = end + 1; + } + return entries; + } + + static std::string toWindowsPathEntry(const std::string &entry) { + if (entry.empty()) { + return std::string(); + } + bool looksWindows = entry.find('\\') != std::string::npos || + (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/'); + if (looksWindows) { + std::string normalized = entry; + std::replace(normalized.begin(), normalized.end(), '/', '\\'); + return normalized; + } + return pathToWindows(std::filesystem::path(entry)); + } + + static std::string toHostPathEntry(const std::string &entry) { + if (entry.empty()) { + return std::string(); + } + auto converted = pathFromWindows(entry.c_str()); + if (!converted.empty()) { + return converted.string(); + } + std::string normalized = entry; + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + return normalized; + } + static void *stdinHandle; static void *stdoutHandle; static void *stderrHandle; @@ -123,4 +171,76 @@ namespace files { stdoutHandle = allocFpHandle(stdout); stderrHandle = allocFpHandle(stderr); } + + std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, + const std::string &filename) { + std::error_code ec; + if (directory.empty()) { + return std::nullopt; + } + if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { + return std::nullopt; + } + std::string needle = filename; + std::transform(needle.begin(), needle.end(), needle.begin(), [](unsigned char ch) { return std::tolower(ch); }); + for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { + if (ec) { + break; + } + std::string candidate = entry.path().filename().string(); + std::transform(candidate.begin(), candidate.end(), candidate.begin(), [](unsigned char ch) { return std::tolower(ch); }); + if (candidate == needle) { + return canonicalPath(entry.path()); + } + } + auto direct = directory / filename; + if (std::filesystem::exists(direct, ec)) { + return canonicalPath(direct); + } + return std::nullopt; + } + + std::filesystem::path canonicalPath(const std::filesystem::path &path) { + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(path, ec); + if (!ec) { + return canonical; + } + return std::filesystem::absolute(path); + } + + std::string hostPathListToWindows(const std::string &value) { + if (value.empty()) { + return value; + } + char delimiter = value.find(';') != std::string::npos ? ';' : ':'; + auto entries = splitList(value, delimiter); + std::string result; + for (size_t i = 0; i < entries.size(); ++i) { + if (i != 0) { + result.push_back(';'); + } + if (!entries[i].empty()) { + result += toWindowsPathEntry(entries[i]); + } + } + return result; + } + + std::string windowsPathListToHost(const std::string &value) { + if (value.empty()) { + return value; + } + auto entries = splitList(value, ';'); + std::string result; + for (size_t i = 0; i < entries.size(); ++i) { + if (i != 0) { + result.push_back(':'); + } + if (!entries[i].empty()) { + result += toHostPathEntry(entries[i]); + } + } + return result; + } } diff --git a/files.h b/files.h index 74e6d30..329a4d4 100644 --- a/files.h +++ b/files.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace files { @@ -11,6 +12,10 @@ namespace files { void *getStdHandle(uint32_t nStdHandle); unsigned int setStdHandle(uint32_t nStdHandle, void *hHandle); void init(); + std::optional 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); } inline bool endsWith(const std::string &str, const std::string &suffix) { diff --git a/module_registry.cpp b/module_registry.cpp index a040328..481f9cf 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -110,6 +110,8 @@ struct ModuleRegistry { std::optional dllDirectory; bool initialized = false; std::unordered_map onExitTables; + std::unordered_map> builtinAliasLists; + std::unordered_map builtinAliasMap; }; ModuleRegistry ®istry() { @@ -180,32 +182,6 @@ std::string normalizedBaseKey(const ParsedModuleName &parsed) { return normalizeAlias(base); } -std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, - const std::string &filename) { - std::error_code ec; - if (directory.empty()) { - return std::nullopt; - } - if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { - return std::nullopt; - } - const std::string lower = toLowerCopy(filename); - for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { - if (ec) { - break; - } - const std::string candidate = toLowerCopy(entry.path().filename().string()); - if (candidate == lower) { - return std::filesystem::canonical(entry.path(), ec); - } - } - auto direct = directory / filename; - if (std::filesystem::exists(direct, ec)) { - return std::filesystem::canonical(direct, ec); - } - return std::nullopt; -} - std::optional combineAndFind(const std::filesystem::path &directory, const std::string &filename) { if (filename.empty()) { @@ -214,16 +190,7 @@ std::optional combineAndFind(const std::filesystem::path if (directory.empty()) { return std::nullopt; } - return findCaseInsensitiveFile(directory, filename); -} - -std::filesystem::path canonicalPath(const std::filesystem::path &path) { - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(path, ec); - if (!ec) { - return canonical; - } - return std::filesystem::absolute(path); + return files::findCaseInsensitiveFile(directory, filename); } std::vector collectSearchDirectories(bool alteredSearchPath) { @@ -305,11 +272,11 @@ std::optional resolveModuleOnDisk(const std::string &requ for (const auto &candidate : names) { auto combined = parsed.directory + "\\" + candidate; auto posixPath = files::pathFromWindows(combined.c_str()); - if (!posixPath.empty()) { - auto resolved = findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), + if (!posixPath.empty()) { + auto resolved = files::findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), std::filesystem::path(posixPath).filename().string()); if (resolved) { - return canonicalPath(*resolved); + return files::canonicalPath(*resolved); } } } @@ -321,7 +288,7 @@ std::optional resolveModuleOnDisk(const std::string &requ for (const auto &candidate : names) { auto resolved = combineAndFind(dir, candidate); if (resolved) { - return canonicalPath(*resolved); + return files::canonicalPath(*resolved); } } } @@ -330,7 +297,7 @@ std::optional resolveModuleOnDisk(const std::string &requ } std::string storageKeyForPath(const std::filesystem::path &path) { - return normalizeAlias(files::pathToWindows(canonicalPath(path))); + return normalizeAlias(files::pathToWindows(files::canonicalPath(path))); } std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; } @@ -349,7 +316,13 @@ void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { return; } auto ® = registry(); - if (reg.modulesByAlias.find(alias) == reg.modulesByAlias.end()) { + auto it = reg.modulesByAlias.find(alias); + if (it == reg.modulesByAlias.end()) { + reg.modulesByAlias[alias] = info; + return; + } + // Prefer externally loaded modules over built-ins when both are present. + if (it->second && it->second->module != nullptr && info->module == nullptr) { reg.modulesByAlias[alias] = info; } } @@ -369,10 +342,20 @@ void registerBuiltinModule(const wibo::Module *module) { auto ® = registry(); reg.modulesByKey[storageKey] = std::move(entry); + reg.builtinAliasLists[module] = {}; + auto &aliasList = reg.builtinAliasLists[module]; for (size_t i = 0; module->names[i]; ++i) { - registerAlias(normalizeAlias(module->names[i]), raw); + std::string alias = normalizeAlias(module->names[i]); + aliasList.push_back(alias); + registerAlias(alias, raw); + reg.builtinAliasMap[alias] = raw; ParsedModuleName parsed = parseModuleName(module->names[i]); - registerAlias(normalizedBaseKey(parsed), raw); + std::string baseAlias = normalizedBaseKey(parsed); + if (baseAlias != alias) { + aliasList.push_back(baseAlias); + registerAlias(baseAlias, raw); + reg.builtinAliasMap[baseAlias] = raw; + } } } @@ -543,7 +526,7 @@ void shutdownModuleRegistry() { ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast(module); } void setDllDirectoryOverride(const std::filesystem::path &path) { - auto canonical = canonicalPath(path); + auto canonical = files::canonicalPath(path); std::lock_guard lock(registry().mutex); registry().dllDirectory = canonical; } @@ -645,76 +628,119 @@ HMODULE loadModule(const char *dllName) { ensureInitialized(); ParsedModuleName parsed = parseModuleName(requested); + + auto ® = registry(); + DWORD diskError = ERROR_SUCCESS; + + auto tryLoadExternal = [&](const std::filesystem::path &path) -> ModuleInfo * { + std::string key = storageKeyForPath(path); + auto existingIt = reg.modulesByKey.find(key); + if (existingIt != reg.modulesByKey.end()) { + ModuleInfo *info = existingIt->second.get(); + if (info->refCount != UINT_MAX) { + info->refCount++; + } + registerExternalModuleAliases(requested, files::canonicalPath(path), info); + return info; + } + + FILE *file = fopen(path.c_str(), "rb"); + if (!file) { + perror("loadModule"); + diskError = ERROR_MOD_NOT_FOUND; + return nullptr; + } + + auto executable = std::make_unique(); + if (!executable->loadPE(file, true)) { + DEBUG_LOG(" loadPE failed for %s\n", path.c_str()); + fclose(file); + diskError = ERROR_BAD_EXE_FORMAT; + return nullptr; + } + fclose(file); + + ModulePtr info = std::make_unique(); + info->module = nullptr; + info->originalName = requested; + info->normalizedName = normalizedBaseKey(parsed); + info->resolvedPath = files::canonicalPath(path); + info->executable = std::move(executable); + info->entryPoint = info->executable->entryPoint; + info->imageBase = info->executable->imageBuffer; + info->imageSize = info->executable->imageSize; + info->refCount = 1; + info->dataFile = false; + info->dontResolveReferences = false; + + ModuleInfo *raw = info.get(); + reg.modulesByKey[key] = std::move(info); + registerExternalModuleAliases(requested, raw->resolvedPath, raw); + ensureExportsInitialized(*raw); + callDllMain(*raw, DLL_PROCESS_ATTACH); + return raw; + }; + + auto resolveAndLoadExternal = [&]() -> ModuleInfo * { + auto resolvedPath = resolveModuleOnDisk(requested, false); + if (!resolvedPath) { + DEBUG_LOG(" module not found on disk\n"); + return nullptr; + } + return tryLoadExternal(*resolvedPath); + }; + std::string alias = normalizedBaseKey(parsed); ModuleInfo *existing = findByAlias(alias); if (!existing) { existing = findByAlias(normalizeAlias(requested)); } if (existing) { - DEBUG_LOG(" found existing module alias %s\n", alias.c_str()); - if (existing->refCount != UINT_MAX) { - existing->refCount++; + DEBUG_LOG(" found existing module alias %s (builtin=%d)\n", alias.c_str(), existing->module != nullptr); + if (existing->module == nullptr) { + if (existing->refCount != UINT_MAX) { + existing->refCount++; + } + DEBUG_LOG(" returning existing external module %s\n", existing->originalName.c_str()); + lastError = ERROR_SUCCESS; + return existing; + } + if (ModuleInfo *external = resolveAndLoadExternal()) { + DEBUG_LOG(" replaced builtin module %s with external copy\n", requested.c_str()); + lastError = ERROR_SUCCESS; + return external; } lastError = ERROR_SUCCESS; + DEBUG_LOG(" returning builtin module %s\n", existing->originalName.c_str()); return existing; } - auto resolvedPath = resolveModuleOnDisk(requested, false); - if (!resolvedPath) { - DEBUG_LOG(" module not found on disk\n"); - lastError = ERROR_MOD_NOT_FOUND; - return nullptr; - } - - std::string key = storageKeyForPath(*resolvedPath); - auto ® = registry(); - auto it = reg.modulesByKey.find(key); - if (it != reg.modulesByKey.end()) { - ModuleInfo *info = it->second.get(); - info->refCount++; - registerExternalModuleAliases(requested, *resolvedPath, info); + if (ModuleInfo *external = resolveAndLoadExternal()) { + DEBUG_LOG(" loaded external module %s\n", requested.c_str()); lastError = ERROR_SUCCESS; - return info; + return external; } - FILE *file = fopen(resolvedPath->c_str(), "rb"); - if (!file) { - perror("loadModule"); - lastError = ERROR_MOD_NOT_FOUND; - return nullptr; + auto fallbackAlias = normalizedBaseKey(parsed); + ModuleInfo *builtin = nullptr; + auto builtinIt = reg.builtinAliasMap.find(fallbackAlias); + if (builtinIt != reg.builtinAliasMap.end()) { + builtin = builtinIt->second; + } + if (!builtin) { + builtinIt = reg.builtinAliasMap.find(normalizeAlias(requested)); + if (builtinIt != reg.builtinAliasMap.end()) { + builtin = builtinIt->second; + } + } + if (builtin && builtin->module != nullptr) { + DEBUG_LOG(" falling back to builtin module %s\n", builtin->originalName.c_str()); + lastError = (diskError != ERROR_SUCCESS) ? diskError : ERROR_SUCCESS; + return builtin; } - auto executable = std::make_unique(); - if (!executable->loadPE(file, true)) { - DEBUG_LOG(" loadPE failed for %s\n", resolvedPath->c_str()); - fclose(file); - lastError = ERROR_BAD_EXE_FORMAT; - return nullptr; - } - fclose(file); - - ModulePtr info = std::make_unique(); - info->module = nullptr; - info->originalName = requested; - info->normalizedName = alias; - info->resolvedPath = *resolvedPath; - info->executable = std::move(executable); - info->entryPoint = info->executable->entryPoint; - info->imageBase = info->executable->imageBuffer; - info->imageSize = info->executable->imageSize; - info->refCount = 1; - info->dataFile = false; - info->dontResolveReferences = false; - - ModuleInfo *raw = info.get(); - reg.modulesByKey[key] = std::move(info); - registerExternalModuleAliases(requested, *resolvedPath, raw); - ensureExportsInitialized(*raw); - - callDllMain(*raw, DLL_PROCESS_ATTACH); - lastError = ERROR_SUCCESS; - - return raw; + lastError = (diskError != ERROR_SUCCESS) ? diskError : ERROR_MOD_NOT_FOUND; + return nullptr; } void freeModule(HMODULE module) { diff --git a/processes.cpp b/processes.cpp index 902acaa..838534e 100644 --- a/processes.cpp +++ b/processes.cpp @@ -1,7 +1,21 @@ #include "processes.h" +#include "common.h" +#include "files.h" #include "handles.h" +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" char **environ; namespace processes { void *allocProcessHandle(pid_t pid) { @@ -21,4 +35,233 @@ namespace processes { assert(0); } } -} \ No newline at end of file + + 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 pathextValues() { + const char *envValue = std::getenv("PATHEXT"); + std::string raw = envValue ? envValue : ".COM;.EXE;.BAT;.CMD"; + std::vector 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; + } + + static std::vector parseHostPath(const std::string &value) { + std::vector 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; + } + return paths; + } + + static std::vector buildSearchDirectories() { + std::vector dirs; + 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 resolveExecutable(const std::string &command, bool searchPath) { + if (command.empty()) { + return std::nullopt; + } + + std::vector candidates; + candidates.push_back(command); + if (!hasExtension(command)) { + for (const auto &ext : pathextValues()) { + candidates.push_back(command + ext); + } + } + + auto tryResolveDirect = [&](const std::string &name) -> std::optional { + 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; + } + } + 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); + } + } + } + } + + return std::nullopt; + } + + int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector &arguments, pid_t *pidOut) { + if (hostExecutable.empty()) { + return ENOENT; + } + + std::vector storage; + storage.reserve(arguments.size() + 1); + storage.push_back(hostExecutable.string()); + for (const auto &arg : arguments) { + storage.push_back(arg); + } + + std::vector nativeArgs; + nativeArgs.reserve(storage.size() + 2); + nativeArgs.push_back(wibo::executableName); + for (auto &entry : storage) { + nativeArgs.push_back(entry.data()); + } + nativeArgs.push_back(nullptr); + + 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, &actions, nullptr, nativeArgs.data(), environ); + posix_spawn_file_actions_destroy(&actions); + if (spawnResult != 0) { + return spawnResult; + } + if (pidOut) { + *pidOut = pid; + } + return 0; + } + + std::vector splitCommandLine(const char *commandLine) { + std::vector 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; + } +} diff --git a/processes.h b/processes.h index 0e6ecca..d4519e8 100644 --- a/processes.h +++ b/processes.h @@ -1,7 +1,11 @@ #pragma once #include +#include +#include #include +#include +#include namespace processes { struct Process { @@ -11,4 +15,8 @@ namespace processes { void *allocProcessHandle(pid_t pid); Process* processFromHandle(void* hHandle, bool pop); + + std::optional resolveExecutable(const std::string &command, bool searchPath); + int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector &arguments, pid_t *pidOut); + std::vector splitCommandLine(const char *commandLine); }