From 7f9d141a20068184585a7d38bfadf3cf28d331d8 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 29 Sep 2025 22:05:33 -0600 Subject: [PATCH] Add --cmdline arg; rework wibo subprocess spawn --- README.md | 5 +- dll/kernel32.cpp | 13 ++--- dll/msvcrt.cpp | 24 +++------ main.cpp | 130 ++++++++++++++++++++++++++++++----------------- processes.cpp | 67 ++++++++++++++++-------- processes.h | 3 +- 6 files changed, 144 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index b7a2257..7fa5d71 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ Supported command line options: - `--help`: Print usage information. - `-D`, `--debug`: Enable shim debug logging (equivalent to `WIBO_DEBUG=1`). -- `-C DIR`, `--chdir DIR`, `--chdir=DIR`: Change to `DIR` before running the program. -- `--`: Stop option parsing; following arguments are interpreted as the program command line. +- `-C DIR`, `--chdir DIR`, `--chdir=DIR`: Change to `DIR` before running the guest program. +- `--cmdline STRING`, `--cmdline=STRING`: Use `STRING` as the exact guest command line. (Including the program name as the first argument.) +- `--`: Stop option parsing; following arguments are interpreted as the exact guest command line. (Including the program name as the first argument.) ## Tests diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index bf7ef29..7bc8159 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -682,24 +682,17 @@ namespace kernel32 { bool useSearchPath = lpApplicationName == nullptr; std::string application; - std::vector arguments = processes::splitCommandLine(lpCommandLine); + std::string commandLine = lpCommandLine ? lpCommandLine : ""; if (lpApplicationName) { application = lpApplicationName; } else { + std::vector arguments = processes::splitCommandLine(commandLine.c_str()); if (arguments.empty()) { wibo::lastError = ERROR_FILE_NOT_FOUND; return 0; } application = arguments.front(); } - if (arguments.empty()) { - arguments.push_back(application); - } - DEBUG_LOG(" -> args:"); - for (const auto &arg : arguments) { - DEBUG_LOG(" '%s'", arg.c_str()); - } - DEBUG_LOG("\n"); auto resolved = processes::resolveExecutable(application, useSearchPath); if (!resolved) { @@ -708,7 +701,7 @@ namespace kernel32 { } pid_t pid = -1; - int spawnResult = processes::spawnViaWibo(*resolved, arguments, &pid); + int spawnResult = processes::spawnWithCommandLine(*resolved, commandLine, &pid); if (spawnResult != 0) { wibo::lastError = (spawnResult == ENOENT) ? ERROR_FILE_NOT_FOUND : ERROR_ACCESS_DENIED; return 0; diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 8702194..e6d6dab 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -2688,16 +2688,10 @@ namespace msvcrt { DEBUG_LOG("_wspawnvp(%d, %s)\n", mode, command.c_str()); std::vector argStorage; + argStorage.emplace_back(command); for (const uint16_t *const *cursor = argv; *cursor; ++cursor) { argStorage.emplace_back(wideStringToString(*cursor)); } - if (argStorage.empty()) { - argStorage.emplace_back(command); - } - DEBUG_LOG("-> argv:"); - for (const auto &arg : argStorage) { - DEBUG_LOG(" '%s'", arg.c_str()); - } auto resolved = processes::resolveExecutable(command, false); if (!resolved) { @@ -2708,10 +2702,10 @@ namespace msvcrt { DEBUG_LOG("-> resolved to %s\n", resolved->c_str()); pid_t pid = -1; - int spawnResult = processes::spawnViaWibo(*resolved, argStorage, &pid); + int spawnResult = processes::spawnWithArgv(*resolved, argStorage, &pid); if (spawnResult != 0) { errno = spawnResult; - DEBUG_LOG("-> spawnViaWibo failed: %d\n", spawnResult); + DEBUG_LOG("-> spawnWithArgv failed: %d\n", spawnResult); return -1; } DEBUG_LOG("-> spawned pid %d\n", pid); @@ -2752,16 +2746,10 @@ namespace msvcrt { DEBUG_LOG("_spawnvp(%d, %s)\n", mode, command.c_str()); std::vector argStorage; + argStorage.emplace_back(command); for (const char * const *cursor = argv; *cursor; ++cursor) { argStorage.emplace_back(*cursor); } - if (argStorage.empty()) { - argStorage.emplace_back(command); - } - DEBUG_LOG("-> argv:"); - for (const auto &arg : argStorage) { - DEBUG_LOG(" '%s'", arg.c_str()); - } auto resolved = processes::resolveExecutable(command, false); if (!resolved) { @@ -2772,10 +2760,10 @@ namespace msvcrt { DEBUG_LOG("-> resolved to %s\n", resolved->c_str()); pid_t pid = -1; - int spawnResult = processes::spawnViaWibo(*resolved, argStorage, &pid); + int spawnResult = processes::spawnWithArgv(*resolved, argStorage, &pid); if (spawnResult != 0) { errno = spawnResult; - DEBUG_LOG("-> spawnViaWibo failed: %d\n", spawnResult); + DEBUG_LOG("-> spawnWithArgv failed: %d\n", spawnResult); return -1; } DEBUG_LOG("-> spawned pid %d\n", pid); diff --git a/main.cpp b/main.cpp index 817ee9d..2068855 100644 --- a/main.cpp +++ b/main.cpp @@ -98,7 +98,9 @@ static void printHelp(const char *argv0) { fprintf(stdout, " --help\t\tShow this help message and exit\n"); fprintf(stdout, " -C, --chdir DIR\tChange working directory before launching the program\n"); fprintf(stdout, " -D, --debug\tEnable shim debug logging (same as WIBO_DEBUG=1)\n"); - fprintf(stdout, " --\t\tStop option parsing; following arguments are interpreted as the program command line\n"); + fprintf(stdout, " --cmdline STRING\tUse STRING as the exact guest command line\n"); + fprintf(stdout, + " --\t\tStop option parsing; following arguments are interpreted as the exact guest command line\n"); } /** @@ -208,6 +210,7 @@ int main(int argc, char **argv) { bool optionDebug = false; bool parsingOptions = true; int programIndex = -1; + std::string cmdLine; for (int i = 1; i < argc; ++i) { const char *arg = argv[i]; @@ -216,6 +219,18 @@ int main(int argc, char **argv) { parsingOptions = false; continue; } + if (strncmp(arg, "--cmdline=", 10) == 0) { + cmdLine = arg + 10; + continue; + } + if (strcmp(arg, "--cmdline") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "Option %s requires a command line argument\n", arg); + return 1; + } + cmdLine = argv[++i]; + continue; + } if (strcmp(arg, "--help") == 0) { printHelp(argv[0]); return 0; @@ -252,7 +267,7 @@ int main(int argc, char **argv) { break; } - if (programIndex == -1) { + if (programIndex == -1 && cmdLine.empty()) { printHelp(argv[0]); return argc <= 1 ? 0 : 1; } @@ -312,61 +327,76 @@ int main(int argc, char **argv) { wibo::tibSelector = static_cast((tibDesc.entry_number << 3) | 7); - char **guestArgv = argv + programIndex; - int guestArgc = argc - programIndex; - - const char *pePath = guestArgv[0]; - if (!pePath || pePath[0] == '\0') { - fprintf(stderr, "No guest binary specified\n"); + // Determine the guest program name + auto guestArgs = processes::splitCommandLine(cmdLine.c_str()); + std::string programName; + if (programIndex != -1) { + programName = argv[programIndex]; + } else if (!guestArgs.empty()) { + programName = guestArgs[0]; + } + if (programName.empty()) { + fprintf(stderr, "No guest program specified\n"); return 1; } - std::string originalName = pePath; - std::filesystem::path resolvedGuestPath = processes::resolveExecutable(originalName, true).value_or({}); + // Resolve the guest program path + std::filesystem::path resolvedGuestPath = processes::resolveExecutable(programName, true).value_or({}); if (resolvedGuestPath.empty()) { - fprintf(stderr, "Failed to resolve path to guest binary %s\n", originalName.c_str()); + fprintf(stderr, "Failed to resolve path to guest program %s\n", programName.c_str()); return 1; } + // Build guest arguments + if (guestArgs.empty()) { + guestArgs.push_back(files::pathToWindows(resolvedGuestPath)); + } + for (int i = programIndex + 1; i < argc; ++i) { + guestArgs.emplace_back(argv[i]); + } + // Build a command line - std::string cmdLine; - for (int i = 0; i < guestArgc; ++i) { - std::string arg; - if (i == 0) { - arg = files::pathToWindows(resolvedGuestPath); - } else { - cmdLine += ' '; - arg = guestArgv[i]; - } - bool needQuotes = arg.find_first_of("\" \t\n") != std::string::npos; - if (needQuotes) - cmdLine += '"'; - int backslashes = 0; - for (const char *p = arg.c_str();; p++) { - char c = *p; - if (c == '\\') { - backslashes++; - continue; + if (cmdLine.empty()) { + for (int i = 0; i < guestArgs.size(); ++i) { + std::string arg; + if (i == 0) { + arg = files::pathToWindows(resolvedGuestPath); + } else { + cmdLine += ' '; + arg = guestArgs[i]; } + bool needQuotes = arg.find_first_of("\" \t\n") != std::string::npos; + if (needQuotes) + cmdLine += '"'; + int backslashes = 0; + for (const char *p = arg.c_str();; p++) { + char c = *p; + if (c == '\\') { + backslashes++; + continue; + } - // Backslashes are doubled *before quotes* - for (int j = 0; j < backslashes; j++) { - cmdLine += '\\'; - if (c == '\0' || c == '"') + // Backslashes are doubled *before quotes* + for (int j = 0; j < backslashes; j++) { cmdLine += '\\'; - } - backslashes = 0; + if (c == '\0' || c == '"') + cmdLine += '\\'; + } + backslashes = 0; - if (c == '\0') - break; - if (c == '\"') - cmdLine += '\\'; - cmdLine += c; + if (c == '\0') + break; + if (c == '\"') + cmdLine += '\\'; + cmdLine += c; + } + if (needQuotes) + cmdLine += '"'; } - if (needQuotes) - cmdLine += '"'; } - cmdLine += '\0'; + if (cmdLine.empty() || cmdLine.back() != '\0') { + cmdLine.push_back('\0'); + } wibo::commandLine = cmdLine; wibo::commandLineW = stringToWideString(wibo::commandLine.c_str()); @@ -374,8 +404,16 @@ int main(int argc, char **argv) { wibo::guestExecutablePath = resolvedGuestPath; wibo::executableName = executablePath; - wibo::argv = guestArgv; - wibo::argc = guestArgc; + + // Build argv/argc + std::vector guestArgv; + guestArgv.reserve(guestArgs.size() + 1); + for (const auto &arg : guestArgs) { + guestArgv.push_back(const_cast(arg.c_str())); + } + guestArgv.push_back(nullptr); + wibo::argv = guestArgv.data(); + wibo::argc = static_cast(guestArgv.size()) - 1; wibo::initializeModuleRegistry(); @@ -401,7 +439,7 @@ int main(int argc, char **argv) { } wibo::mainModule = - wibo::registerProcessModule(std::move(executable), std::move(resolvedGuestPath), std::move(originalName)); + wibo::registerProcessModule(std::move(executable), std::move(resolvedGuestPath), std::move(programName)); if (!wibo::mainModule || !wibo::mainModule->executable) { fprintf(stderr, "Failed to register process module\n"); return 1; diff --git a/processes.cpp b/processes.cpp index f084e50..d7e05c2 100644 --- a/processes.cpp +++ b/processes.cpp @@ -181,32 +181,21 @@ namespace processes { return std::nullopt; } - int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector &arguments, pid_t *pidOut) { - if (hostExecutable.empty()) { - return ENOENT; + static int spawnInternal(const std::vector &args, pid_t *pidOut) { + std::vector argv; + argv.reserve(args.size() + 2); + argv.push_back(const_cast(wibo::executableName.c_str())); + for (auto &arg : args) { + argv.push_back(const_cast(arg.c_str())); } - - 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(const_cast(wibo::executableName.c_str())); - for (auto &entry : storage) { - nativeArgs.push_back(const_cast(entry.c_str())); - } - nativeArgs.push_back(nullptr); + argv.push_back(nullptr); DEBUG_LOG("Spawning process: %s, args: [", wibo::executableName.c_str()); - for (size_t i = 0; i < storage.size(); ++i) { + for (size_t i = 0; i < args.size(); ++i) { if (i != 0) { DEBUG_LOG(", "); } - DEBUG_LOG("'%s'", storage[i].c_str()); + DEBUG_LOG("'%s'", args[i].c_str()); } DEBUG_LOG("]\n"); @@ -217,7 +206,7 @@ namespace processes { setenv("WIBO_DEBUG_INDENT", indent.c_str(), 1); pid_t pid = -1; - int spawnResult = posix_spawn(&pid, wibo::executableName.c_str(), &actions, nullptr, nativeArgs.data(), environ); + 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; @@ -228,6 +217,42 @@ namespace processes { 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; + } + + std::vector 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, pidOut); + } + + int spawnWithArgv(const std::string &applicationName, const std::vector &argv, pid_t *pidOut) { + if (wibo::executableName.empty() || (applicationName.empty() && argv.empty())) { + return ENOENT; + } + + std::vector 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 splitCommandLine(const char *commandLine) { std::vector result; if (!commandLine) { diff --git a/processes.h b/processes.h index 393ab61..0ff5709 100644 --- a/processes.h +++ b/processes.h @@ -19,6 +19,7 @@ namespace processes { 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); + int spawnWithCommandLine(const std::string &applicationName, const std::string &commandLine, pid_t *pidOut); + int spawnWithArgv(const std::string &applicationName, const std::vector &argv, pid_t *pidOut); std::vector splitCommandLine(const char *commandLine); }