Add --cmdline arg; rework wibo subprocess spawn

This commit is contained in:
Luke Street 2025-09-29 22:05:33 -06:00
parent 9dd65bc70a
commit 7f9d141a20
6 changed files with 144 additions and 98 deletions

View File

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

View File

@ -682,24 +682,17 @@ namespace kernel32 {
bool useSearchPath = lpApplicationName == nullptr;
std::string application;
std::vector<std::string> arguments = processes::splitCommandLine(lpCommandLine);
std::string commandLine = lpCommandLine ? lpCommandLine : "";
if (lpApplicationName) {
application = lpApplicationName;
} else {
std::vector<std::string> 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;

View File

@ -2688,16 +2688,10 @@ namespace msvcrt {
DEBUG_LOG("_wspawnvp(%d, %s)\n", mode, command.c_str());
std::vector<std::string> 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<std::string> 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);

130
main.cpp
View File

@ -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<uint16_t>((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<char *> guestArgv;
guestArgv.reserve(guestArgs.size() + 1);
for (const auto &arg : guestArgs) {
guestArgv.push_back(const_cast<char *>(arg.c_str()));
}
guestArgv.push_back(nullptr);
wibo::argv = guestArgv.data();
wibo::argc = static_cast<int>(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;

View File

@ -181,32 +181,21 @@ namespace processes {
return std::nullopt;
}
int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector<std::string> &arguments, pid_t *pidOut) {
if (hostExecutable.empty()) {
return ENOENT;
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> storage;
storage.reserve(arguments.size() + 1);
storage.push_back(hostExecutable.string());
for (const auto &arg : arguments) {
storage.push_back(arg);
}
std::vector<char *> nativeArgs;
nativeArgs.reserve(storage.size() + 2);
nativeArgs.push_back(const_cast<char *>(wibo::executableName.c_str()));
for (auto &entry : storage) {
nativeArgs.push_back(const_cast<char *>(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<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, pidOut);
}
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;
}
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) {

View File

@ -19,6 +19,7 @@ namespace processes {
Process* processFromHandle(void* hHandle, bool pop);
std::optional<std::filesystem::path> resolveExecutable(const std::string &command, bool searchPath);
int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector<std::string> &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<std::string> &argv, pid_t *pidOut);
std::vector<std::string> splitCommandLine(const char *commandLine);
}