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::mm::MemoryManager;
6use crate::task::{
7    CurrentTask, EventHandler, SignalHandler, SignalHandlerInner, ThreadGroup, ThreadGroupKey,
8    WaitCanceler, Waiter,
9};
10use crate::vfs::{
11    Anon, FileHandle, FileObject, FileOps, fileops_impl_dataless, fileops_impl_nonseekable,
12    fileops_impl_noop_sync,
13};
14use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked};
15use starnix_uapi::error;
16use starnix_uapi::errors::Errno;
17use starnix_uapi::open_flags::OpenFlags;
18use starnix_uapi::vfs::FdEvents;
19
20pub struct PidFdFileObject {
21    /// The key of the task represented by this file.
22    tg: ThreadGroupKey,
23
24    // Receives a notification when the tracked process terminates.
25    terminated_event: zx::EventPair,
26}
27
28impl PidFdFileObject {
29    fn get_signals_from_events(events: FdEvents) -> zx::Signals {
30        if events.contains(FdEvents::POLLIN) {
31            zx::Signals::EVENTPAIR_PEER_CLOSED
32        } else {
33            zx::Signals::NONE
34        }
35    }
36
37    fn get_events_from_signals(signals: zx::Signals) -> FdEvents {
38        let mut events = FdEvents::empty();
39
40        if signals.contains(zx::Signals::EVENTPAIR_PEER_CLOSED) {
41            events |= FdEvents::POLLIN;
42        }
43
44        events
45    }
46}
47
48pub fn new_pidfd<L>(
49    locked: &mut Locked<L>,
50    current_task: &CurrentTask,
51    proc: &ThreadGroup,
52    mm: &MemoryManager,
53    flags: OpenFlags,
54) -> FileHandle
55where
56    L: LockEqualOrBefore<FileOpsCore>,
57{
58    // We should really be monitoring the ThreadGroup's drop_notifier instead, but we also need to
59    // ensure that we're not signalling the pidfd until after all memory resources associated with
60    // the process are released. In the current Starnix codebase, there is a 1:1 correspondence
61    // between ThreadGroups (i.e. processes) and MemoryManagers, and the MemoryManager of a process
62    // may outlive the ThreadGroup in some circumstances. Therefore, as a temporary workaround, here
63    // we monitor the MemoryManager's drop_notifier, which is guaranteed to only fire when all the
64    // memory mappings associated with the process have been released. To be revisited once Starnix
65    // implements explicit cleanup of resources on process exit.
66    let terminated_event = mm.drop_notifier.event();
67
68    Anon::new_private_file(
69        locked,
70        current_task,
71        Box::new(PidFdFileObject { tg: proc.into(), terminated_event }),
72        flags,
73        "[pidfd]",
74    )
75}
76
77impl FileOps for PidFdFileObject {
78    fileops_impl_nonseekable!();
79    fileops_impl_dataless!();
80    fileops_impl_noop_sync!();
81
82    fn as_thread_group_key(&self, _file: &FileObject) -> Result<ThreadGroupKey, Errno> {
83        Ok(self.tg.clone())
84    }
85
86    fn wait_async(
87        &self,
88        _locked: &mut Locked<FileOpsCore>,
89        _file: &FileObject,
90        _current_task: &CurrentTask,
91        waiter: &Waiter,
92        events: FdEvents,
93        handler: EventHandler,
94    ) -> Option<WaitCanceler> {
95        let signal_handler = SignalHandler {
96            inner: SignalHandlerInner::ZxHandle(PidFdFileObject::get_events_from_signals),
97            event_handler: handler,
98            err_code: None,
99        };
100        let canceler = waiter
101            .wake_on_zircon_signals(
102                &self.terminated_event,
103                PidFdFileObject::get_signals_from_events(events),
104                signal_handler,
105            )
106            .unwrap(); // errors cannot happen unless the kernel is out of memory
107        Some(WaitCanceler::new_port(canceler))
108    }
109
110    fn query_events(
111        &self,
112        _locked: &mut Locked<FileOpsCore>,
113        _file: &FileObject,
114        _current_task: &CurrentTask,
115    ) -> Result<FdEvents, Errno> {
116        match self
117            .terminated_event
118            .wait_one(zx::Signals::EVENTPAIR_PEER_CLOSED, zx::MonotonicInstant::ZERO)
119            .to_result()
120        {
121            Err(zx::Status::TIMED_OUT) => Ok(FdEvents::empty()),
122            Ok(zx::Signals::EVENTPAIR_PEER_CLOSED) => Ok(FdEvents::POLLIN),
123            result => unreachable!("unexpected result: {result:?}"),
124        }
125    }
126}
127
128pub fn new_zombie_pidfd<L>(
129    locked: &mut Locked<L>,
130    current_task: &CurrentTask,
131    flags: OpenFlags,
132) -> FileHandle
133where
134    L: LockEqualOrBefore<FileOpsCore>,
135{
136    Anon::new_private_file(
137        locked,
138        current_task,
139        Box::new(ZombiePidFdFileObject {}),
140        flags,
141        "[pidfd]",
142    )
143}
144
145struct ZombiePidFdFileObject {}
146
147impl FileOps for ZombiePidFdFileObject {
148    fileops_impl_nonseekable!();
149    fileops_impl_dataless!();
150    fileops_impl_noop_sync!();
151
152    fn as_thread_group_key(&self, _file: &FileObject) -> Result<ThreadGroupKey, Errno> {
153        // There's nothing really reasonable to return here?
154        error!(EINVAL)
155    }
156
157    fn wait_async(
158        &self,
159        _locked: &mut Locked<FileOpsCore>,
160        _file: &FileObject,
161        _current_task: &CurrentTask,
162        _waiter: &Waiter,
163        _events: FdEvents,
164        _handler: EventHandler,
165    ) -> Option<WaitCanceler> {
166        // There's nothing to wait on; is denying blocking sufficient?
167        None
168    }
169
170    fn query_events(
171        &self,
172        _locked: &mut Locked<FileOpsCore>,
173        _file: &FileObject,
174        _current_task: &CurrentTask,
175    ) -> Result<FdEvents, Errno> {
176        Ok(FdEvents::POLLIN)
177    }
178}