393 lines
9.3 KiB
C++
393 lines
9.3 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: Process.Unix.cpp
|
|
File: Process.Linux.cpp
|
|
Date: 2021-6-12
|
|
Author: Reece
|
|
***/
|
|
#include <RuntimeInternal.hpp>
|
|
#include "Processes.hpp"
|
|
#include "Process.Unix.hpp"
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <Source/Threading/Primitives/Semaphore.Unix.hpp>
|
|
|
|
namespace Aurora::Processes
|
|
{
|
|
struct ProcessImpl;
|
|
|
|
static AuThreadPrimitives::RWLockUnique_t gRWLock;
|
|
static AuHashMap<pid_t, ProcessImpl *> gPidLookupMap;
|
|
|
|
ProcessImpl::ProcessImpl(const StartupParmaters ¶ms) : startup_(params)
|
|
{
|
|
AuIOFS::NormalizePath(this->startup_.process, this->startup_.process);
|
|
|
|
this->startup_.args.insert(startup_.args.begin(), startup_.process);
|
|
|
|
for (const auto &arg : this->startup_.args)
|
|
{
|
|
this->cargs_.push_back(arg.c_str());
|
|
this->debug_ += arg + " ";
|
|
}
|
|
|
|
this->cargs_.push_back(nullptr);
|
|
if (this->debug_.size())
|
|
{
|
|
this->debug_.resize(this->debug_.size() - 1);
|
|
}
|
|
|
|
this->type_ = this->startup_.type;
|
|
}
|
|
|
|
ProcessImpl::~ProcessImpl()
|
|
{
|
|
if (this->type_ == ESpawnType::eSpawnChildProcessWorker)
|
|
{
|
|
TryKill();
|
|
Terminate();
|
|
}
|
|
|
|
{
|
|
AU_LOCK_GUARD(gRWLock->AsWritable());
|
|
|
|
if (this->alive_)
|
|
{
|
|
if (this->type_ == ESpawnType::eSpawnThreadLeader)
|
|
{
|
|
::kill(this->pidt_, SIGCONT);
|
|
}
|
|
}
|
|
|
|
AuTryRemove(gPidLookupMap, this->pidt_);
|
|
}
|
|
|
|
ShutdownPipes();
|
|
}
|
|
|
|
AuUInt ProcessImpl::GetProcessId()
|
|
{
|
|
return this->pidt_;
|
|
}
|
|
|
|
void ProcessImpl::ByeLol(AuSInt code)
|
|
{
|
|
this->exitCode_ = code;
|
|
|
|
if (this->finished_)
|
|
{
|
|
this->finished_->Set();
|
|
}
|
|
}
|
|
|
|
void ProcessImpl::ShutdownPipes()
|
|
{
|
|
if (auto fd = AuExchange(pipeStdErr_[0], {})) close(fd);
|
|
if (auto fd = AuExchange(pipeStdErr_[1], {})) close(fd);
|
|
if (auto fd = AuExchange(pipeStdOut_[0], {})) close(fd);
|
|
if (auto fd = AuExchange(pipeStdOut_[1], {})) close(fd);
|
|
if (auto fd = AuExchange(pipeStdIn_[0], {})) close(fd);
|
|
if (auto fd = AuExchange(pipeStdIn_[1], {})) close(fd);
|
|
}
|
|
|
|
bool ProcessImpl::TryKill()
|
|
{
|
|
AU_LOCK_GUARD(gRWLock->AsReadable());
|
|
|
|
if (this->alive_)
|
|
{
|
|
return ::kill(this->pidt_, SIGTERM) == 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ProcessImpl::Terminate()
|
|
{
|
|
AU_LOCK_GUARD(gRWLock->AsReadable());
|
|
|
|
if (this->alive_)
|
|
{
|
|
return ::kill(this->pidt_, SIGKILL) == 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AuSPtr<Aurora::Threading::IWaitable> ProcessImpl::AsWaitable()
|
|
{
|
|
return this->finished_;
|
|
}
|
|
|
|
AuSInt ProcessImpl::GetExitCode()
|
|
{
|
|
return this->exitCode_;
|
|
}
|
|
|
|
bool ProcessImpl::Read(EStandardHandle stream, const AuMemoryViewStreamWrite &destination, bool nonblock)
|
|
{
|
|
auto handle = stream == EStandardHandle::eStdError ? this->pipeStdErr_[0] : this->pipeStdOut_[0];
|
|
if (handle < 0)
|
|
{
|
|
SysPushErrorUninitialized();
|
|
return false;
|
|
}
|
|
|
|
auto control = fcntl(handle, F_GETFL);
|
|
auto ref = control;
|
|
|
|
if (nonblock)
|
|
{
|
|
control |= O_NONBLOCK;
|
|
}
|
|
else
|
|
{
|
|
control &= ~O_NONBLOCK;
|
|
}
|
|
|
|
if (ref != control)
|
|
{
|
|
fcntl(handle, F_SETFL, control);
|
|
}
|
|
|
|
int tmp;
|
|
|
|
do
|
|
{
|
|
tmp = read(handle, destination.ptr, destination.length);
|
|
} while ((tmp == -1 && errno == EINTR));
|
|
|
|
if (tmp <= 0)
|
|
{
|
|
if (tmp == 0)
|
|
{
|
|
return nonblock;
|
|
}
|
|
|
|
SysPushErrorMem();
|
|
return false;
|
|
}
|
|
|
|
destination.outVariable = tmp;
|
|
return true;
|
|
}
|
|
|
|
bool ProcessImpl::Write(const AuMemoryViewStreamRead &source)
|
|
{
|
|
auto handle = this->pipeStdIn_[1];
|
|
if (!handle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return write(handle, source.ptr, source.length) == source.length;
|
|
}
|
|
|
|
bool ProcessImpl::Init()
|
|
{
|
|
if (this->startup_.fwdOut)
|
|
{
|
|
if (pipe(this->pipeStdOut_))
|
|
{
|
|
SysPushErrorMem();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (this->startup_.fwdErr)
|
|
{
|
|
if (pipe(this->pipeStdErr_))
|
|
{
|
|
SysPushErrorMem();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (this->startup_.fwdIn)
|
|
{
|
|
if (pipe(this->pipeStdIn_))
|
|
{
|
|
SysPushErrorMem();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this->finished_ = AuThreadPrimitives::EventShared(false, false, true);
|
|
return bool(this->finished_);
|
|
}
|
|
|
|
bool ProcessImpl::Start()
|
|
{
|
|
this->exitCode_ = 0x10110100;
|
|
|
|
if (this->type_ == ESpawnType::eSpawnOvermap)
|
|
{
|
|
execv(this->startup_.process.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->startup_.process, this->debug_);
|
|
return false;
|
|
}
|
|
|
|
pid_t pid;
|
|
{
|
|
pid = fork();
|
|
if (pid == 0)
|
|
{
|
|
if (this->startup_.fwdIn)
|
|
{
|
|
dup2(pipeStdIn_[0], STDIN_FILENO);
|
|
|
|
::close(pipeStdIn_[0]);
|
|
::close(pipeStdIn_[1]);
|
|
}
|
|
|
|
if (this->startup_.fwdErr)
|
|
{
|
|
dup2(pipeStdErr_[1], STDERR_FILENO);
|
|
::close(pipeStdErr_[0]);
|
|
::close(pipeStdErr_[1]);
|
|
}
|
|
|
|
if (this->startup_.fwdOut)
|
|
{
|
|
dup2(pipeStdOut_[1], STDOUT_FILENO);
|
|
::close(pipeStdOut_[0]);
|
|
::close(pipeStdOut_[1]);
|
|
}
|
|
|
|
if (this->type_ != ESpawnType::eSpawnChildProcessWorker)
|
|
{
|
|
setsid();
|
|
}
|
|
|
|
execv(this->startup_.process.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->startup_.process, this->debug_);
|
|
return false;
|
|
}
|
|
else if (pid > 0)
|
|
{
|
|
if (pipeStdOut_[1])
|
|
{
|
|
::close(AuExchange(pipeStdOut_[1], 0));
|
|
}
|
|
|
|
if (pipeStdErr_[1])
|
|
{
|
|
::close(AuExchange(pipeStdErr_[1], 0));
|
|
}
|
|
|
|
if (pipeStdIn_[0])
|
|
{
|
|
::close(AuExchange(pipeStdIn_[0], 0));
|
|
}
|
|
|
|
this->pidt_ = pid;
|
|
this->alive_ = true;
|
|
|
|
{
|
|
AU_LOCK_GUARD(gRWLock->AsWritable());
|
|
SysAssert(AuTryInsert(gPidLookupMap, pid, this));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AUKN_SYM IProcess *SpawnNew(const StartupParmaters ¶ms)
|
|
{
|
|
auto ret = _new ProcessImpl(params);
|
|
|
|
if (!ret)
|
|
{
|
|
SysPushErrorMem();
|
|
return {};
|
|
}
|
|
|
|
if (!ret->Init())
|
|
{
|
|
SysPushErrorNested();
|
|
delete ret;
|
|
return {};
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
AUKN_SYM void SpawnRelease(IProcess *process)
|
|
{
|
|
AuSafeDelete<ProcessImpl *>(process);
|
|
}
|
|
|
|
static void HandleChildTermiantion(pid_t pid, int code)
|
|
{
|
|
AU_LOCK_GUARD(gRWLock->AsReadable());
|
|
|
|
auto handler = gPidLookupMap.find(pid);
|
|
if (handler == gPidLookupMap.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
auto process = *handler;
|
|
process.second->ByeLol(code);
|
|
}
|
|
}
|
|
|
|
static void SigChldHandler(int)
|
|
{
|
|
int code;
|
|
pid_t pid;
|
|
|
|
while (true)
|
|
{
|
|
pid = wait3(&code, WNOHANG, nullptr);
|
|
|
|
if ((pid == 0) ||
|
|
(pid == -1))
|
|
{
|
|
break;
|
|
}
|
|
|
|
HandleChildTermiantion(pid, code);
|
|
}
|
|
}
|
|
|
|
void InitUnix()
|
|
{
|
|
gRWLock = AuThreadPrimitives::RWLockUnique();
|
|
|
|
struct sigaction action =
|
|
{
|
|
.sa_handler = SigChldHandler,
|
|
.sa_flags = SA_ONSTACK
|
|
};
|
|
sigemptyset(&action.sa_mask);
|
|
sigaction(SIGCHLD, &action, nullptr);
|
|
}
|
|
|
|
void DeinitUnix()
|
|
{
|
|
struct sigaction action =
|
|
{
|
|
.sa_handler = SIG_DFL,
|
|
.sa_flags = SA_ONSTACK | SA_NOCLDWAIT
|
|
};
|
|
sigemptyset(&action.sa_mask);
|
|
sigaction(SIGCHLD, &action, nullptr);
|
|
|
|
gRWLock.reset();
|
|
}
|
|
} |