|
6 | 6 | */ |
7 | 7 | #include "foundation/constants.h" |
8 | 8 | #include "foundation/compat_fs.h" |
| 9 | +#include "foundation/compat_fs_internal.h" |
9 | 10 |
|
10 | 11 | #include <stdio.h> |
11 | 12 | #include <stdlib.h> |
@@ -197,11 +198,139 @@ int cbm_rmdir(const char *path) { |
197 | 198 | return ret; |
198 | 199 | } |
199 | 200 |
|
| 201 | +/* Build a properly-quoted Windows command line from an argv array. |
| 202 | + * Returns a heap-allocated wide string, or NULL on allocation failure. |
| 203 | + * Quoting follows the MSVC CRT convention: arguments containing spaces, |
| 204 | + * tabs, or double-quotes are wrapped in double-quotes, with backslashes |
| 205 | + * before a closing quote doubled and the quote itself escaped. Argument |
| 206 | + * bytes are treated as UTF-8 and converted to wide via cbm_utf8_to_wide, |
| 207 | + * so non-ASCII arguments (e.g. a non-ASCII %USERPROFILE%) survive intact. |
| 208 | + * Declared in compat_fs_internal.h so the test suite can drive it. */ |
| 209 | +wchar_t *cbm_build_cmdline(const char *const *argv) { |
| 210 | + /* First pass: compute required buffer size. */ |
| 211 | + size_t total = 1; /* NUL terminator */ |
| 212 | + for (int i = 0; argv[i]; i++) { |
| 213 | + const char *arg = argv[i]; |
| 214 | + bool needs_quote = (arg[0] == '\0'); |
| 215 | + for (const char *p = arg; *p; p++) { |
| 216 | + if (*p == ' ' || *p == '\t' || *p == '"') { |
| 217 | + needs_quote = true; |
| 218 | + } |
| 219 | + } |
| 220 | + if (i > 0) { |
| 221 | + total++; /* space separator */ |
| 222 | + } |
| 223 | + if (needs_quote) { |
| 224 | + total += 2; /* opening and closing quote */ |
| 225 | + size_t backslashes = 0; |
| 226 | + for (const char *p = arg; *p; p++) { |
| 227 | + if (*p == '\\') { |
| 228 | + backslashes++; |
| 229 | + } else if (*p == '"') { |
| 230 | + total += backslashes + 1; /* double backslashes + escape backslash */ |
| 231 | + backslashes = 0; |
| 232 | + } else { |
| 233 | + backslashes = 0; |
| 234 | + } |
| 235 | + total++; |
| 236 | + } |
| 237 | + /* Trailing backslashes before closing quote must be doubled. */ |
| 238 | + total += backslashes; |
| 239 | + } else { |
| 240 | + total += strlen(arg); |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + /* Build the quoted command line in UTF-8 first, then widen it as a |
| 245 | + * whole via cbm_utf8_to_wide. Every character the quoting logic acts |
| 246 | + * on (space, tab, '"', '\\') is ASCII and, by UTF-8's design, never |
| 247 | + * appears inside a multibyte sequence, so operating on raw bytes here |
| 248 | + * is safe and keeps multibyte argument bytes intact for conversion. */ |
| 249 | + char *buf = (char *)malloc(total); |
| 250 | + if (!buf) { |
| 251 | + return NULL; |
| 252 | + } |
| 253 | + |
| 254 | + /* Second pass: write the command line bytes. */ |
| 255 | + char *w = buf; |
| 256 | + for (int i = 0; argv[i]; i++) { |
| 257 | + const char *arg = argv[i]; |
| 258 | + bool needs_quote = (arg[0] == '\0'); |
| 259 | + for (const char *p = arg; *p; p++) { |
| 260 | + if (*p == ' ' || *p == '\t' || *p == '"') { |
| 261 | + needs_quote = true; |
| 262 | + break; |
| 263 | + } |
| 264 | + } |
| 265 | + if (i > 0) { |
| 266 | + *w++ = ' '; |
| 267 | + } |
| 268 | + if (needs_quote) { |
| 269 | + *w++ = '"'; |
| 270 | + size_t backslashes = 0; |
| 271 | + for (const char *p = arg; *p; p++) { |
| 272 | + if (*p == '\\') { |
| 273 | + backslashes++; |
| 274 | + *w++ = '\\'; |
| 275 | + } else if (*p == '"') { |
| 276 | + /* Double the preceding backslashes, then escape the quote. */ |
| 277 | + for (size_t b = 0; b < backslashes; b++) { |
| 278 | + *w++ = '\\'; |
| 279 | + } |
| 280 | + *w++ = '\\'; |
| 281 | + *w++ = '"'; |
| 282 | + backslashes = 0; |
| 283 | + } else { |
| 284 | + backslashes = 0; |
| 285 | + *w++ = *p; |
| 286 | + } |
| 287 | + } |
| 288 | + /* Double trailing backslashes before the closing quote. */ |
| 289 | + for (size_t b = 0; b < backslashes; b++) { |
| 290 | + *w++ = '\\'; |
| 291 | + } |
| 292 | + *w++ = '"'; |
| 293 | + } else { |
| 294 | + for (const char *p = arg; *p; p++) { |
| 295 | + *w++ = *p; |
| 296 | + } |
| 297 | + } |
| 298 | + } |
| 299 | + *w = '\0'; |
| 300 | + |
| 301 | + wchar_t *out = cbm_utf8_to_wide(buf); |
| 302 | + free(buf); |
| 303 | + return out; |
| 304 | +} |
| 305 | + |
200 | 306 | int cbm_exec_no_shell(const char *const *argv) { |
201 | 307 | if (!argv || !argv[0]) { |
202 | 308 | return CBM_NOT_FOUND; |
203 | 309 | } |
204 | | - return (int)_spawnvp(_P_WAIT, argv[0], argv); |
| 310 | + |
| 311 | + wchar_t *cmdline = cbm_build_cmdline(argv); |
| 312 | + if (!cmdline) { |
| 313 | + return CBM_NOT_FOUND; |
| 314 | + } |
| 315 | + |
| 316 | + STARTUPINFOW si; |
| 317 | + PROCESS_INFORMATION pi; |
| 318 | + memset(&si, 0, sizeof(si)); |
| 319 | + memset(&pi, 0, sizeof(pi)); |
| 320 | + si.cb = sizeof(si); |
| 321 | + |
| 322 | + if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { |
| 323 | + free(cmdline); |
| 324 | + return CBM_NOT_FOUND; |
| 325 | + } |
| 326 | + free(cmdline); |
| 327 | + |
| 328 | + WaitForSingleObject(pi.hProcess, INFINITE); |
| 329 | + DWORD exit_code = (DWORD)CBM_NOT_FOUND; |
| 330 | + GetExitCodeProcess(pi.hProcess, &exit_code); |
| 331 | + CloseHandle(pi.hProcess); |
| 332 | + CloseHandle(pi.hThread); |
| 333 | + return (int)exit_code; |
205 | 334 | } |
206 | 335 |
|
207 | 336 | #else /* POSIX */ |
|
0 commit comments