46inline std::string validate_contract(
const core::LaunchContract& contract) {
47 const auto contains_nul = [](
const std::string& value) {
48 return value.find(
'\0') != std::string::npos;
50 if (contract.execution.binary.empty())
return "execution binary is empty";
51 if (contains_nul(contract.execution.binary))
return "execution binary contains NUL";
52 if (contains_nul(contract.execution.cwd))
return "working directory contains NUL";
53 for (
const auto& argument : contract.execution.arguments) {
54 if (contains_nul(argument))
return "execution argument contains NUL";
56 for (
const auto& [key, value] : contract.environment) {
57 if (key.empty() || key.find(
'=') != std::string::npos || contains_nul(key)) {
58 return "invalid environment key";
60 if (contains_nul(value))
return "environment value contains NUL: " + key;
62 if (!contract.execution.library_paths.empty()) {
63 const auto& key = contract.execution.library_path_env_key;
64 if (key.empty() || key.find(
'=') != std::string::npos || contains_nul(key)) {
65 return "invalid library path environment key";
67 for (
const auto& path : contract.execution.library_paths) {
68 if (contains_nul(path))
return "library path contains NUL";
83inline std::vector<std::string> build_environment(
const core::LaunchContract& contract) {
84 std::vector<std::string> env;
87 for (
const auto& [key, value] : contract.environment) {
88 env.push_back(key +
"=" + value);
92 if (!contract.execution.library_paths.empty()) {
94 char sep = core::get_path_separator();
96 for (
size_t i = 0; i < contract.execution.library_paths.size(); i++) {
97 if (i > 0) lib_path += sep;
98 lib_path += contract.execution.library_paths[i];
102 std::string lib_key = contract.execution.library_path_env_key;
104 for (
auto& e : env) {
105 if (e.find(lib_key +
"=") == 0) {
107 std::string existing = e.substr(lib_key.size() + 1);
108 e = lib_key +
"=" + lib_path + sep + existing;
115 env.push_back(lib_key +
"=" + lib_path);
119 std::sort(env.begin(), env.end());
130inline std::vector<std::string> build_argv(
const core::LaunchContract& contract) {
131 std::vector<std::string> argv;
132 argv.push_back(contract.execution.binary);
133 for (
const auto& arg : contract.execution.arguments) {
150inline ExecResult execute_unix(
const core::LaunchContract& contract) {
153 if (
const auto error = validate_contract(contract); !error.empty()) {
154 result.error = error;
158 auto argv_strings = build_argv(contract);
159 auto env_strings = build_environment(contract);
162 std::vector<char*> argv;
163 for (
auto& s : argv_strings) {
164 argv.push_back(
const_cast<char*
>(s.c_str()));
166 argv.push_back(
nullptr);
168 std::vector<char*> envp;
169 for (
auto& s : env_strings) {
170 envp.push_back(
const_cast<char*
>(s.c_str()));
172 envp.push_back(
nullptr);
177 result.error =
"fork failed: " + std::string(strerror(errno));
185 if (!contract.execution.cwd.empty()) {
186 if (chdir(contract.execution.cwd.c_str()) != 0) {
192 execve(contract.execution.binary.c_str(), argv.data(), envp.data());
199 if (waitpid(pid, &status, 0) == -1) {
200 result.error =
"waitpid failed: " + std::string(strerror(errno));
204 if (WIFEXITED(status)) {
205 result.exit_code = WEXITSTATUS(status);
207 }
else if (WIFSIGNALED(status)) {
208 result.exit_code = 128 + WTERMSIG(status);
211 result.error =
"process terminated abnormally";
222inline ExecResult exec_replace_unix(
const core::LaunchContract& contract) {
225 if (
const auto error = validate_contract(contract); !error.empty()) {
226 result.error = error;
230 auto argv_strings = build_argv(contract);
231 auto env_strings = build_environment(contract);
233 std::vector<char*> argv;
234 for (
auto& s : argv_strings) {
235 argv.push_back(
const_cast<char*
>(s.c_str()));
237 argv.push_back(
nullptr);
239 std::vector<char*> envp;
240 for (
auto& s : env_strings) {
241 envp.push_back(
const_cast<char*
>(s.c_str()));
243 envp.push_back(
nullptr);
246 if (!contract.execution.cwd.empty()) {
247 if (chdir(contract.execution.cwd.c_str()) != 0) {
248 result.error =
"chdir failed: " + std::string(strerror(errno));
254 execve(contract.execution.binary.c_str(), argv.data(), envp.data());
257 result.error =
"execve failed: " + std::string(strerror(errno));
272inline std::string build_command_line(
const std::vector<std::string>& argv) {
274 for (
size_t i = 0; i < argv.size(); i++) {
275 if (i > 0) cmd +=
" ";
276 const auto& argument = argv[i];
277 const bool quote = argument.empty() || argument.find_first_of(
" \t\"") != std::string::npos;
278 if (!quote) { cmd += argument;
continue; }
280 std::size_t backslashes = 0;
281 for (
const char character : argument) {
282 if (character ==
'\\') { ++backslashes;
continue; }
283 if (character ==
'"') {
284 cmd.append(backslashes * 2 + 1,
'\\');
287 cmd.append(backslashes,
'\\');
292 cmd.append(backslashes * 2,
'\\');
301inline std::string build_environment_block(
const std::vector<std::string>& env) {
303 for (
const auto& e : env) {
315inline ExecResult spawn_windows(
const core::LaunchContract& contract,
bool wait_for_exit) {
318 if (
const auto error = validate_contract(contract); !error.empty()) {
319 result.error = error;
323 auto argv = build_argv(contract);
324 auto env = build_environment(contract);
326 std::string cmd_line = build_command_line(argv);
327 std::string env_block = build_environment_block(env);
329 STARTUPINFOA si = {0};
332 PROCESS_INFORMATION pi = {0};
334 BOOL success = CreateProcessA(
335 contract.execution.binary.c_str(),
342 contract.execution.cwd.empty() ?
nullptr : contract.execution.cwd.c_str(),
348 result.error =
"CreateProcess failed: " + std::to_string(GetLastError());
353 if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) {
354 result.error =
"WaitForSingleObject failed: " + std::to_string(GetLastError());
355 CloseHandle(pi.hProcess);
356 CloseHandle(pi.hThread);
361 if (GetExitCodeProcess(pi.hProcess, &exit_code)) {
362 result.exit_code =
static_cast<int>(exit_code);
365 result.error =
"GetExitCodeProcess failed";
369 result.exit_code = 0;
372 CloseHandle(pi.hProcess);
373 CloseHandle(pi.hThread);
393inline ExecResult execute(
const core::LaunchContract& contract) {
395 return detail::spawn_windows(contract,
true);
397 return execute_unix(contract);
409inline ExecResult exec_replace(
const core::LaunchContract& contract) {
411 auto result = detail::spawn_windows(contract,
false);
417 return exec_replace_unix(contract);