53inline Result run(
const std::vector<std::string>& args,
54 bool merge_stderr =
false,
55 std::size_t limit = 8U * 1024U * 1024U,
56 const std::map<std::string, std::string>& environment = {},
57 const std::function<bool(
const std::string&)>& on_line = {}) {
59 if (args.empty() || args[0].empty())
return result;
60 for (
const auto& arg : args)
if (arg.find(
'\0') != std::string::npos)
return result;
63 if (::pipe(fd) != 0)
return result;
64 posix_spawn_file_actions_t actions;
65 if (posix_spawn_file_actions_init(&actions) != 0) {
66 ::close(fd[0]); ::close(fd[1]);
return result;
68 posix_spawn_file_actions_addclose(&actions, fd[0]);
69 posix_spawn_file_actions_adddup2(&actions, fd[1], STDOUT_FILENO);
70 if (merge_stderr) posix_spawn_file_actions_adddup2(&actions, fd[1], STDERR_FILENO);
71 posix_spawn_file_actions_addclose(&actions, fd[1]);
73 std::vector<char*> argv;
74 for (
const auto& arg : args) argv.push_back(
const_cast<char*
>(arg.c_str()));
75 argv.push_back(
nullptr);
78 std::vector<std::string> env_storage;
79 std::vector<char*> envp;
80 if (!environment.empty()) {
81 for (
char** p =
environ; p && *p; ++p) {
82 const std::string item(*p);
83 const auto pos = item.find(
'=');
84 if (pos != std::string::npos && environment.find(item.substr(0, pos)) == environment.end())
85 env_storage.push_back(item);
87 for (
const auto& [key, value] : environment) {
88 if (key.empty() || key.find(
'=') != std::string::npos || key.find(
'\0') != std::string::npos || value.find(
'\0') != std::string::npos) {
89 posix_spawn_file_actions_destroy(&actions);
90 ::close(fd[0]); ::close(fd[1]);
return result;
92 env_storage.push_back(key +
"=" + value);
94 for (
auto& item : env_storage) envp.push_back(item.data());
95 envp.push_back(
nullptr);
99 const int err = posix_spawnp(&child, args[0].c_str(), &actions,
nullptr,
100 argv.data(), environment.empty() ?
environ : envp.data());
101 posix_spawn_file_actions_destroy(&actions);
103 if (err != 0) { ::close(fd[0]);
return result; }
104 result.started =
true;
107 std::size_t total = 0;
108 bool stopped =
false;
110 const ssize_t n = ::read(fd[0], buf,
sizeof(buf));
111 if (n < 0 && errno == EINTR)
continue;
113 total +=
static_cast<std::size_t
>(n);
114 if (total > limit) { result.truncated =
true; stopped =
true;
break; }
116 pending.append(buf,
static_cast<std::size_t
>(n));
118 while ((p = pending.find(
'\n')) != std::string::npos) {
119 std::string
line = pending.substr(0, p + 1);
120 pending.erase(0, p + 1);
121 if (!on_line(line)) { stopped =
true;
break; }
125 if (pending.size() > 65536) { result.truncated =
true; stopped =
true;
break; }
127 result.output.append(buf,
static_cast<std::size_t
>(n));
130 if (on_line && !stopped && !pending.empty()) on_line(pending);
131 if (stopped) ::kill(child, SIGTERM);
135 do { waited = ::waitpid(child, &status, 0); }
while (waited == -1 && errno == EINTR);
136 if (waited == child) {
137 if (WIFEXITED(status)) result.exit_code = WEXITSTATUS(status);
138 else if (WIFSIGNALED(status)) result.exit_code = 128 + WTERMSIG(status);
140 if (stopped) result.truncated =
true;