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