What is broken: handling of "execute" with long command parameters (see win32execute).
In more details:
* parameter lpCommandLine was formatted incorrectly (it has to contain app name too)
* temporary file with parameters to the compiler was formatted incorrectly (it
should NOT contain app name; if it contains backslashes, then these
backslashes need to be escaped as double-backslashes)
* wrong location (directory) of temporary files in win32execute
* premature deletion of temporary files in win32execute
}
std::string
-argv_to_string(const char* const* argv, const std::string& prefix)
+argv_to_string(const char* const* argv,
+ const std::string& prefix,
+ bool escape_backslashes)
{
std::string result;
size_t i = 0;
for (size_t j = 0; arg[j]; ++j) {
switch (arg[j]) {
case '\\':
- ++bs;
- break;
- // Fallthrough.
+ if (!escape_backslashes) {
+ ++bs;
+ break;
+ }
+ // Fallthrough.
case '"':
bs = (bs << 1) + 1;
// Fallthrough.
// Recreate a Windows command line string based on `argv`. If `prefix` is
// non-empty, add it as the first argument.
-std::string argv_to_string(const char* const* argv, const std::string& prefix);
+// If escape_backslashes is true, emit additional backslash for each backslash
+// which is not preceding '"' and is not at the end of argv[i] either.
+std::string argv_to_string(const char* const* argv,
+ const std::string& prefix,
+ bool escape_backslashes = false);
// Return the error message corresponding to `error_code`.
std::string error_message(DWORD error_code);
DEBUG_ASSERT(ctx.config.compiler_type() == CompilerType::gcc);
args.erase_last("-fdiagnostics-color");
}
- int status = execute(args.to_argv().data(),
+ int status = execute(ctx,
+ args.to_argv().data(),
std::move(tmp_stdout.fd),
- std::move(tmp_stderr.fd),
- &ctx.compiler_pid);
+ std::move(tmp_stderr.fd));
if (status != 0 && !ctx.diagnostics_color_failed
&& ctx.config.compiler_type() == CompilerType::gcc) {
auto errors = Util::read_file(tmp_stderr.path);
bool fall_back_to_original_compiler = false;
Args saved_orig_args;
nonstd::optional<mode_t> original_umask;
+ std::string saved_temp_dir;
{
Context ctx;
LOG_RAW("Failed; falling back to running the real compiler");
+ saved_temp_dir = ctx.config.temporary_dir();
saved_orig_args = std::move(ctx.orig_args);
auto execv_argv = saved_orig_args.to_argv();
LOG("Executing {}", Util::format_argv_for_logging(execv_argv.data()));
- // Run execv below after ctx and finalizer have been destructed.
+ // Run execute_noreturn below after ctx and finalizer have been
+ // destructed.
}
}
umask(*original_umask);
}
auto execv_argv = saved_orig_args.to_argv();
- execv(execv_argv[0], const_cast<char* const*>(execv_argv.data()));
- throw Fatal("execv of {} failed: {}", execv_argv[0], strerror(errno));
+ execute_noreturn(const_cast<char* const*>(execv_argv.data()),
+ saved_temp_dir);
+ throw Fatal(
+ "execute_noreturn of {} failed: {}", execv_argv[0], strerror(errno));
}
return EXIT_SUCCESS;
using nonstd::string_view;
#ifdef _WIN32
+static int win32execute(const char* path,
+ const char* const* argv,
+ int doreturn,
+ int fd_stdout,
+ int fd_stderr,
+ const std::string& temp_dir);
+
int
-execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* /*pid*/)
+execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err)
+{
+ return win32execute(argv[0],
+ argv,
+ 1,
+ fd_out.release(),
+ fd_err.release(),
+ ctx.config.temporary_dir());
+}
+
+void
+execute_noreturn(const char* const* argv, const std::string& temp_dir)
{
- return win32execute(argv[0], argv, 1, fd_out.release(), fd_err.release());
+ win32execute(argv[0], argv, 0, -1, -1, temp_dir);
}
std::string
const char* const* argv,
int doreturn,
int fd_stdout,
- int fd_stderr)
+ int fd_stderr,
+ const std::string& temp_dir)
{
PROCESS_INFORMATION pi;
memset(&pi, 0x00, sizeof(pi));
std::string full_path = Win32Util::add_exe_suffix(path);
std::string tmp_file_path;
if (args.length() > 8192) {
- TemporaryFile tmp_file(path);
+ TemporaryFile tmp_file(FMT("{}/cmd_args", temp_dir));
+ args = Win32Util::argv_to_string(argv + 1, sh, true);
Util::write_fd(*tmp_file.fd, args.data(), args.length());
- args = FMT("\"@{}\"", tmp_file.path);
+ args = FMT("{} @{}", full_path, tmp_file.path);
tmp_file_path = tmp_file.path;
+ LOG("args from file {}", tmp_file.path);
}
BOOL ret = CreateProcess(full_path.c_str(),
const_cast<char*>(args.c_str()),
nullptr,
&si,
&pi);
- if (!tmp_file_path.empty()) {
- Util::unlink_tmp(tmp_file_path);
- }
if (fd_stdout != -1) {
close(fd_stdout);
close(fd_stderr);
full_path,
Win32Util::error_message(error),
error);
+ if (!tmp_file_path.empty()) {
+ Util::unlink_tmp(tmp_file_path);
+ }
return -1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
+ if (!tmp_file_path.empty()) {
+ Util::unlink_tmp(tmp_file_path);
+ }
+
DWORD exitcode;
GetExitCodeProcess(pi.hProcess, &exitcode);
CloseHandle(pi.hProcess);
// Execute a compiler backend, capturing all output to the given paths the full
// path to the compiler to run is in argv[0].
int
-execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
+execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err)
{
LOG("Executing {}", Util::format_argv_for_logging(argv));
{
SignalHandlerBlocker signal_handler_blocker;
- *pid = fork();
+ ctx.compiler_pid = fork();
}
- if (*pid == -1) {
+ if (ctx.compiler_pid == -1) {
throw Fatal("Failed to fork: {}", strerror(errno));
}
- if (*pid == 0) {
+ if (ctx.compiler_pid == 0) {
// Child.
dup2(*fd_out, STDOUT_FILENO);
fd_out.close();
int status;
int result;
- while ((result = waitpid(*pid, &status, 0)) != *pid) {
+ while ((result = waitpid(ctx.compiler_pid, &status, 0)) != ctx.compiler_pid) {
if (result == -1 && errno == EINTR) {
continue;
}
{
SignalHandlerBlocker signal_handler_blocker;
- *pid = 0;
+ ctx.compiler_pid = 0;
}
if (WEXITSTATUS(status) == 0 && WIFSIGNALED(status)) {
return WEXITSTATUS(status);
}
+
+void
+execute_noreturn(const char* const* argv, const std::string& /* unused */)
+{
+ execv(argv[0], const_cast<char* const*>(argv));
+}
#endif
std::string
class Context;
-int execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid);
+int execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err);
+
+void execute_noreturn(const char* const* argv, const std::string& temp_dir);
// Find an executable named `name` in `$PATH`. Exclude any executables that are
// links to `exclude_name`.
#ifdef _WIN32
std::string win32getshell(const std::string& path);
-int win32execute(const char* path,
- const char* const* argv,
- int doreturn,
- int fd_stdout,
- int fd_stderr);
#endif
# define NOMINMAX 1
# include <windows.h>
# define mkdir(a, b) _mkdir(a)
-# define execv(a, b) win32execute(a, b, 0, -1, -1)
+# define execv(a, b) \
+ do_not_call_execv_on_windows // to protect against incidental use of MinGW
+ // execv
# define strncasecmp _strnicmp
# define strcasecmp _stricmp
CHECK(Win32Util::argv_to_string(argv, "")
== R"("a" "b c" "\"d\"" "'e'" "\\\"h")");
}
+ {
+ const char* const argv[] = {"a\\b\\c", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "") == R"("a\b\c")");
+ }
+ {
+ const char* const argv[] = {"a\\b\\c", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "", true) == R"("a\\b\\c")");
+ }
+ {
+ const char* const argv[] = {R"(a\b \"c\" \)", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "") == R"("a\b \\\"c\\\" \\")");
+ }
+ {
+ const char* const argv[] = {R"(a\b \"c\" \)", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "", true)
+ == R"("a\\b \\\"c\\\" \\")");
+ }
}
TEST_SUITE_END();