// -*- c++ -*- // Distributed under the BSD 2-Clause License. // See accompanying file LICENSE for details. #include "execute.h" #include "ctor.h" #include "pointerlist.h" #if !defined(_WIN32) #include #include #include #include extern char **environ; #else #define WINDOWS_LEAN_AND_MEAN #include #undef max #include #endif #include #include #include #include #include /* https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/ https://github.com/famzah/popen-noshell/commit/1f9eaf4eeef348d1efe0f3c7fe8ab670653cfbb1 https://blog.famzah.net/2018/12/19/posix_spawn-performance-benchmarks-and-usage-examples/ https://stackoverflow.com/questions/4259629/what-is-the-difference-between-fork-and-vfork/5207945#5207945 */ namespace { #if !defined(_WIN32) int parent_waitpid(pid_t pid) { int status{}; auto rc_pid = waitpid(pid, &status, 0); if(rc_pid > 0) { if(WIFEXITED(status)) { // Child exited with normally return WEXITSTATUS(status); } if(WIFSIGNALED(status)) { // Child exited via signal (segfault, abort, ...) std::cerr << strsignal(status) << '\n'; return WTERMSIG(status); } } else { // No PID returned, this is an error if(errno == ECHILD) { // No children exist. return 1; } else { // Unexpected error. abort(); } } // Should never happen... return 1; } #endif //_WIN32 #if defined(_WIN32) std::string getLastErrorAsString() { DWORD errorMessageID = ::GetLastError(); if(errorMessageID == 0) { return {}; } LPSTR message_buffer{}; auto size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message_buffer, 0, nullptr); std::string message(message_buffer, size); LocalFree(message_buffer); return message; } int moveSelf(const ctor::settings& settings) { int cnt{0}; char source[MAX_PATH]; HMODULE module = GetModuleHandle(0); GetModuleFileNameA(module, source, MAX_PATH); while(true) { if(cnt > 10) // If we need to try more than 10 times something is wrong { return 1; } std::filesystem::path tmp_file = settings.builddir; tmp_file /= "tmp"; std::string target = tmp_file.string() + "-" + std::to_string(cnt); if(MoveFileA(source, target.data())) { break; // success } auto err = GetLastError(); if(err == ERROR_ALREADY_EXISTS) { if(DeleteFileA(target.data())) { continue; // Try again } err = GetLastError(); if(err != ERROR_ACCESS_DENIED) { std::cerr << "Could not delete file\n"; return err; } cnt++; continue; // Increment and try again } else { std::cerr << "Could not move file\n"; return err; } } return 0; } #endif // _WIN32 } // namespace :: int execute(const ctor::settings& settings, const std::string& command, const std::vector& args, const std::map& env, [[maybe_unused]] bool terminate) { std::vector argv; argv.push_back(command.data()); for(const auto& arg : args) { argv.push_back(arg.data()); } argv.push_back(nullptr); std::string cmd; for(const auto& arg : argv) { if(arg == nullptr) { break; } if(!cmd.empty()) { cmd += " "; } cmd += arg; } if(settings.verbose > 0) { std::cout << cmd << std::endl; } #if !defined(_WIN32) #if 1 auto pid = vfork(); if(pid == 0) { EnvMap envmap(environ); for(const auto& [key, value] : env) { envmap.insert(key + "=" + value); } if(settings.dry_run) { _exit(0); } auto [_, envv] = envmap.get(); execve(command.data(), const_cast(argv.data()), const_cast(envv)); std::cout << "Could not execute " << command << ": " << strerror(errno) << "\n"; _exit(1); // execve only returns if an error occurred } return parent_waitpid(pid); #elif 0 pid_t pid{}; EnvMap envmap(environ); for(const auto& [key, value] : env) { envmap.insert(key + "=" + value); } auto [_, envv] = envmap.get(); if(posix_spawn(&pid, command.data(), nullptr, nullptr, (char**)argv.data(), const_cast(envv))) { return 1; } return parent_waitpid(pid); #else (void)parent_waitpid; return system(cmd.data()); #endif #else // _WIN32 if(terminate) { auto ret = moveSelf(settings); if(ret != 0) { return ret; } } auto env_strings = GetEnvironmentStrings(); EnvMap envmap(env_strings); FreeEnvironmentStrings(env_strings); for(const auto& [key, value] : env) { envmap.insert(key + "=" + value); } // TODO: Use SetDllDirectory(...) to set DLL search directory? SECURITY_ATTRIBUTES security_attr; // Set the bInheritHandle flag so pipe handles are inherited. security_attr.nLength = sizeof(SECURITY_ATTRIBUTES); security_attr.bInheritHandle = TRUE; security_attr.lpSecurityDescriptor = nullptr; HANDLE stream_in_read{}; HANDLE stream_in_write{}; HANDLE stream_out_read{}; HANDLE stream_out_write{}; if(!CreatePipe(&stream_out_read, &stream_out_write, &security_attr, 0)) { std::cout << "Error CreatePipe (out): " << getLastErrorAsString() << "\n"; } if(!SetHandleInformation(stream_out_read, HANDLE_FLAG_INHERIT, 0)) { std::cout << "Error - SetHandleInformation (out): " << getLastErrorAsString() << "\n"; } // Create a pipe for the child process's STDIN. if(!CreatePipe(&stream_in_read, &stream_in_write, &security_attr, 0)) { std::cout << "Error CreatePipe (in): " << getLastErrorAsString() << "\n"; } // Ensure the write handle to the pipe for STDIN is not inherited. if(!SetHandleInformation(stream_in_write, HANDLE_FLAG_INHERIT, 0)) { std::cout << "Error - SetHandleInformation (in): " << getLastErrorAsString() << "\n"; } STARTUPINFO si{}; si.hStdInput = GetStdHandle(((DWORD)-10));//STD_INPUT_HANDLE si.hStdOutput = stream_out_write; si.hStdError = stream_out_write; si.dwFlags |= STARTF_USESTDHANDLES; PROCESS_INFORMATION pi{}; si.cb = sizeof(si); if(!CreateProcess(nullptr, // lpApplicationName (char*)cmd.data(), // lpCommandLine nullptr, // lpProcessAttributes nullptr, // lpThreadAttributes TRUE, // bInheritHandles INHERIT_PARENT_AFFINITY | 0x00000200 | // CREATE_NEW_PROCESS_GROUP 0, // dwCreationFlags envmap.stringify().data(), // lpEnvironment nullptr, // lpCurrentDirectory &si, // lpStartupInfo &pi)) // lpProcessInformation { std::cout << "Could not execute " << command << ": " << getLastErrorAsString() << "\n"; return 1; } int ignore_lines{}; std::filesystem::path cmd_path{command}; if(cmd_path.filename() == "cl.exe") { // Ignore first line, avoiding the annoying msvc compiler of printing the filename // of the file that is being compiled ignore_lines = 1; } std::atomic running{true}; auto parent_waitpid = std::thread([&](){ WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(stream_out_write); CloseHandle(stream_in_read); running.store(false); }); CHAR buf[1024]; while(running.load()) { if(WaitForSingleObject(stream_out_read, 0) != WAIT_OBJECT_0) { Sleep(1); continue; } DWORD read_bytes{}; auto res = ReadFile(stream_out_read, buf, sizeof(buf), &read_bytes, nullptr); if(res != TRUE || read_bytes == 0) { break; } std::string str; str.append(buf, read_bytes); if(ignore_lines == 0) { std::cout << str << std::flush; } if(str.find('\n') != std::string::npos) { ignore_lines = std::max(0, ignore_lines - 1); } } DWORD exit_code{}; GetExitCodeProcess(pi.hProcess, &exit_code); parent_waitpid.join(); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return exit_code; #endif // _WIN32 return 1; }