// This source code is released into the public domain. module; #include #include #include #include #include export module nihil.posix:process; import nihil.error; namespace nihil { // wait_result: the exit status of a process. export struct wait_result final { // Return true if the process exited normally with an exit code of // zero, otherwise false. [[nodiscard]] auto okay(this wait_result const &self) -> bool { return self.status() == 0; } [[nodiscard]] explicit operator bool(this wait_result const &self) { return self.okay(); } // Return the exit status, if any. [[nodiscard]] auto status(this wait_result const &self) -> std::optional { if (WIFEXITED(self.m_status)) return WEXITSTATUS(self.m_status); return {}; } // Return the exit signal, if any. [[nodiscard]] auto signal(this wait_result const &self) -> std::optional { if (WIFSIGNALED(self.m_status)) return WTERMSIG(self.m_status); return {}; } private: friend struct process; int m_status; // Construct a new wait_result from the output of waitpid(). explicit wait_result(int status) : m_status(status) { } }; // Represents a process we created, which can be waited for. export struct process final { process() = delete; // Create a new process from a pid, which must be a child of the // current process. explicit process(::pid_t pid) : m_pid(pid) { } // When destroyed, we automatically wait for the process to // avoid creating zombie processes. ~process() { if (m_pid == -1) return; auto status = int{}; std::ignore = ::waitpid(m_pid, &status, WEXITED); } // Movable. process(process &&other) noexcept : m_pid(std::exchange(other.m_pid, -1)) {} auto operator=(this process &self, process &&other) noexcept -> process & { if (&self != &other) { self.m_pid = std::exchange(other.m_pid, -1); } return self; // NOLINT } // Not copyable. process(process const &) = delete; auto operator=(this process &, process const &) -> process & = delete; // Get the child's process id. [[nodiscard]] auto pid(this process const &self) noexcept -> ::pid_t { return self.m_pid; } // Wait for this process to exit (by calling waitpid()) and return // its exit status. This destroys the process state, leaving this // object in a moved-from state. [[nodiscard]] auto wait(this process &&self) // NOLINT -> std::expected { auto status = int{}; auto ret = waitpid(self.m_pid, &status, 0); if (ret == -1) return std::unexpected(error(std::errc(errno))); return wait_result(status); } // Release this process so we won't try to wait for it when // destroying this object. This will leave a zombie process // unless the wait is done another way. [[nodiscard]] auto release(this process &&self) -> ::pid_t // NOLINT { auto const ret = self.pid(); self.m_pid = -1; return ret; } private: ::pid_t m_pid; }; } // namespace nihil