aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.posix/process.ccm
blob: ee7de156f95ff4221edfaa884aefa47798760060 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// This source code is released into the public domain.
module;

#include <expected>
#include <optional>
#include <system_error>
#include <utility>

#include <sys/wait.h>

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<int>
	{
		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<int>
	{
		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<wait_result, error>
	{
		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