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