/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuProcess.Unix.cpp File: Process.Linux.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "AuProcesses.hpp" #include "AuProcess.Unix.hpp" #include #include #include #include #include #include #include #if defined(AURORA_COMPILER_CLANG) // warning: enumeration values 'kEnumCount' and 'kEnumInvalid' not handled in switch [-Wswitch #pragma clang diagnostic ignored "-Wswitch" // Yea, I don't give a shit. #endif #if defined(AURORA_IS_LINUX_DERIVED) #include #include #if !defined(CLONE_CLEAR_SIGHAND) #define CLONE_CLEAR_SIGHAND 0x100000000ULL #endif #endif namespace Aurora::Processes { static AuThreadPrimitives::RWLockUnique_t gRWLock; static AuHashMap gPidLookupMap; struct ProcessAliveLoopSource : AuLoop::LSEvent { ProcessAliveLoopSource(); virtual AuLoop::ELoopSource GetType() override; }; ProcessAliveLoopSource::ProcessAliveLoopSource() : LSEvent(false, false, true) {} AuLoop::ELoopSource ProcessAliveLoopSource::GetType() { return AuLoop::ELoopSource::eProcessDead; } ProcessImpl::ProcessImpl(StartupParameters &¶ms) : startup_(AuMove(params)) { AuIOFS::NormalizePath(this->startup_.process, this->startup_.process); if (this->startup_.workingDirectory) { AuString a; AuIOFS::NormalizePath(a, this->startup_.workingDirectory.value()); this->startup_.workingDirectory = a; } 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(); } if (this->loopSource_) { this->loopSource_->Set(); } } void ProcessImpl::ShutdownPipes() { if (!this->bDontRelOut_) { if (auto fd = AuExchange(pipeStdOut_[1], {})) { ::close(fd); } } if (!this->bDontRelErr_) { if (auto fd = AuExchange(pipeStdErr_[1], {})) { ::close(fd); } } if (!this->bDontRelIn_) { if (auto fd = AuExchange(pipeStdIn_[0], {})) { ::close(fd); } } } bool ProcessImpl::TryKill() { AU_LOCK_GUARD(gRWLock->AsReadable()); if (this->alive_) { if (::kill(this->pidt_, SIGTERM) == 0) { return this->finished_->LockMS(500); } } 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_; } AuSPtr ProcessImpl::AsLoopSource() { return this->loopSource_; } AuSInt ProcessImpl::GetExitCode() { return this->exitCode_; } bool ProcessImpl::Read(EStandardHandle stream, const AuMemoryViewStreamWrite &destination, bool nonblock) { if (!IO::EStandardStreamIsValid(stream) || (stream == EStandardHandle::eInputStream)) { SysPushErrorArg("Invalid Stream"); return false; } if (!destination) { SysPushErrorArg(); return false; } auto handle = stream == EStandardHandle::eErrorStream ? 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; } auto control = ::fcntl(handle, F_GETFL); auto ref = control; if (/*nonblock*/ true) { control |= O_NONBLOCK; } else { control &= ~O_NONBLOCK; } if (ref != control) { ::fcntl(handle, F_SETFL, control); } return ::write(handle, source.ptr, source.length) == source.length; } static bool InitProcessStdHandles(EStreamForward fwd, int *fds, AuIO::EStandardStream stream, IO::IIOHandle *pHandle) { AuOptionalEx optHandle; bool bIsRead = stream == AuIO::EStandardStream::eInputStream; switch (fwd) { case EStreamForward::eIOHandle: optHandle = pHandle->GetOSHandleSafe(); if (!optHandle) { return false; } if (bIsRead) { fds[0] = dup((int)optHandle.value()); } else { fds[1] = dup((int)optHandle.value()); } break; case EStreamForward::eCurrentProcess: if (stream == AuIO::EStandardStream::eInputStream) { fds[0] = dup(STDIN_FILENO); if (fds[0] < 0) { return false; } } else if (stream == AuIO::EStandardStream::eErrorStream) { fds[1] = dup(STDERR_FILENO); if (fds[1] < 0) { return false; } } else if (stream == AuIO::EStandardStream::eOutputStream) { fds[1] = dup(STDOUT_FILENO); if (fds[1] < 0) { return false; } } else { return false; } break; case EStreamForward::eAsyncPipe: if (::pipe(fds)) { SysPushErrorMem(); return false; } break; case EStreamForward::eNull: if (bIsRead) { fds[0] = ::open("/dev/null", O_RDWR); } else { fds[1] = ::open("/dev/null", O_RDWR); } break; case EStreamForward::eNewConsoleWindow: SysPushErrorGeneric("AuProcesses is not the right place for PTY support. At least not in this form (this level of abstraction only cares for pipes)."); return false; } if (fds[0] > 0) { if (!AuIO::SetFDShareAccess(fds[0], bIsRead)) { return false; } } if (fds[1] > 0) { if (!AuIO::SetFDShareAccess(fds[1], !bIsRead)) { return false; } } // Yea, I guess we can't really guard against threads racing to leak close on exec-less fds // Let's just not care on UNIX. // Worst case, we leak a daemons input/output FD to a different daemon of a differing permission level // ...and somehow you can find that fd // ...and probably dup it before ::start() // ...and issue the right commands over the stream to do something bad // Shouldn't need to worry about his for a while. return true; } bool ProcessImpl::Init() { InitProcessStdHandles(this->startup_.fwdOut, this->pipeStdOut_, AuIO::EStandardStream::eOutputStream, this->startup_.handleOutStream); InitProcessStdHandles(this->startup_.fwdErr, this->pipeStdErr_, AuIO::EStandardStream::eErrorStream, this->startup_.handleErrorStream); InitProcessStdHandles(this->startup_.fwdIn, this->pipeStdIn_, AuIO::EStandardStream::eInputStream, this->startup_.handleInputStream); this->loopSource_ = AuMakeShared(); if (!this->loopSource_) { return false; } this->finished_ = AuThreadPrimitives::EventShared(false, false, true); if (!this->finished_) { return false; } if ((this->startup_.fwdIn == EStreamForward::eAsyncPipe) || (this->startup_.fwdOut == EStreamForward::eAsyncPipe)) { this->fsHandle_ = AuIO::IOHandleShared(); if (!this->fsHandle_) { return false; } this->fsStream_ = AuMakeShared(); if (!this->fsStream_) { return false; } this->fsHandle_->InitFromPairMove(this->pipeStdOut_[0] == -1 ? AuOptionalEx {} : AuOptionalEx { (AuUInt64)this->pipeStdOut_[0] } , this->pipeStdIn_[1] == -1 ? AuOptionalEx {} : AuOptionalEx { (AuUInt64)this->pipeStdIn_[1] }); this->fsStream_->Init(this->fsHandle_); } if (this->startup_.fwdErr == EStreamForward::eAsyncPipe) { this->fsErrorHandle_ = AuIO::IOHandleShared(); if (!this->fsErrorHandle_) { return false; } this->fsErrorStream_ = AuMakeShared(); if (!this->fsErrorStream_) { return false; } this->fsErrorHandle_->InitFromPairMove(this->pipeStdErr_[0] == -1 ? AuOptionalEx {} : AuOptionalEx { (AuUInt64)this->pipeStdErr_[0] }, {}); this->fsErrorStream_->Init(this->fsErrorHandle_); } return true; } AuSPtr ProcessImpl::NewAsyncTransaction() { return this->fsStream_ ? this->fsStream_->NewTransaction() : AuSPtr {}; } AuSPtr ProcessImpl::NewErrorStreamAsyncTransaction() { return this->fsErrorStream_ ? this->fsErrorStream_->NewTransaction() : AuSPtr {}; } void ProcessImpl::ForkMain() { { ::dup2(this->pipeStdIn_[0], STDIN_FILENO); ::close(this->pipeStdIn_[0]); } { ::dup2(this->pipeStdErr_[1], STDERR_FILENO); ::close(this->pipeStdErr_[1]); } { ::dup2(this->pipeStdOut_[1], STDOUT_FILENO); ::close(this->pipeStdOut_[1]); } #if defined(AURORA_IS_LINUX_DERIVED) if (this->type_ == ESpawnType::eSpawnChildProcessWorker) { ::prctl(PR_SET_PDEATHSIG, SIGTERM); } #endif if (this->type_ != ESpawnType::eSpawnOvermap) { ::setsid(); } if (!this->startup_.bInheritEnvironmentVariables) { try { AuList keys; for (const auto &[key, value] : AuProcess::EnvironmentGetAll()) { keys.push_back(key); } AuProcess::EnvironmentRemoveMany(keys); } catch (...) { SysPanic("Couldn't fork"); } } if (this->startup_.environmentVariables.size()) { SysAssert(AuProcess::EnvironmentSetMany(this->startup_.environmentVariables)); } #if defined(AURORA_IS_XNU_DERIVED) if (this->startup_.workingDirectory) { ::pthread_chdir_np(this->startup_.workingDirectory.value().c_str()); } #else if (this->startup_.workingDirectory) { ::chroot(this->startup_.workingDirectory.value().c_str()); } #endif ::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. Launch: {} ({})", this->startup_.process, this->debug_); } bool ProcessImpl::Start() { this->exitCode_ = 0x10110100; if (this->type_ == ESpawnType::eSpawnOvermap) { this->ForkMain(); return false; } pid_t pid; { pid = ::fork(); if (pid == 0) { this->ForkMain(); 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(StartupParameters &¶ms) { auto ret = _new ProcessImpl(AuMove(params)); if (!ret) { SysPushErrorMem(); return {}; } if (!ret->Init()) { SysPushErrorNested(); 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() { 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(); } }