|
28 | 28 |
|
29 | 29 | #include <array>
|
30 | 30 | #include <filesystem>
|
| 31 | +#include <memory> |
| 32 | +#include <optional> |
31 | 33 |
|
32 | 34 | #include "util/BaseString.hpp"
|
33 | 35 | #include "util/require.h"
|
@@ -148,6 +150,20 @@ class NdbProcess {
|
148 | 150 |
|
149 | 151 | NdbProcess(BaseString name, Pipes *fds) : m_name(name), m_pipes(fds) {}
|
150 | 152 |
|
| 153 | + /* |
| 154 | + * Quoting function to be used for passing program name and arguments to a |
| 155 | + * Windows program that follows the quoting of command line supported by |
| 156 | + * Microsoft C/C++ runtime. See for example description in: |
| 157 | + * https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args |
| 158 | + * |
| 159 | + * Note this quoting is not always suitable when calling other programs since |
| 160 | + * they are free to interpret the command line as they wish and the possible |
| 161 | + * quoting done may mess up. For example cmd.exe treats unquoted ^ |
| 162 | + * differently. |
| 163 | + */ |
| 164 | + static std::optional<BaseString> quote_for_windows_crt( |
| 165 | + const char *str) noexcept; |
| 166 | + |
151 | 167 | static bool start_process(process_handle_t &pid, const char *path,
|
152 | 168 | const char *cwd, const Args &args, Pipes *pipes);
|
153 | 169 | };
|
@@ -225,6 +241,45 @@ inline void NdbProcess::Args::add(const Args &args) {
|
225 | 241 | for (unsigned i = 0; i < args.m_args.size(); i++) add(args.m_args[i].c_str());
|
226 | 242 | }
|
227 | 243 |
|
| 244 | +std::optional<BaseString> NdbProcess::quote_for_windows_crt( |
| 245 | + const char *str) noexcept { |
| 246 | + /* |
| 247 | + * Assuming program file names can not include " or end with a \ this |
| 248 | + * function should be usable also for quoting the command part of command |
| 249 | + * line when calling a C program via CreateProcess. |
| 250 | + */ |
| 251 | + const char *p = str; |
| 252 | + while (isprint(*p) && *p != ' ' && *p != '"' && *p != '*' && *p != '?') p++; |
| 253 | + if (*p == '\0' && str[0] != '\0') return str; |
| 254 | + BaseString ret; |
| 255 | + ret.append('"'); |
| 256 | + size_t backslashes = 0; |
| 257 | + while (*str) { |
| 258 | + switch (*str) { |
| 259 | + case '"': |
| 260 | + if (backslashes) { |
| 261 | + // backslashes preceding double quote needs quoting |
| 262 | + ret.append(backslashes, '\\'); |
| 263 | + backslashes = 0; |
| 264 | + } |
| 265 | + // use double double quotes to quote double quote |
| 266 | + ret.append('"'); |
| 267 | + break; |
| 268 | + case '\\': |
| 269 | + // Count backslashes in case they will be followed by double quote |
| 270 | + backslashes++; |
| 271 | + break; |
| 272 | + default: |
| 273 | + backslashes = 0; |
| 274 | + } |
| 275 | + ret.append(*str); |
| 276 | + str++; |
| 277 | + } |
| 278 | + if (backslashes) ret.append(backslashes, '\\'); |
| 279 | + ret.append('"'); |
| 280 | + return ret; |
| 281 | +} |
| 282 | + |
228 | 283 | #ifdef _WIN32
|
229 | 284 |
|
230 | 285 | /******
|
@@ -297,11 +352,27 @@ inline bool NdbProcess::start_process(process_handle_t &pid, const char *path,
|
297 | 352 | if (len > 0) path = full_path;
|
298 | 353 | }
|
299 | 354 |
|
300 |
| - BaseString cmdLine(path); |
301 |
| - BaseString argStr; |
302 |
| - argStr.assign(args.args(), " "); |
303 |
| - cmdLine.append(" "); |
304 |
| - cmdLine.append(argStr); |
| 355 | + std::optional<BaseString> quoted = quote_for_windows_crt(path); |
| 356 | + if (!quoted) { |
| 357 | + fprintf(stderr, "Function failed, could not quote command name: %s\n", |
| 358 | + path); |
| 359 | + return false; |
| 360 | + } |
| 361 | + BaseString cmdLine(quoted.value().c_str()); |
| 362 | + |
| 363 | + /* Quote each argument, and append it to the command line */ |
| 364 | + auto &args_vec = args.args(); |
| 365 | + for (size_t i = 0; i < args_vec.size(); i++) { |
| 366 | + auto *arg = args_vec[i].c_str(); |
| 367 | + std::optional<BaseString> quoted = quote_for_windows_crt(arg); |
| 368 | + if (!quoted) { |
| 369 | + fprintf(stderr, "Function failed, could not quote command argument: %s\n", |
| 370 | + arg); |
| 371 | + return false; |
| 372 | + } |
| 373 | + cmdLine.append(' '); |
| 374 | + cmdLine.append(quoted.value().c_str()); |
| 375 | + } |
305 | 376 | char *command_line = strdup(cmdLine.c_str());
|
306 | 377 |
|
307 | 378 | STARTUPINFO si;
|
@@ -451,11 +522,14 @@ inline bool NdbProcess::start_process(process_handle_t &pid, const char *path,
|
451 | 522 | }
|
452 | 523 | }
|
453 | 524 |
|
454 |
| - // Concatenate arguments |
455 |
| - BaseString args_str; |
456 |
| - args_str.assign(args.args(), " "); |
| 525 | + auto &args_vec = args.args(); |
| 526 | + size_t arg_cnt = args_vec.size(); |
| 527 | + char **argv = new char *[1 + arg_cnt + 1]; |
| 528 | + argv[0] = const_cast<char *>(path); |
| 529 | + for (size_t i = 0; i < arg_cnt; i++) |
| 530 | + argv[1 + i] = const_cast<char *>(args_vec[i].c_str()); |
| 531 | + argv[1 + arg_cnt] = nullptr; |
457 | 532 |
|
458 |
| - char **argv = BaseString::argify(path, args_str.c_str()); |
459 | 533 | execvp(path, argv);
|
460 | 534 |
|
461 | 535 | fprintf(stderr, "execv failed, error %d '%s'\n", errno, strerror(errno));
|
|
0 commit comments