Reece
a20bb97128
[*] Fixed missing inherit handles flag [TODO] consider CREATE_NO_WINDOW flag when refactoring create process object
328 lines
8.4 KiB
C++
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);
|
|
}
|
|
}
|