#include "compat.h"
#include "debug.h"
#ifdef NIX_VERSION
#if NIX_VERSION >= 0x021900
#include <processes.hh>
#else
#include <util.hh>
#endif
#else
#include <csignal>
#include <fcntl.h>
#include <format>
#include <spawn.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>
#endif
using namespace std::string_literals;
using namespace std::string_view_literals;
namespace nixpluginpijul
{
void runProcess(const char *p, const StringList &args, const char *chdir, OutputMode mode)
{
DBG_BEGIN
#ifdef NIX_VERSION
auto res = nix::runProgram(nix::RunOptions{
.program = std::string(p),
#if NIX_VERSION >= 0x022200
.lookupPath = true,
#else
.searchPath = true,
#endif
.args = args,
.chdir = chdir ? Path(chdir) : std::optional<Path>(),
.isInteractive = mode.interactive(),
});
if (!nix::statusOk(res.first)) {
throw nix::ExecError(res.first, "program '%1%' %2%", p, nix::statusToString(res.first));
}
if (mode.output()) {
*mode.output() = std::move(res.second);
}
#else
int fds[2];
posix_spawn_file_actions_t fa;
posix_spawn_file_actions_init(&fa);
if (mode.output()) {
if (pipe(fds) != 0) {
perror("failed to create pipe");
exit(EX_OSERR);
}
posix_spawn_file_actions_adddup2(&fa, fds[1], 0);
posix_spawn_file_actions_adddup2(&fa, fds[1], 1);
}
if (!mode.interactive()) {
posix_spawn_file_actions_addclose(&fa, 2);
}
if (chdir) {
posix_spawn_file_actions_addchdir_np(&fa, chdir);
}
std::vector<const char *> argv;
argv.push_back(p);
for (const auto &el : args) {
argv.push_back(el.c_str());
}
pid_t pid;
if (posix_spawnp(&pid, p, &fa, nullptr, const_cast<char *const *const>(argv.data()), nullptr) != 0) {
perror("failed to spawn process");
exit(EX_OSERR);
}
close(fds[1]);
posix_spawn_file_actions_destroy(&fa);
fd_set set;
FD_ZERO(&set);
int nfds = 0;
if (mode.output()) {
FD_SET(fds[0], &set);
fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
nfds = 1;
}
sigset_t sigs;
sigfillset(&sigs);
sigdelset(&sigs, SIGCHLD);
timespec timeout{.tv_sec = 1};
bool running = true;
int status;
while (running) {
int ret = pselect(nfds, &set, nullptr, nullptr, &timeout, &sigs);
printf("%d\n", ret);
if (ret == -1) {
if (errno != EINTR) {
perror("pselect");
exit(EX_OSERR);
}
}
ret = waitpid(pid, &status, WNOHANG);
if (ret == -1) {
perror("waitpid");
exit(EX_OSERR);
} else if (ret == pid) {
running = false;
ret = 1; }
if (ret > 0 && mode.output()) {
char buf[4096];
ssize_t len;
do {
len = read(fds[0], buf, sizeof(buf));
if (len < 0 && errno != EWOULDBLOCK) {
perror("read");
exit(EX_OSERR);
}
if (len > 1) {
mode.output()->append(buf, len);
}
} while (len > 0);
}
}
if (mode.output()) {
close(fds[0]);
}
if (status != 0) {
fatal(std::format("process `{}' exited with status {}", p, WEXITSTATUS(status)).c_str());
}
#endif
DBG_END
}
void fatal(const char *text)
{
#ifdef NIX_VERSION
throw nix::Error("%1%", text);
#else
fprintf(stderr, "fatal: %s\n", text);
exit(EX_SOFTWARE);
#endif
}
}