AuroraRuntime/Source/Processes/Process.Linux.cpp
Reece a20bb97128 [+] Explicit async/blocking read process stdout/err
[*] Fixed missing inherit handles flag
[TODO] consider CREATE_NO_WINDOW flag when refactoring create process object
2021-12-24 14:34:55 +00:00

328 lines
8.4 KiB
C++

/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Process.Linux.cpp
Date: 2021-6-12
Author: Reece
***/
#include <RuntimeInternal.hpp>
#include "Processes.hpp"
#include "Process.Linux.hpp"
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <Source/Threading/Primitives/Semaphore.Unix.hpp>
static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
}
static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
#ifndef P_PIDFD
#define P_PIDFD 3
#endif
namespace Aurora::Processes
{
class ProcessImpl : public IProcess
{
public:
ProcessImpl(AuString cmd, AuList<AuString> args);
~ProcessImpl();
void ShutdownPipes();
bool TryKill() override;
bool Terminate() override;
AuSPtr<Aurora::Threading::IWaitable> AsWaitable() override;
AuSInt GetExitCode() override;
bool Read(bool error, void *buffer, AuUInt32 &len) override;
bool Read(void *buffer, AuUInt32 &len, bool errorStream, bool nonblock) override;
bool Write(const void *buffer, AuUInt32 len) override;
bool Start(enum ESpawnType type, bool fwdOut, bool fwdErr, bool fwdIn) override;
private:
int pipeStdOut_[2]{};
int pipeStdErr_[2]{};
int pipeStdIn_ [2]{};
AuString module_;
ESpawnType type_;
AuList<AuString> args_;
AuList<const char *> cargs_;
AuString windows_;
Threading::Threads::ThreadUnique_t thread_;
std::atomic<int> handle_;
AuSInt exitCode_;
};
ProcessImpl::ProcessImpl(AuString cmd, AuList<AuString> args) : module_(cmd), args_(args)
{
this->args_.insert(this->args_.begin(), cmd);
for (auto &arg : this->args_)
{
this->cargs_.push_back(arg.c_str());
this->windows_ += arg + " ";
}
this->cargs_.push_back(nullptr);
this->windows_.resize(this->windows_.size() - 1);
}
ProcessImpl::~ProcessImpl()
{
if (this->type_ == ESpawnType::eSpawnChildProcessWorker)
{
TryKill();
Terminate();
}
if (this->handle_)
{
if (this->type_ == ESpawnType::eSpawnThreadLeader)
{
sys_pidfd_send_signal(this->handle_, SIGCONT, NULL, 0);
}
}
if (this->thread_)
{
this->thread_.reset();
}
if (auto handle = this->handle_.exchange(0))
{
close(handle);
}
ShutdownPipes();
}
void ProcessImpl::ShutdownPipes()
{
if (auto fd = std::exchange(pipeStdErr_[0], {})) close(fd);
if (auto fd = std::exchange(pipeStdErr_[1], {})) close(fd);
if (auto fd = std::exchange(pipeStdOut_[0], {})) close(fd);
if (auto fd = std::exchange(pipeStdOut_[1], {})) close(fd);
if (auto fd = std::exchange(pipeStdIn_[0], {})) close(fd);
if (auto fd = std::exchange(pipeStdIn_[1], {})) close(fd);
}
bool ProcessImpl::TryKill()
{
if (this->handle_)
{
return sys_pidfd_send_signal(this->handle_, SIGTERM, NULL, 0) == 0;
}
return true;
}
bool ProcessImpl::Terminate()
{
if (this->handle_)
{
sys_pidfd_send_signal(this->handle_, SIGKILL, NULL, 0);
}
return true;
}
AuSPtr<Aurora::Threading::IWaitable> ProcessImpl::AsWaitable()
{
if (!this->thread_) return nullptr;
return this->thread_->AsWaitable();
}
AuSInt ProcessImpl::GetExitCode()
{
return this->exitCode_;
}
static int aurora_sys_waitid(int which, pid_t pid, siginfo_t *info, int options,
struct rusage *ru)
{
return syscall(__NR_waitid, which, pid, info, options, ru);
}
bool ProcessImpl::Read(bool error, void *buffer, AuUInt32 &len)
{
return Read(buffer, len, error, false);
}
bool ProcessImpl::Read(void *buffer, AuUInt32 &len, bool errorStream, bool nonblock)
{
len = 0;
auto handle = errorStream ? pipeStdErr_[0] : pipeStdOut_[0];
if (handle < 0)
{
return false;
}
auto control = fcntl(fd, F_GETFL);
auto ref = control;
if (nonblock)
{
control |= O_NONBLOCK;
}
else
{
control &= ~O_NONBLOCK;
}
if (ref != control)
{
fcntl(fd, F_SETFL, control);
}
auto tmp = read(handle, buffer, len);
if (tmp < 0)
{
return false;
}
len = tmp;
return true;
}
bool ProcessImpl::Write(const void *buffer, AuUInt32 len)
{
auto handle = pipeStdIn_[1];
if (!handle) return false;
return write(handle, buffer, len) == len;
}
bool ProcessImpl::Start(enum ESpawnType type, bool fwdOut, bool fwdErr, bool fwdIn)
{
this->exitCode_ = 0x10110100;
this->type_ = type;
if (type == ESpawnType::eSpawnAtomicOvermap)
{
execv(this->module_.c_str(), (char *const *)this->cargs_.data()); // https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
SysPushErrorGen("execv didn't overwrite the process map, given {} ({})", this->module_, this->windows_);
return false;
}
if (fwdOut)
{
if (!pipe(pipeStdOut_))
{
return false;
}
}
if (fwdErr)
{
if (!pipe(pipeStdErr_))
{
return false;
}
}
if (fwdIn)
{
if (!pipe(pipeStdIn_))
{
return false;
}
}
Threading::Threads::AbstractThreadVectors handler;
pid_t pid;
{
Threading::Primitives::Semaphore semaphore;
pid = fork();
if (pid == 0)
{
semaphore.Lock();
if (fwdIn)
{
dup2(pipeStdIn_[0], STDIN_FILENO);
close(std::exchange(pipeStdIn_[0], 0));
}
if (fwdErr)
{
dup2(pipeStdErr_[1], STDERR_FILENO);
close(std::exchange(pipeStdErr_[1], 0));
}
if (fwdOut)
{
dup2(pipeStdOut_[1], STDOUT_FILENO);
close(std::exchange(pipeStdOut_[1], 0));
}
if (type != ESpawnType::eSpawnChildProcessWorker)
{
setsid();
}
execv(this->module_.c_str(), (char * const *)this->cargs_.data()); // https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
SysPushErrorGen("execv didn't overwrite the process map, given {} ({})", this->module_, this->windows_);
return false;
}
else if (pid < 0)
{
return false;
}
else
{
this->handle_ = sys_pidfd_open(pid, 0);
semaphore.Unlock();
}
}
handler.DoRun = [=](Threading::Threads::IAuroraThread *)
{
{
// TODO: experimental (and requires kernel 5.3+)
siginfo_t info = {
.si_signo = 0,
};
// TODO: vaildate response
aurora_sys_waitid(P_PIDFD, this->handle_, &info, WEXITED, NULL);
// TODO: confirm this works?!?
this->exitCode_ = info.si_status;
}
};
this->thread_ = Threading::Threads::ThreadUnique(handler);
if (!this->thread_) return false;
this->thread_->Run();
return true;
}
AUKN_SYM IProcess *SpawnNew(const AuString &app, const AuList<AuString> &args)
{
return _new ProcessImpl(app, args);
}
AUKN_SYM void SpawnRelease(IProcess *process)
{
SafeDelete<ProcessImpl *>(process);
}
}