/*** 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 #include "Processes.hpp" #include "Process.Unix.hpp" #include #include #include #include #include //#include namespace Aurora::Processes { struct ProcessImpl; static AuThreadPrimitives::RWLockUnique_t gRWLock; static AuHashMap 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); } } 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 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) { 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); } auto tmp = read(handle, destination.ptr, destination.length); if (tmp < 0) { 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_)) { return false; } } if (this->startup_.fwdErr) { if (!pipe(this->pipeStdErr_)) { return false; } } if (this->startup_.fwdIn) { if (!pipe(this->pipeStdIn_)) { 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; { //Threading::Primitives::Semaphore semaphore; pid = fork(); if (pid == 0) { if (this->startup_.fwdIn) { dup2(pipeStdIn_[0], STDIN_FILENO); close(AuExchange(pipeStdIn_[0], 0)); } if (this->startup_.fwdErr) { dup2(pipeStdErr_[1], STDERR_FILENO); close(AuExchange(pipeStdErr_[1], 0)); } if (this->startup_.fwdOut) { dup2(pipeStdOut_[1], STDOUT_FILENO); close(AuExchange(pipeStdOut_[1], 0)); } if (this->type_ != ESpawnType::eSpawnChildProcessWorker) { setsid(); } { AU_LOCK_GUARD(gSpinLock->AsWritable()); AuTryInsert(gPidLookupMap, getpid(), this); } this->alive_ = true; //semaphore.Unlock(); 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) { return false; } else { this->pidt_ = pid; //semaphore.Lock(); } } return true; } AUKN_SYM IProcess *SpawnNew(const StartupParmaters ¶ms) { auto ret = _new ProcessImpl(params); if (!ret) { return {}; } if (!ret->Init()) { delete ret; return {}; } return ret; } AUKN_SYM void SpawnRelease(IProcess *process) { AuSafeDelete(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() { signal(SIGCHLD, SigChldHandler); gRWLock = AuThreadPrimitives::RWLockUnique(); } void DeinitUnix() { gRWLock.reset(); } }