1 | /*
|
2 | * Souffle - A Datalog Compiler
|
3 | * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved
|
4 | * Licensed under the Universal Permissive License v 1.0 as shown at:
|
5 | * - https://opensource.org/licenses/UPL
|
6 | * - <souffle root>/licenses/SOUFFLE-UPL.txt
|
7 | */
|
8 |
|
9 | /************************************************************************
|
10 | *
|
11 | * @file SubProcess.h
|
12 | *
|
13 | * Wrapper for launching subprocesses.
|
14 | *
|
15 | ***********************************************************************/
|
16 |
|
17 | #pragma once
|
18 |
|
19 | #include "souffle/utility/Types.h"
|
20 | #include "souffle/utility/span.h"
|
21 | #include <algorithm>
|
22 | #include <cassert>
|
23 | #include <cstdlib>
|
24 | #include <optional>
|
25 | #include <type_traits>
|
26 |
|
27 | #ifdef _MSC_VER
|
28 | #define NOMINMAX
|
29 | #include <windows.h>
|
30 | #else
|
31 | #include <sys/wait.h>
|
32 | #include <unistd.h>
|
33 | #endif
|
34 |
|
35 | namespace souffle {
|
36 |
|
37 | namespace detail {
|
38 | [[noreturn]] inline void perrorExit(char const* msg) {
|
39 | ::perror(msg);
|
40 | std::exit(EXIT_FAILURE);
|
41 | }
|
42 |
|
43 | // These are used by bash and are a defacto standard on Linux.
|
44 | // This list is incomplete.
|
45 | enum LinuxExitCode : int {
|
46 | cannot_execute = 126,
|
47 | command_not_found = 127,
|
48 | };
|
49 |
|
50 | using LinuxWaitStatus = int;
|
51 | } // namespace detail
|
52 |
|
53 | /**
|
54 | * Executes a process w/ the given `argv` arguments and `envp` overriden env-vars.
|
55 | *
|
56 | * @param argv The arguments to the process.
|
57 | * Do not include the 'program invoked as' argument 0. This is implicitly done for you.
|
58 | * @param envp Collection of env vars to override.
|
59 | * Any env not specified in `envp` is inherited from this process' environment.
|
60 | * @return `None` IFF unable to launch `program`, otherwise `program`'s `wait` status.
|
61 | * NB: This is not the exit code, though the exit code can be obtained from it.
|
62 | * However, you can do `execute(...) == 0` if you only care that it succeeded.
|
63 | */
|
64 | template <typename Envp = span<std::pair<char const*, char const*>>,
|
65 | typename = std::enable_if_t<is_iterable_of<Envp, std::pair<char const*, char const*> const>>>
|
66 | std::optional<detail::LinuxWaitStatus> execute(
|
67 | std::string const& program, span<char const* const> argv = {}, Envp&& envp = {}) {
|
68 | #ifndef _MSC_VER
|
69 | using EC = detail::LinuxExitCode;
|
70 |
|
71 | auto pid = ::fork();
|
72 | switch (pid) {
|
73 | case -1: return std::nullopt; // unable to fork. likely hit a resource limit of some kind.
|
74 |
|
75 | case 0: { // child
|
76 | // thankfully we're a fork. we can trash this proc's `::environ` w/o reprocussions
|
77 | for (auto&& [k, v] : envp) {
|
78 | if (::setenv(k, v, 1)) detail::perrorExit("setenv");
|
79 | }
|
80 |
|
81 | char* argv_temp[argv.size() + 2];
|
82 | argv_temp[0] = const_cast<char*>(program.c_str());
|
83 | std::copy_n(argv.data(), argv.size(), const_cast<char const**>(argv_temp) + 1);
|
84 | argv_temp[argv.size() + 1] = nullptr;
|
85 |
|
86 | ::execvp(program.c_str(), argv_temp);
|
87 | std::exit(EC::cannot_execute);
|
88 | }
|
89 |
|
90 | default: { // parent
|
91 | detail::LinuxWaitStatus status;
|
92 | if (::waitpid(pid, &status, 0) == -1) {
|
93 | // not recoverable / should never happen.
|
94 | detail::perrorExit("`waitpid` failed");
|
95 | }
|
96 |
|
97 | // check it exited or signaled (didn't specify `WNOHANG` or `WUNTRACED`)
|
98 | assert(WIFSIGNALED(status) || WIFEXITED(status));
|
99 |
|
100 | // check that the fork child successfully `exec`'d
|
101 | if (WIFEXITED(status)) {
|
102 | switch (WEXITSTATUS(status)) {
|
103 | default: return WEXITSTATUS(status);
|
104 |
|
105 | case EC::cannot_execute: // FALL THRU: command_not_found
|
106 | case EC::command_not_found: return std::nullopt; // fork couldn't execute the program
|
107 | }
|
108 | }
|
109 | // what should be returned on signal? Treat as error
|
110 | return EXIT_FAILURE;
|
111 | }
|
112 | }
|
113 | #else
|
114 | STARTUPINFOW si;
|
115 | PROCESS_INFORMATION pi;
|
116 | DWORD exit_code = 0;
|
117 |
|
118 | memset(&si, 0, sizeof(si));
|
119 | si.cb = sizeof(si);
|
120 | memset(&pi, 0, sizeof(pi));
|
121 |
|
122 | std::size_t l;
|
123 | std::wstring program_w(program.length() + 1, L' ');
|
124 | ::mbstowcs_s(&l, program_w.data(), program_w.size(), program.data(), program.size());
|
125 | program_w.resize(l - 1);
|
126 |
|
127 | WCHAR FoundPath[PATH_MAX];
|
128 | int64_t Found = (int64_t)FindExecutableW(program_w.c_str(), nullptr, FoundPath);
|
129 | if (Found <= 32) {
|
130 | std::cerr << "Cannot find executable '" << program << "'.\n";
|
131 | return std::nullopt;
|
132 | }
|
133 |
|
134 | std::wstringstream args_w;
|
135 | args_w << program_w;
|
136 | for (const auto& arg : argv) {
|
137 | std::string arg_s(arg);
|
138 | std::wstring arg_w(arg_s.length() + 1, L' ');
|
139 | ::mbstowcs_s(&l, arg_w.data(), arg_w.size(), arg_s.data(), arg_s.size());
|
140 | arg_w.resize(l - 1);
|
141 | args_w << L' ' << arg_w;
|
142 | }
|
143 |
|
144 | std::string envir;
|
145 | for (const auto& couple : envp) {
|
146 | envir += couple.first;
|
147 | envir += '=';
|
148 | envir += couple.second;
|
149 | envir += '\0';
|
150 | }
|
151 | envir += '\0';
|
152 |
|
153 | if (!CreateProcessW(FoundPath, args_w.str().data(), NULL, NULL, FALSE, 0, /*envir.data()*/ nullptr, NULL,
|
154 | &si, &pi)) {
|
155 | return std::nullopt;
|
156 | }
|
157 |
|
158 | WaitForSingleObject(pi.hProcess, INFINITE);
|
159 |
|
160 | if (!GetExitCodeProcess(pi.hProcess, &exit_code)) {
|
161 | return std::nullopt;
|
162 | }
|
163 |
|
164 | CloseHandle(pi.hProcess);
|
165 | CloseHandle(pi.hThread);
|
166 |
|
167 | return static_cast<int>(exit_code);
|
168 |
|
169 | #endif
|
170 | }
|
171 |
|
172 | /**
|
173 | * Executes a process w/ the given `argv` arguments and `envp` overriden env-vars.
|
174 | *
|
175 | * @param argv The arguments to the process.
|
176 | * Do not include the 'program invoked as' argument 0. This is implicitly done for you.
|
177 | * @param envp Collection of env vars to override.
|
178 | * Any env not specified in `envp` is inherited from this process' environment.
|
179 | * @return `None` IFF unable to launch `program`, otherwise `program`'s `wait` status.
|
180 | * NB: This is not the exit code, though the exit code can be obtained from it.
|
181 | * However, you can do `execute(...) == 0` if you only care that it succeeded.
|
182 | */
|
183 | template <typename Envp = span<std::pair<char const*, std::string>>,
|
184 | typename = std::enable_if_t<is_iterable_of<Envp, std::pair<char const*, std::string> const>>>
|
185 | std::optional<detail::LinuxWaitStatus> execute(
|
186 | std::string const& program, span<std::string const> argv, Envp&& envp = {}) {
|
187 | auto go = [](auto* dst, auto&& src, auto&& f) {
|
188 | size_t i = 0;
|
189 | for (auto&& x : src)
|
190 | dst[i++] = f(x);
|
191 | return span<std::remove_pointer_t<decltype(dst)>>{dst, dst + src.size()};
|
192 | };
|
193 |
|
194 | std::unique_ptr<char const*[]> argv_temp = std::make_unique<char const*[]>(argv.size());
|
195 | std::unique_ptr<std::pair<char const*, char const*>[]> envp_temp =
|
196 | std::make_unique<std::pair<char const*, char const*>[]>(envp.size());
|
197 | auto argv_ptr = go(argv_temp.get(), argv, [](auto&& x) { return x.c_str(); });
|
198 | auto envp_ptr = go(envp_temp.get(), envp, [](auto&& kv) {
|
199 | return std::pair{kv.first, kv.second.c_str()};
|
200 | });
|
201 | return souffle::execute(program, argv_ptr, envp_ptr);
|
202 | }
|
203 |
|
204 | } // namespace souffle
|