diff --git a/AGENTS.md b/AGENTS.md index c2cbcf3..ad50cfa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ ## Build, Test, and Development Commands - `cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` configures a 32-bit toolchain; ensure multilib packages are present. - `cmake --build build --target wibo` compiles the shim; switch to `-DCMAKE_BUILD_TYPE=Release` for optimised binaries. -- `./build/wibo /path/to/program.exe` runs a Windows binary through the shim; use `WIBO_DEBUG=1` for verbose logging. +- `./build/wibo /path/to/program.exe` runs a Windows binary. Use `WIBO_DEBUG=1` (or `--debug`/`-D`) for verbose logging. Use `--chdir`/`-C` to set the working directory. - `cmake -B build -DBUILD_TESTING=ON` + `ctest --test-dir build --output-on-failure` runs the self-checking WinAPI fixtures (requires `i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). - `clang-format -i path/to/file.cpp` and `clang-tidy path/to/file.cpp -p build` keep contributions aligned with the repo's tooling. - DON'T use `clang-format` on existing files, only new or heavily modified ones; the repo hasn't been fully formatted yet. diff --git a/README.md b/README.md index 93857be..e980d20 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,16 @@ cmake --build build --target wibo ## Running ```sh -./build/wibo /path/to/program.exe -# or, with debug logging: -WIBO_DEBUG=1 ./build/wibo /path/to/program.exe +./build/wibo /path/to/program.exe [arguments...] ``` +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. + ## Tests Self-checking Windows fixtures run through CTest. They require a 32-bit MinGW cross toolchain (`i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). @@ -40,8 +45,6 @@ This will cross-compile the fixture executables, run them through `wibo`, and fa Rough to-do list: - Implement more APIs -- Do something intelligent with Windows `HANDLE`s -- Convert paths in environment variables (and the structure of `PATH` itself, maybe) to Windows format --- diff --git a/main.cpp b/main.cpp index 3fcd2e5..c50b286 100644 --- a/main.cpp +++ b/main.cpp @@ -3,12 +3,14 @@ #include "strutil.h" #include #include +#include #include #include #include #include #include #include +#include #include uint32_t wibo::lastError = 0; @@ -84,6 +86,18 @@ TIB tib; const size_t MAPS_BUFFER_SIZE = 0x10000; +static void printHelp(const char *argv0) { + std::filesystem::path exePath(argv0 ? argv0 : "wibo"); + std::string exeName = exePath.filename().string(); + fprintf(stdout, "Usage: %s [options] [arguments...]\n", exeName.c_str()); + fprintf(stdout, "\n"); + fprintf(stdout, "Options:\n"); + 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"); +} + /** * Read /proc/self/maps into a buffer. * @@ -186,17 +200,73 @@ static void blockUpper2GB() { } int main(int argc, char **argv) { - if (argc <= 1) { - printf("Usage: ./wibo program.exe ...\n"); - return 1; + std::string chdirPath; + bool optionDebug = false; + bool parsingOptions = true; + int programIndex = -1; + + for (int i = 1; i < argc; ++i) { + const char *arg = argv[i]; + if (parsingOptions) { + if (strcmp(arg, "--") == 0) { + parsingOptions = false; + continue; + } + if (strcmp(arg, "--help") == 0) { + printHelp(argv[0]); + return 0; + } + if (strcmp(arg, "-D") == 0 || strcmp(arg, "--debug") == 0) { + optionDebug = true; + continue; + } + if (strncmp(arg, "--chdir=", 8) == 0) { + chdirPath = arg + 8; + continue; + } + if (strcmp(arg, "-C") == 0 || strcmp(arg, "--chdir") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "Option %s requires a directory argument\n", arg); + return 1; + } + chdirPath = argv[++i]; + continue; + } + if (strncmp(arg, "-C", 2) == 0 && arg[2] != '\0') { + chdirPath = arg + 2; + continue; + } + if (arg[0] == '-' && arg[1] != '\0') { + fprintf(stderr, "Unknown option: %s\n", arg); + fprintf(stderr, "\n"); + printHelp(argv[0]); + return 1; + } + } + + programIndex = i; + break; } - if (getenv("WIBO_DEBUG")) { + if (programIndex == -1) { + printHelp(argv[0]); + return argc <= 1 ? 0 : 1; + } + + if (!chdirPath.empty()) { + if (chdir(chdirPath.c_str()) != 0) { + std::string message = std::string("Failed to chdir to ") + chdirPath; + perror(message.c_str()); + return 1; + } + } + + if (optionDebug || getenv("WIBO_DEBUG")) { wibo::debugEnabled = true; } - if (getenv("WIBO_DEBUG_INDENT")) { - wibo::debugIndent = std::stoul(getenv("WIBO_DEBUG_INDENT")); + if (const char *debugIndentEnv = getenv("WIBO_DEBUG_INDENT")) { + wibo::debugIndent = std::stoul(debugIndentEnv); } blockUpper2GB(); @@ -226,15 +296,18 @@ int main(int argc, char **argv) { wibo::tibSelector = static_cast((tibDesc.entry_number << 3) | 7); + char **guestArgv = argv + programIndex; + int guestArgc = argc - programIndex; + // Build a command line std::string cmdLine; - for (int i = 1; i < argc; i++) { + for (int i = 0; i < guestArgc; ++i) { std::string arg; - if (i == 1) { - arg = files::pathToWindows(std::filesystem::absolute(argv[1])); + if (i == 0) { + arg = files::pathToWindows(std::filesystem::absolute(guestArgv[0])); } else { cmdLine += ' '; - arg = argv[i]; + arg = guestArgv[i]; } bool needQuotes = arg.find_first_of("\\\" \t\n") != std::string::npos; if (needQuotes) @@ -271,15 +344,15 @@ int main(int argc, char **argv) { DEBUG_LOG("Command line: %s\n", wibo::commandLine); wibo::executableName = argv[0]; - wibo::argv = argv + 1; - wibo::argc = argc - 1; + wibo::argv = guestArgv; + wibo::argc = guestArgc; wibo::initializeModuleRegistry(); wibo::Executable exec; wibo::mainModule = &exec; - char* pe_path = argv[1]; + char* pe_path = guestArgv[0]; FILE *f = fopen(pe_path, "rb"); if (!f) { std::string mesg = std::string("Failed to open file ") + pe_path;