Skip to main content

starnix_core/vfs/
pidfd.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::task::{
6    CurrentTask, EventHandler, Pid, ProcessEntryRef, SignalHandler, SignalHandlerInner,
7    WaitCanceler, Waiter,
8};
9use crate::vfs::{
10    Anon, FileHandle, FileObject, FileOps, fileops_impl_dataless, fileops_impl_nonseekable,
11    fileops_impl_noop_sync,
12};
13use fuchsia_async as fasync;
14use starnix_uapi::errors::Errno;
15use starnix_uapi::open_flags::OpenFlags;
16use starnix_uapi::vfs::FdEvents;
17use starnix_uapi::{error, from_status_like_fdio};
18
19pub struct PidFdFileObject {
20    /// The process represented by this file.
21    pid: Pid,
22
23    /// Receives a notification when the tracked process terminates.
24    ///
25    /// The peer is held by the task monitoring the process, which drops it once the process has
26    /// been fully released. Dropping this endpoint in turn tells that task to stop monitoring.
27    ///
28    /// `None` if the process was already terminated when the pidfd was created.
29    terminated_event: Option<zx::EventPair>,
30}
31
32impl PidFdFileObject {
33    /// Process identifier ([`Pid`]) tracked by this [`PidFdFileObject`].
34    pub(crate) fn pid(&self) -> &Pid {
35        &self.pid
36    }
37
38    fn get_signals_from_events(events: FdEvents) -> zx::Signals {
39        if events.contains(FdEvents::POLLIN) {
40            zx::Signals::EVENTPAIR_PEER_CLOSED
41        } else {
42            zx::Signals::NONE
43        }
44    }
45
46    fn get_events_from_signals(signals: zx::Signals) -> FdEvents {
47        let mut events = FdEvents::empty();
48
49        if signals.contains(zx::Signals::EVENTPAIR_PEER_CLOSED) {
50            events |= FdEvents::POLLIN;
51        }
52
53        events
54    }
55}
56
57/// Returns an event that is signalled with `EVENTPAIR_PEER_CLOSED` when the current memory manager
58/// of the process identified by `pid` is dropped.
59///
60/// Returns `None` if the process no longer has a reachable memory manager.
61fn get_memory_manager_drop_event(pid: &Pid) -> Option<zx::EventPair> {
62    let Some(ProcessEntryRef::Process(proc)) = pid.get_process() else {
63        return None;
64    };
65    let task = pid.get_task().or_else(|_| proc.read().get_running_task());
66    task.ok().and_then(|task| task.mm().ok()).map(|mm| mm.drop_notifier.event())
67}
68
69/// Waits until the process identified by `pid` has terminated and all of its resources have been
70/// released.
71///
72/// The memory manager is monitored first.  Once no memory manager remains, the Zircon process is
73/// monitored for termination.
74async fn wait_for_process_release(
75    pid: Pid,
76    initial_mm_event: Option<zx::EventPair>,
77    zx_process: zx::Process,
78) {
79    let mut mm_event = initial_mm_event;
80    while let Some(event) = mm_event {
81        if fasync::OnSignals::new(&event, zx::Signals::EVENTPAIR_PEER_CLOSED).await.is_err() {
82            break;
83        }
84
85        // The memory manager has been dropped. If the process has a new one, it was replaced by
86        // an `execve` and monitoring must continue with the new memory manager.
87        mm_event = get_memory_manager_drop_event(&pid);
88    }
89
90    let _ = fasync::OnSignals::new(&zx_process, zx::Signals::PROCESS_TERMINATED).await;
91}
92
93/// Signals the pidfd holding the peer of `local_event` once the process identified by `pid` has
94/// been fully released.
95///
96/// Stops early if the pidfd is closed first, which drops the peer and asserts
97/// `EVENTPAIR_PEER_CLOSED` on `local_event`.
98async fn monitor_pidfd(
99    pid: Pid,
100    initial_mm_event: Option<zx::EventPair>,
101    zx_process: zx::Process,
102    local_event: zx::EventPair,
103) {
104    let pidfd_closed =
105        std::pin::pin!(fasync::OnSignals::new(&local_event, zx::Signals::EVENTPAIR_PEER_CLOSED));
106    let released = std::pin::pin!(wait_for_process_release(pid, initial_mm_event, zx_process));
107    let _ = futures::future::select(pidfd_closed, released).await;
108
109    // Returning drops `local_event`, which signals `EVENTPAIR_PEER_CLOSED` on the peer, waking any
110    // poller still waiting on the pidfd.
111}
112
113pub fn new_pidfd(
114    current_task: &CurrentTask,
115    pid: Pid,
116    flags: OpenFlags,
117) -> Result<FileHandle, Errno> {
118    let terminated_event = match pid.get_process() {
119        Some(ProcessEntryRef::Process(proc)) => {
120            let zx_process = proc
121                .process
122                .duplicate_handle(zx::Rights::SAME_RIGHTS)
123                .map_err(|status| from_status_like_fdio!(status))?;
124            // Look up the memory manager here rather than in the monitoring task: the process is
125            // known to be alive at this point, whereas it may already have been zombified, and
126            // hence have an unreachable memory manager, by the time the task first runs.
127            let initial_mm_event = get_memory_manager_drop_event(&pid);
128            let (local_event, terminated_event) = zx::EventPair::create();
129            let monitored_pid = pid.clone();
130
131            current_task.kernel().kthreads.spawn_future(
132                move || monitor_pidfd(monitored_pid, initial_mm_event, zx_process, local_event),
133                "pidfd-monitor",
134            );
135
136            Some(terminated_event)
137        }
138        Some(ProcessEntryRef::Zombie) => None,
139        None => {
140            if pid.get_task().is_ok_and(|task| !task.is_leader()) {
141                return error!(EINVAL);
142            }
143            return error!(ESRCH);
144        }
145    };
146
147    Ok(Anon::new_private_file(
148        current_task,
149        Box::new(PidFdFileObject { pid, terminated_event }),
150        flags,
151        "[pidfd]",
152    ))
153}
154
155impl FileOps for PidFdFileObject {
156    fileops_impl_nonseekable!();
157    fileops_impl_dataless!();
158    fileops_impl_noop_sync!();
159
160    fn as_pid(&self, _file: &FileObject) -> Result<Pid, Errno> {
161        Ok(self.pid.clone())
162    }
163
164    fn wait_async(
165        &self,
166        _file: &FileObject,
167        _current_task: &CurrentTask,
168        waiter: &Waiter,
169        events: FdEvents,
170        handler: EventHandler,
171    ) -> Option<WaitCanceler> {
172        let terminated_event = self.terminated_event.as_ref()?;
173        let signal_handler = SignalHandler {
174            inner: SignalHandlerInner::ZxHandle(PidFdFileObject::get_events_from_signals),
175            event_handler: handler,
176            err_code: None,
177        };
178        let canceler = waiter
179            .wake_on_zircon_signals(
180                terminated_event,
181                PidFdFileObject::get_signals_from_events(events),
182                signal_handler,
183            )
184            .unwrap(); // errors cannot happen unless the kernel is out of memory
185        Some(WaitCanceler::new_port(canceler))
186    }
187
188    fn query_events(
189        &self,
190        _file: &FileObject,
191        _current_task: &CurrentTask,
192    ) -> Result<FdEvents, Errno> {
193        let Some(terminated_event) = &self.terminated_event else {
194            return Ok(FdEvents::POLLIN);
195        };
196        match terminated_event
197            .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
198            .to_result()
199        {
200            Err(zx::Status::TIMED_OUT) => Ok(FdEvents::empty()),
201            Ok(zx::Signals::EVENTPAIR_PEER_CLOSED) => Ok(FdEvents::POLLIN),
202            result => unreachable!("unexpected result: {result:?}"),
203        }
204    }
205}