OILS / vendor / souffle / utility / SubProcess.h View on Github | oilshell.org

204 lines, 113 significant
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
35namespace souffle {
36
37namespace 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.
45enum LinuxExitCode : int {
46 cannot_execute = 126,
47 command_not_found = 127,
48};
49
50using 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 */
64template <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>>>
66std::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 */
183template <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>>>
185std::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