From 787bdba0ecb36f6bb46ea72db5c8b326d417801c Mon Sep 17 00:00:00 2001 From: Tim Angus Date: Tue, 12 Aug 2025 19:26:36 +0100 Subject: [PATCH] Rewrite the LCC process spawning code on Windows, to handle file names containing spaces --- code/tools/lcc/etc/lcc.c | 103 +++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/code/tools/lcc/etc/lcc.c b/code/tools/lcc/etc/lcc.c index 1ff51c59..b608887f 100644 --- a/code/tools/lcc/etc/lcc.c +++ b/code/tools/lcc/etc/lcc.c @@ -221,64 +221,81 @@ char *basename(char *name) { } #ifdef WIN32 -#include +#include -static char *escapeDoubleQuotes(const char *string) { - int stringLength = strlen(string); - int bufferSize = stringLength + 1; - int i, j; - char *newString; +static char *quoteArgument(const char *arg) { + // Quote if it has spaces, tabs, or is empty + if(!*arg || strpbrk(arg, " \t\"")) { + size_t length = strlen(arg); + size_t bufferSize = length * 2 + 3; // maximum escapes + quotes + terminator + char *buffer = (char *)malloc(bufferSize); + char *p = buffer; - if (string == NULL) - return NULL; + *p++ = '"'; // Open quote - for (i = 0; i < stringLength; i++) { - if (string[i] == '"') - bufferSize++; + for(size_t i = 0; i < length; i++) { + if(arg[i] == '"') { + // Escape quotes + *p++ = '\\'; + *p++ = '"'; + } else { + // Everything else + *p++ = arg[i]; + } + } + + *p++ = '"'; // Close quote + *p = '\0'; + + return buffer; } - newString = (char*)malloc(bufferSize); - - if (newString == NULL) - return NULL; - - for (i = 0, j = 0; i < stringLength; i++) { - if (string[i] == '"') - newString[j++] = '\\'; - - newString[j++] = string[i]; - } - - newString[j] = '\0'; - - return newString; + // Duping to make memory management easier + return _strdup(arg); } static int spawn(const char *cmdname, char **argv) { - int argc = 0; - char **newArgv = argv; - int i; - intptr_t exitStatus; + size_t totalLength = 0; + for(int i = 0; argv[i] != NULL; i++) { + char *quotedArg = quoteArgument(argv[i]); + totalLength += strlen(quotedArg) + 1; + free(quotedArg); + } - // _spawnvp removes double quotes from arguments, so we - // have to escape them manually - while (*newArgv++ != NULL) - argc++; + char *cmdline = (char *)malloc(totalLength + 1); + cmdline[0] = '\0'; - newArgv = (char **)malloc(sizeof(char*) * (argc + 1)); + for(int i = 0; argv[i] != NULL; i++) { + char *quotedArg = quoteArgument(argv[i]); + strcat(cmdline, quotedArg); + if(argv[i+1]) strcat(cmdline, " "); + free(quotedArg); + } - for (i = 0; i < argc; i++) - newArgv[i] = escapeDoubleQuotes(argv[i]); + STARTUPINFOA si = { sizeof(si) }; + PROCESS_INFORMATION pi; + BOOL result = CreateProcessA( + cmdname, cmdline, + NULL, NULL, FALSE, + 0, NULL, NULL, + &si, &pi); - newArgv[argc] = NULL; + if(!result) { + fprintf(stderr, "CreateProcess failed (%lu)\n", GetLastError()); + free(cmdline); + return -1; + } - exitStatus = _spawnvp(_P_WAIT, cmdname, (const char *const *)newArgv); + WaitForSingleObject(pi.hProcess, INFINITE); - for (i = 0; i < argc; i++) - free(newArgv[i]); + DWORD exit_code; + GetExitCodeProcess(pi.hProcess, &exit_code); - free(newArgv); - return exitStatus; + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + free(cmdline); + + return (int)exit_code; } #else