Skip to main content

starry_process/
process.rs

1use alloc::{
2    collections::btree_set::BTreeSet,
3    sync::{Arc, Weak},
4    vec::Vec,
5};
6use core::{
7    fmt,
8    sync::atomic::{AtomicBool, Ordering},
9};
10
11use ax_kspin::SpinNoIrq;
12use ax_lazyinit::LazyInit;
13use weak_map::StrongMap;
14
15use crate::{Pid, ProcessGroup, Session};
16
17#[derive(Default)]
18pub(crate) struct ThreadGroup {
19    pub(crate) threads: BTreeSet<Pid>,
20    pub(crate) exit_code: i32,
21    pub(crate) group_exited: bool,
22}
23
24/// A process.
25pub struct Process {
26    pid: Pid,
27    is_zombie: AtomicBool,
28    is_child_subreaper: AtomicBool,
29    pub(crate) tg: SpinNoIrq<ThreadGroup>,
30
31    children: SpinNoIrq<StrongMap<Pid, Arc<Process>>>,
32    parent: SpinNoIrq<Weak<Process>>,
33
34    group: SpinNoIrq<Arc<ProcessGroup>>,
35}
36
37impl Process {
38    /// The [`Process`] ID.
39    pub fn pid(&self) -> Pid {
40        self.pid
41    }
42
43    /// Returns `true` if the [`Process`] is the init process.
44    ///
45    /// This is a convenience method for checking if the [`Process`]
46    /// [`Arc::ptr_eq`]s with the init process, which is cheaper than
47    /// calling [`init_proc`] or testing if [`Process::parent`] is `None`.
48    pub fn is_init(self: &Arc<Self>) -> bool {
49        Arc::ptr_eq(self, INIT_PROC.get().unwrap())
50    }
51
52    /// Returns `true` if this process acts as a child subreaper.
53    ///
54    /// Linux keeps this flag per process: it is preserved across `execve`,
55    /// applies to all threads in the thread group, and is not inherited by
56    /// newly forked child processes.
57    pub fn is_child_subreaper(&self) -> bool {
58        self.is_child_subreaper.load(Ordering::Acquire)
59    }
60
61    /// Enables or disables child subreaper behavior for this process.
62    pub fn set_child_subreaper(&self, enabled: bool) {
63        self.is_child_subreaper.store(enabled, Ordering::Release);
64    }
65}
66
67/// Parent & children
68impl Process {
69    /// The parent [`Process`].
70    pub fn parent(&self) -> Option<Arc<Process>> {
71        self.parent.lock().upgrade()
72    }
73
74    /// The child [`Process`]es.
75    pub fn children(&self) -> Vec<Arc<Process>> {
76        self.children.lock().values().cloned().collect()
77    }
78}
79
80/// [`ProcessGroup`] & [`Session`]
81impl Process {
82    /// The [`ProcessGroup`] that the [`Process`] belongs to.
83    pub fn group(&self) -> Arc<ProcessGroup> {
84        self.group.lock().clone()
85    }
86
87    fn set_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) {
88        let mut self_group = self.group.lock();
89
90        self_group.processes.lock().remove(&self.pid);
91
92        group.processes.lock().insert(self.pid, self);
93
94        *self_group = group.clone();
95    }
96
97    /// Creates a new [`Session`] and new [`ProcessGroup`] and moves the
98    /// [`Process`] to it.
99    ///
100    /// If the [`Process`] is already a session leader, this method does
101    /// nothing and returns `None`.
102    ///
103    /// Otherwise, it returns the new [`Session`] and [`ProcessGroup`].
104    ///
105    /// The caller has to ensure that the new [`ProcessGroup`] does not conflict
106    /// with any existing [`ProcessGroup`]. Thus, the [`Process`] must not
107    /// be a [`ProcessGroup`] leader.
108    ///
109    /// Checking [`Session`] conflicts is unnecessary.
110    pub fn create_session(self: &Arc<Self>) -> Option<(Arc<Session>, Arc<ProcessGroup>)> {
111        if self.group.lock().session.sid() == self.pid {
112            return None;
113        }
114
115        let new_session = Session::new(self.pid);
116        let new_group = ProcessGroup::new(self.pid, &new_session);
117        self.set_group(&new_group);
118
119        Some((new_session, new_group))
120    }
121
122    /// Creates a new [`ProcessGroup`] and moves the [`Process`] to it.
123    ///
124    /// If the [`Process`] is already a group leader, this method does nothing
125    /// and returns `None`.
126    ///
127    /// Otherwise, it returns the new [`ProcessGroup`].
128    ///
129    /// The caller has to ensure that the new [`ProcessGroup`] does not conflict
130    /// with any existing [`ProcessGroup`].
131    pub fn create_group(self: &Arc<Self>) -> Option<Arc<ProcessGroup>> {
132        if self.group.lock().pgid() == self.pid {
133            return None;
134        }
135
136        let new_group = ProcessGroup::new(self.pid, &self.group.lock().session);
137        self.set_group(&new_group);
138
139        Some(new_group)
140    }
141
142    /// Moves the [`Process`] to a specified [`ProcessGroup`].
143    ///
144    /// Returns `true` if the move succeeded. The move failed if the
145    /// [`ProcessGroup`] is not in the same [`Session`] as the [`Process`].
146    ///
147    /// If the [`Process`] is already in the specified [`ProcessGroup`], this
148    /// method does nothing and returns `true`.
149    pub fn move_to_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) -> bool {
150        if Arc::ptr_eq(&self.group.lock(), group) {
151            return true;
152        }
153
154        if !Arc::ptr_eq(&self.group.lock().session, &group.session) {
155            return false;
156        }
157
158        self.set_group(group);
159        true
160    }
161}
162
163/// Threads
164impl Process {
165    /// Adds a thread to this [`Process`] with the given thread ID.
166    pub fn add_thread(self: &Arc<Self>, tid: Pid) {
167        self.tg.lock().threads.insert(tid);
168    }
169
170    /// Removes a thread from this [`Process`] and sets the exit code if the
171    /// group has not exited.
172    ///
173    /// Returns `true` if this was the last thread in the process.
174    pub fn exit_thread(self: &Arc<Self>, tid: Pid, exit_code: i32) -> bool {
175        let mut tg = self.tg.lock();
176        if !tg.group_exited {
177            tg.exit_code = exit_code;
178        }
179        tg.threads.remove(&tid);
180        tg.threads.is_empty()
181    }
182
183    /// Get all threads in this [`Process`].
184    pub fn threads(&self) -> Vec<Pid> {
185        self.tg.lock().threads.iter().cloned().collect()
186    }
187
188    /// Renames a thread in the thread group.
189    ///
190    /// Used by `execve`'s de_thread step when a non-leader thread successfully
191    /// `execve`s: the calling thread inherits the leader's TID so that
192    /// `gettid() == getpid()` holds in the new image. We swap `old_tid` for
193    /// `new_tid` atomically inside the thread-group lock so there is no
194    /// instant in which the caller is unrepresented in the group.
195    pub fn rename_thread(self: &Arc<Self>, old_tid: Pid, new_tid: Pid) {
196        let mut tg = self.tg.lock();
197        tg.threads.remove(&old_tid);
198        tg.threads.insert(new_tid);
199    }
200
201    /// Returns `true` if the [`Process`] is group exited.
202    pub fn is_group_exited(&self) -> bool {
203        self.tg.lock().group_exited
204    }
205
206    /// Marks the [`Process`] as group exited.
207    pub fn group_exit(&self) {
208        self.tg.lock().group_exited = true;
209    }
210
211    /// The exit code of the [`Process`].
212    pub fn exit_code(&self) -> i32 {
213        self.tg.lock().exit_code
214    }
215}
216
217/// Status & exit
218impl Process {
219    fn orphan_reaper(self: &Arc<Self>) -> Arc<Process> {
220        let init_proc = INIT_PROC.get().unwrap();
221        let mut cursor = self.parent();
222
223        while let Some(proc) = cursor {
224            if Arc::ptr_eq(&proc, init_proc) {
225                break;
226            }
227            if proc.is_child_subreaper() && !proc.is_zombie() {
228                return proc;
229            }
230            cursor = proc.parent();
231        }
232
233        init_proc.clone()
234    }
235
236    /// Returns `true` if the [`Process`] is a zombie process.
237    pub fn is_zombie(&self) -> bool {
238        self.is_zombie.load(Ordering::Acquire)
239    }
240
241    /// Terminates the [`Process`], marking it as a zombie process.
242    ///
243    /// Child processes are inherited by the init process or by the nearest
244    /// subreaper process.
245    ///
246    /// This method does nothing if the [`Process`] is the init process.
247    pub fn exit(self: &Arc<Self>) {
248        if self.is_init() {
249            return;
250        }
251
252        let reaper_proc = self.orphan_reaper();
253        let reaper_parent = Arc::downgrade(&reaper_proc);
254        let children = {
255            let mut children = self.children.lock();
256            core::mem::take(&mut *children)
257        };
258
259        let mut reaper_children = reaper_proc.children.lock();
260        for (pid, child) in children {
261            *child.parent.lock() = reaper_parent.clone();
262            reaper_children.insert(pid, child);
263        }
264        drop(reaper_children);
265
266        self.is_zombie.store(true, Ordering::Release);
267    }
268
269    /// Frees a zombie [`Process`]. Removes it from the parent.
270    ///
271    /// This method panics if the [`Process`] is not a zombie.
272    pub fn free(&self) {
273        assert!(self.is_zombie(), "only zombie process can be freed");
274
275        if let Some(parent) = self.parent() {
276            parent.children.lock().remove(&self.pid);
277        }
278    }
279}
280
281impl fmt::Debug for Process {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        let mut builder = f.debug_struct("Process");
284        builder.field("pid", &self.pid);
285
286        let tg = self.tg.lock();
287        if tg.group_exited {
288            builder.field("group_exited", &tg.group_exited);
289        }
290        if self.is_zombie() {
291            builder.field("exit_code", &tg.exit_code);
292        }
293
294        if let Some(parent) = self.parent() {
295            builder.field("parent", &parent.pid());
296        }
297        builder.field("group", &self.group());
298        builder.finish()
299    }
300}
301
302/// Builder
303impl Process {
304    fn new(pid: Pid, parent: Option<Arc<Process>>) -> Arc<Process> {
305        let group = parent.as_ref().map_or_else(
306            || {
307                let session = Session::new(pid);
308                ProcessGroup::new(pid, &session)
309            },
310            |p| p.group(),
311        );
312
313        let process = Arc::new(Process {
314            pid,
315            is_zombie: AtomicBool::new(false),
316            is_child_subreaper: AtomicBool::new(false),
317            tg: SpinNoIrq::new(ThreadGroup::default()),
318            children: SpinNoIrq::new(StrongMap::new()),
319            parent: SpinNoIrq::new(parent.as_ref().map(Arc::downgrade).unwrap_or_default()),
320            group: SpinNoIrq::new(group.clone()),
321        });
322
323        group.processes.lock().insert(pid, &process);
324
325        if let Some(parent) = parent {
326            parent.children.lock().insert(pid, process.clone());
327        } else {
328            INIT_PROC.init_once(process.clone());
329        }
330
331        process
332    }
333
334    /// Creates a init [`Process`].
335    ///
336    /// This function can be called multiple times, but
337    /// [`ProcessBuilder::build`] on the the result must be called only once.
338    pub fn new_init(pid: Pid) -> Arc<Process> {
339        Self::new(pid, None)
340    }
341
342    /// Creates a child [`Process`].
343    pub fn fork(self: &Arc<Process>, pid: Pid) -> Arc<Process> {
344        Self::new(pid, Some(self.clone()))
345    }
346}
347
348static INIT_PROC: LazyInit<Arc<Process>> = LazyInit::new();
349
350/// Gets the init process.
351///
352/// This function panics if the init process has not been initialized yet.
353pub fn init_proc() -> Arc<Process> {
354    INIT_PROC.get().unwrap().clone()
355}