/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Process.Linux.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "Processes.hpp" #include "Process.Linux.hpp" #include #include #include #include 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 args); ~ProcessImpl(); void ShutdownPipes(); bool TryKill() override; bool Terminate() override; AuSPtr AsWaitable() override; AuSInt GetExitCode() override; bool Read (bool error, void *buffer, AuUInt32 &len) 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 args_; AuList cargs_; AuString windows_; Threading::Threads::ThreadUnique_t thread_; std::atomic handle_; AuSInt exitCode_; }; ProcessImpl::ProcessImpl(AuString cmd, AuList 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 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) { len = 0; auto handle = error ? pipeStdErr_[0] : pipeStdOut_[0]; if (handle < 0) return false; 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 &args) { return _new ProcessImpl(app, args); } AUKN_SYM void SpawnRelease(IProcess *process) { SafeDelete(process); } }