Skip to main content

starnix_modules_procfs/
pid_directory.rs

1// Copyright 2021 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 itertools::Itertools;
6use regex_lite::Regex;
7use starnix_core::mm::{
8    MemoryAccessor, MemoryAccessorExt, MemoryManager, MemoryStats, PAGE_SIZE, ProcMapsFile,
9    ProcSmapsFile,
10};
11use starnix_core::security;
12use starnix_core::task::{
13    CurrentTask, Task, TaskPersistentInfo, TaskStateCode, ThreadGroup, ThreadGroupKey,
14    path_from_root,
15};
16use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
17use starnix_core::vfs::pseudo::dynamic_file::{DynamicFile, DynamicFileBuf, DynamicFileSource};
18use starnix_core::vfs::pseudo::simple_directory::SimpleDirectory;
19use starnix_core::vfs::pseudo::simple_file::{
20    BytesFile, BytesFileOps, SimpleFileNode, parse_i32_file, parse_unsigned_file,
21    serialize_for_file,
22};
23use starnix_core::vfs::pseudo::stub_empty_file::StubEmptyFile;
24use starnix_core::vfs::pseudo::vec_directory::{VecDirectory, VecDirectoryEntry};
25use starnix_core::vfs::{
26    CallbackSymlinkNode, CloseFreeSafe, DirectoryEntryType, DirentSink, FdNumber, FileObject,
27    FileOps, FileSystemHandle, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString,
28    ProcMountinfoFile, ProcMountsFile, SeekTarget, SymlinkTarget, default_seek, emit_dotdot,
29    fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_seekable,
30    fileops_impl_unbounded_seek, fs_node_impl_dir_readonly,
31};
32use starnix_logging::{bug_ref, track_stub};
33
34use starnix_task_command::TaskCommand;
35use starnix_types::time::duration_to_scheduler_clock;
36use starnix_uapi::auth::{
37    CAP_SYS_NICE, CAP_SYS_RESOURCE, PTRACE_MODE_ATTACH_FSCREDS, PTRACE_MODE_NOAUDIT,
38    PTRACE_MODE_READ_FSCREDS, PtraceAccessMode,
39};
40use starnix_uapi::device_id::DeviceId;
41use starnix_uapi::errors::Errno;
42use starnix_uapi::file_mode::{Access, FileMode, mode};
43use starnix_uapi::open_flags::OpenFlags;
44use starnix_uapi::resource_limits::Resource;
45use starnix_uapi::user_address::UserAddress;
46use starnix_uapi::{
47    OOM_ADJUST_MIN, OOM_DISABLE, OOM_SCORE_ADJ_MIN, RLIM_INFINITY, errno, error, ino_t, off_t,
48    pid_t, uapi,
49};
50use std::borrow::Cow;
51use std::ops::{Deref, Range};
52use std::sync::{Arc, LazyLock, Weak};
53
54/// Loads entries for the `scope` of a task.
55fn task_entries(scope: TaskEntryScope) -> Vec<(FsString, FileMode)> {
56    // NOTE: keep entries in sync with `TaskDirectory::lookup()`.
57    let mut entries = vec![
58        (b"cgroup".into(), mode!(IFREG, 0o444)),
59        (b"cwd".into(), mode!(IFLNK, 0o777)),
60        (b"exe".into(), mode!(IFLNK, 0o777)),
61        (b"fd".into(), mode!(IFDIR, 0o500)),
62        (b"fdinfo".into(), mode!(IFDIR, 0o555)),
63        (b"io".into(), mode!(IFREG, 0o400)),
64        (b"limits".into(), mode!(IFREG, 0o444)),
65        (b"maps".into(), mode!(IFREG, 0o444)),
66        (b"mem".into(), mode!(IFREG, 0o600)),
67        (b"root".into(), mode!(IFLNK, 0o777)),
68        (b"sched".into(), mode!(IFREG, 0o644)),
69        (b"schedstat".into(), mode!(IFREG, 0o444)),
70        (b"smaps".into(), mode!(IFREG, 0o444)),
71        (b"stat".into(), mode!(IFREG, 0o444)),
72        (b"statm".into(), mode!(IFREG, 0o444)),
73        (b"status".into(), mode!(IFREG, 0o444)),
74        (b"cmdline".into(), mode!(IFREG, 0o444)),
75        (b"environ".into(), mode!(IFREG, 0o400)),
76        (b"auxv".into(), mode!(IFREG, 0o400)),
77        (b"comm".into(), mode!(IFREG, 0o644)),
78        (b"attr".into(), mode!(IFDIR, 0o555)),
79        (b"ns".into(), mode!(IFDIR, 0o511)),
80        (b"mountinfo".into(), mode!(IFREG, 0o444)),
81        (b"mounts".into(), mode!(IFREG, 0o444)),
82        (b"oom_adj".into(), mode!(IFREG, 0o744)),
83        (b"oom_score".into(), mode!(IFREG, 0o444)),
84        (b"oom_score_adj".into(), mode!(IFREG, 0o744)),
85        (b"timerslack_ns".into(), mode!(IFREG, 0o666)),
86        (b"wchan".into(), mode!(IFREG, 0o444)),
87        (b"clear_refs".into(), mode!(IFREG, 0o200)),
88        (b"pagemap".into(), mode!(IFREG, 0o400)),
89    ];
90
91    if scope == TaskEntryScope::ThreadGroup {
92        entries.push((b"task".into(), mode!(IFDIR, 0o555)));
93    }
94
95    entries
96}
97
98#[derive(Copy, Clone, Eq, PartialEq)]
99pub enum TaskEntryScope {
100    Task,
101    ThreadGroup,
102}
103
104/// Represents a directory node for either `/proc/<pid>` or `/proc/<pid>/task/<tid>`.
105///
106/// This directory lazily creates its child entries to save memory.
107///
108/// It pre-allocates a range of inode numbers (`inode_range`) for all its child entries to mark
109/// them as unchanged when re-accessed.
110/// The `creds` stored within is applied to the directory node itself and child entries.
111pub struct TaskDirectory {
112    task_weak: Weak<Task>,
113    scope: TaskEntryScope,
114    inode_range: Range<ino_t>,
115}
116
117#[derive(Clone)]
118struct TaskDirectoryNode(Arc<TaskDirectory>);
119
120impl Deref for TaskDirectoryNode {
121    type Target = TaskDirectory;
122
123    fn deref(&self) -> &Self::Target {
124        &self.0
125    }
126}
127
128impl TaskDirectory {
129    fn new(fs: &FileSystemHandle, task: &Arc<Task>, scope: TaskEntryScope) -> FsNodeHandle {
130        let creds = task.real_creds().euid_as_fscred();
131        let task_weak = Arc::downgrade(task);
132        fs.create_node_and_allocate_node_id(
133            TaskDirectoryNode(Arc::new(TaskDirectory {
134                task_weak,
135                scope,
136                inode_range: fs.allocate_ino_range(task_entries(scope).len()),
137            })),
138            FsNodeInfo::new(mode!(IFDIR, 0o555), creds),
139        )
140    }
141}
142
143impl FsNodeOps for TaskDirectoryNode {
144    fs_node_impl_dir_readonly!();
145
146    fn create_file_ops(
147        &self,
148        _node: &FsNode,
149        _current_task: &CurrentTask,
150        _flags: OpenFlags,
151    ) -> Result<Box<dyn FileOps>, Errno> {
152        Ok(Box::new(self.clone()))
153    }
154
155    fn lookup(
156        &self,
157        node: &FsNode,
158        _current_task: &CurrentTask,
159        name: &FsStr,
160    ) -> Result<FsNodeHandle, Errno> {
161        let task_weak = self.task_weak.clone();
162        let creds = node.info().cred();
163        let fs = node.fs();
164        let (mode, ino) = task_entries(self.scope)
165            .into_iter()
166            .enumerate()
167            .find_map(|(index, (n, mode))| {
168                if name == *n {
169                    Some((mode, self.inode_range.start + index as ino_t))
170                } else {
171                    None
172                }
173            })
174            .ok_or_else(|| errno!(ENOENT))?;
175
176        // NOTE: keep entries in sync with `task_entries()`.
177        let ops: Box<dyn FsNodeOps> = match &**name {
178            b"cgroup" => Box::new(CgroupFile::new_node(task_weak)),
179            b"cwd" => Box::new(CallbackSymlinkNode::new({
180                move || {
181                    Ok(SymlinkTarget::Node(
182                        Task::from_weak(&task_weak)?.running_state()?.fs().cwd(),
183                    ))
184                }
185            })),
186            b"exe" => Box::new(CallbackSymlinkNode::new({
187                move || {
188                    let task = Task::from_weak(&task_weak)?;
189                    if let Some(node) = task.mm().ok().and_then(|mm| mm.executable_node()) {
190                        Ok(SymlinkTarget::Node(node))
191                    } else {
192                        error!(ENOENT)
193                    }
194                }
195            })),
196            b"fd" => Box::new(FdDirectory::new(task_weak)),
197            b"fdinfo" => Box::new(FdInfoDirectory::new(task_weak)),
198            b"io" => Box::new(IoFile::new_node()),
199            b"limits" => Box::new(LimitsFile::new_node(task_weak)),
200            b"maps" => Box::new(PtraceCheckedNode::new_node(
201                task_weak,
202                PTRACE_MODE_READ_FSCREDS,
203                |_, task| Ok(ProcMapsFile::new(task)),
204            )),
205            b"mem" => Box::new(MemFile::new_node(task_weak)),
206            b"root" => Box::new(CallbackSymlinkNode::new({
207                move || {
208                    Ok(SymlinkTarget::Node(
209                        Task::from_weak(&task_weak)?.running_state()?.fs().root(),
210                    ))
211                }
212            })),
213            b"sched" => Box::new(StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322893980"))),
214            b"schedstat" => {
215                Box::new(StubEmptyFile::new_node(bug_ref!("https://fxbug.dev/322894256")))
216            }
217            b"smaps" => Box::new(PtraceCheckedNode::new_node(
218                task_weak,
219                PTRACE_MODE_READ_FSCREDS,
220                |_, task| Ok(ProcSmapsFile::new(task)),
221            )),
222            b"stat" => Box::new(StatFile::new_node(task_weak, self.scope)),
223            b"statm" => Box::new(StatmFile::new_node(task_weak)),
224            b"status" => Box::new(StatusFile::new_node(task_weak)),
225            b"cmdline" => Box::new(CmdlineFile::new_node(task_weak)),
226            b"environ" => Box::new(EnvironFile::new_node(task_weak)),
227            b"auxv" => Box::new(AuxvFile::new_node(task_weak)),
228            b"comm" => {
229                let task = self.task_weak.upgrade().ok_or_else(|| errno!(ESRCH))?;
230                Box::new(CommFile::new_node(task_weak, task.persistent_info.clone()))
231            }
232            b"attr" => {
233                let dir = SimpleDirectory::new();
234                dir.edit(&fs, |dir| {
235                    for (attr, name) in [
236                        (security::ProcAttr::Current, "current"),
237                        (security::ProcAttr::Exec, "exec"),
238                        (security::ProcAttr::FsCreate, "fscreate"),
239                        (security::ProcAttr::KeyCreate, "keycreate"),
240                        (security::ProcAttr::SockCreate, "sockcreate"),
241                    ] {
242                        dir.entry_etc(
243                            name.into(),
244                            AttrNode::new(task_weak.clone(), attr),
245                            mode!(IFREG, 0o666),
246                            DeviceId::NONE,
247                            creds,
248                        );
249                    }
250                    dir.entry_etc(
251                        "prev".into(),
252                        AttrNode::new(task_weak, security::ProcAttr::Previous),
253                        mode!(IFREG, 0o444),
254                        DeviceId::NONE,
255                        creds,
256                    );
257                });
258                Box::new(dir)
259            }
260            b"ns" => Box::new(NsDirectory { task: task_weak }),
261            b"mountinfo" => Box::new(ProcMountinfoFile::new_node(task_weak)),
262            b"mounts" => Box::new(ProcMountsFile::new_node(task_weak)),
263            b"oom_adj" => Box::new(OomAdjFile::new_node(task_weak)),
264            b"oom_score" => Box::new(OomScoreFile::new_node(task_weak)),
265            b"oom_score_adj" => Box::new(OomScoreAdjFile::new_node(task_weak)),
266            b"timerslack_ns" => Box::new(TimerslackNsFile::new_node(task_weak)),
267            b"wchan" => Box::new(BytesFile::new_node(b"0".to_vec())),
268            b"clear_refs" => Box::new(ClearRefsFile::new_node(task_weak)),
269            b"pagemap" => Box::new(PtraceCheckedNode::new_node(
270                task_weak,
271                PTRACE_MODE_READ_FSCREDS,
272                |_, _| Ok(StubEmptyFile::new(bug_ref!("https://fxbug.dev/452096300"))),
273            )),
274            b"task" => {
275                let task = self.task_weak.upgrade().ok_or_else(|| errno!(ESRCH))?;
276                Box::new(TaskListDirectory { thread_group: Arc::downgrade(&task.thread_group()) })
277            }
278            name => unreachable!(
279                "entry \"{:?}\" should be supported to keep in sync with task_entries()",
280                name
281            ),
282        };
283
284        Ok(fs.create_node(ino, ops, FsNodeInfo::new(mode, creds)))
285    }
286}
287
288/// `TaskDirectory` doesn't implement the `close` method.
289impl CloseFreeSafe for TaskDirectory {}
290impl FileOps for TaskDirectory {
291    fileops_impl_directory!();
292    fileops_impl_noop_sync!();
293    fileops_impl_unbounded_seek!();
294
295    fn readdir(
296        &self,
297        file: &FileObject,
298        _current_task: &CurrentTask,
299        sink: &mut dyn DirentSink,
300    ) -> Result<(), Errno> {
301        emit_dotdot(file, sink)?;
302
303        // Skip through the entries until the current offset is reached.
304        // Subtract 2 from the offset to account for `.` and `..`.
305        for (index, (name, mode)) in
306            task_entries(self.scope).into_iter().enumerate().skip(sink.offset() as usize - 2)
307        {
308            sink.add(
309                self.inode_range.start + index as ino_t,
310                sink.offset() + 1,
311                DirectoryEntryType::from_mode(mode),
312                name.as_ref(),
313            )?;
314        }
315        Ok(())
316    }
317
318    fn as_thread_group_key(&self, _file: &FileObject) -> Result<ThreadGroupKey, Errno> {
319        let task = self.task_weak.upgrade().ok_or_else(|| errno!(ESRCH))?;
320        Ok(task.thread_group().into())
321    }
322}
323
324/// Creates an [`FsNode`] that represents the `/proc/<pid>` directory for `task`.
325pub fn pid_directory(
326    current_task: &CurrentTask,
327    fs: &FileSystemHandle,
328    task: &Arc<Task>,
329) -> FsNodeHandle {
330    // proc(5): "The files inside each /proc/pid directory are normally
331    // owned by the effective user and effective group ID of the process."
332    let fs_node = TaskDirectory::new(fs, task, TaskEntryScope::ThreadGroup);
333
334    security::task_to_fs_node(current_task, task, &fs_node);
335    fs_node
336}
337
338/// Creates an [`FsNode`] that represents the `/proc/<pid>/task/<tid>` directory for `task`.
339fn tid_directory(fs: &FileSystemHandle, task: &Arc<Task>) -> FsNodeHandle {
340    TaskDirectory::new(fs, task, TaskEntryScope::Task)
341}
342
343/// `FdDirectory` implements the directory listing operations for a `proc/<pid>/fd` directory.
344///
345/// Reading the directory returns a list of all the currently open file descriptors for the
346/// associated task.
347struct FdDirectory {
348    task: Weak<Task>,
349}
350
351impl FdDirectory {
352    fn new(task: Weak<Task>) -> Self {
353        Self { task }
354    }
355}
356
357impl FsNodeOps for FdDirectory {
358    fs_node_impl_dir_readonly!();
359
360    fn create_file_ops(
361        &self,
362        _node: &FsNode,
363        _current_task: &CurrentTask,
364        _flags: OpenFlags,
365    ) -> Result<Box<dyn FileOps>, Errno> {
366        Ok(VecDirectory::new_file(fds_to_directory_entries(
367            Task::from_weak(&self.task)?.files()?.get_all_fds(),
368        )))
369    }
370
371    fn lookup(
372        &self,
373        node: &FsNode,
374        _current_task: &CurrentTask,
375        name: &FsStr,
376    ) -> Result<FsNodeHandle, Errno> {
377        let fd = FdNumber::from_fs_str(name).map_err(|_| errno!(ENOENT))?;
378        let task = Task::from_weak(&self.task)?;
379        // Make sure that the file descriptor exists before creating the node.
380        let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
381        // Derive the symlink's mode from the mode in which the file was opened.
382        let mode = FileMode::IFLNK | Access::from_open_flags(file.flags()).user_mode();
383        let task_reference = self.task.clone();
384        Ok(node.fs().create_node_and_allocate_node_id(
385            CallbackSymlinkNode::new(move || {
386                let task = Task::from_weak(&task_reference)?;
387                let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
388                Ok(SymlinkTarget::Node(file.name.to_passive()))
389            }),
390            FsNodeInfo::new(mode, task.real_fscred()),
391        ))
392    }
393}
394
395const NS_ENTRIES: &[&str] = &[
396    "cgroup",
397    "ipc",
398    "mnt",
399    "net",
400    "pid",
401    "pid_for_children",
402    "time",
403    "time_for_children",
404    "user",
405    "uts",
406];
407
408/// /proc/<pid>/attr directory entry.
409struct AttrNode {
410    attr: security::ProcAttr,
411    task: Weak<Task>,
412}
413
414impl AttrNode {
415    fn new(task: Weak<Task>, attr: security::ProcAttr) -> impl FsNodeOps {
416        SimpleFileNode::new(move |_| Ok(AttrNode { attr, task: task.clone() }))
417    }
418}
419
420impl FileOps for AttrNode {
421    fileops_impl_seekable!();
422    fileops_impl_noop_sync!();
423
424    fn writes_update_seek_offset(&self) -> bool {
425        false
426    }
427
428    fn read(
429        &self,
430        _file: &FileObject,
431        current_task: &CurrentTask,
432        offset: usize,
433        data: &mut dyn OutputBuffer,
434    ) -> Result<usize, Errno> {
435        let task = Task::from_weak(&self.task)?;
436        let response = security::get_procattr(current_task, &task, self.attr)?;
437        data.write(&response[offset..])
438    }
439
440    fn write(
441        &self,
442        _file: &FileObject,
443        current_task: &CurrentTask,
444        offset: usize,
445        data: &mut dyn InputBuffer,
446    ) -> Result<usize, Errno> {
447        let task = Task::from_weak(&self.task)?;
448
449        // If the current task is not the target then writes are not allowed.
450        if current_task.task != task {
451            return error!(EPERM);
452        }
453        if offset != 0 {
454            return error!(EINVAL);
455        }
456
457        let data = data.read_all()?;
458        let data_len = data.len();
459        security::set_procattr(current_task, self.attr, data.as_slice())?;
460        Ok(data_len)
461    }
462}
463
464/// /proc/[pid]/ns directory
465struct NsDirectory {
466    task: Weak<Task>,
467}
468
469impl FsNodeOps for NsDirectory {
470    fs_node_impl_dir_readonly!();
471
472    fn create_file_ops(
473        &self,
474        _node: &FsNode,
475        _current_task: &CurrentTask,
476        _flags: OpenFlags,
477    ) -> Result<Box<dyn FileOps>, Errno> {
478        // For each namespace, this contains a link to the current identifier of the given namespace
479        // for the current task.
480        Ok(VecDirectory::new_file(
481            NS_ENTRIES
482                .iter()
483                .map(|&name| VecDirectoryEntry {
484                    entry_type: DirectoryEntryType::LNK,
485                    name: FsString::from(name),
486                    inode: None,
487                })
488                .collect(),
489        ))
490    }
491
492    fn lookup(
493        &self,
494        node: &FsNode,
495        current_task: &CurrentTask,
496        name: &FsStr,
497    ) -> Result<FsNodeHandle, Errno> {
498        // If name is a given namespace, link to the current identifier of the that namespace for
499        // the current task.
500        // If name is {namespace}:[id], get a file descriptor for the given namespace.
501
502        let name = String::from_utf8(name.to_vec()).map_err(|_| errno!(ENOENT))?;
503        let mut elements = name.split(':');
504        let ns = elements.next().expect("name must not be empty");
505        // The name doesn't starts with a known namespace.
506        if !NS_ENTRIES.contains(&ns) {
507            return error!(ENOENT);
508        }
509
510        let task = Task::from_weak(&self.task)?;
511        if let Some(id) = elements.next() {
512            // The name starts with {namespace}:, check that it matches {namespace}:[id]
513            static NS_IDENTIFIER_RE: LazyLock<Regex> =
514                LazyLock::new(|| Regex::new("^\\[[0-9]+\\]$").unwrap());
515            if !NS_IDENTIFIER_RE.is_match(id) {
516                return error!(ENOENT);
517            }
518            let node_info = || FsNodeInfo::new(mode!(IFREG, 0o444), task.real_fscred());
519            let fallback = || {
520                node.fs().create_node_and_allocate_node_id(BytesFile::new_node(vec![]), node_info())
521            };
522            Ok(match ns {
523                "cgroup" => {
524                    track_stub!(TODO("https://fxbug.dev/297313673"), "cgroup namespaces");
525                    fallback()
526                }
527                "ipc" => {
528                    track_stub!(TODO("https://fxbug.dev/297313673"), "ipc namespaces");
529                    fallback()
530                }
531                "mnt" => node
532                    .fs()
533                    .create_node_and_allocate_node_id(current_task.fs().namespace(), node_info()),
534                "net" => {
535                    track_stub!(TODO("https://fxbug.dev/297313673"), "net namespaces");
536                    fallback()
537                }
538                "pid" => {
539                    track_stub!(TODO("https://fxbug.dev/297313673"), "pid namespaces");
540                    fallback()
541                }
542                "pid_for_children" => {
543                    track_stub!(TODO("https://fxbug.dev/297313673"), "pid_for_children namespaces");
544                    fallback()
545                }
546                "time" => {
547                    track_stub!(TODO("https://fxbug.dev/297313673"), "time namespaces");
548                    fallback()
549                }
550                "time_for_children" => {
551                    track_stub!(
552                        TODO("https://fxbug.dev/297313673"),
553                        "time_for_children namespaces"
554                    );
555                    fallback()
556                }
557                "user" => {
558                    track_stub!(TODO("https://fxbug.dev/297313673"), "user namespaces");
559                    fallback()
560                }
561                "uts" => {
562                    track_stub!(TODO("https://fxbug.dev/297313673"), "uts namespaces");
563                    fallback()
564                }
565                _ => return error!(ENOENT),
566            })
567        } else {
568            // The name is {namespace}, link to the correct one of the current task.
569            let id = current_task.fs().namespace().id;
570            Ok(node.fs().create_node_and_allocate_node_id(
571                CallbackSymlinkNode::new(move || {
572                    Ok(SymlinkTarget::Path(format!("{name}:[{id}]").into()))
573                }),
574                FsNodeInfo::new(mode!(IFLNK, 0o7777), task.real_fscred()),
575            ))
576        }
577    }
578}
579
580/// `FdInfoDirectory` implements the directory listing operations for a `proc/<pid>/fdinfo`
581/// directory.
582///
583/// Reading the directory returns a list of all the currently open file descriptors for the
584/// associated task.
585struct FdInfoDirectory {
586    task: Weak<Task>,
587}
588
589impl FdInfoDirectory {
590    fn new(task: Weak<Task>) -> Self {
591        Self { task }
592    }
593}
594
595impl FsNodeOps for FdInfoDirectory {
596    fs_node_impl_dir_readonly!();
597
598    fn create_file_ops(
599        &self,
600        _node: &FsNode,
601        current_task: &CurrentTask,
602        _flags: OpenFlags,
603    ) -> Result<Box<dyn FileOps>, Errno> {
604        let task = Task::from_weak(&self.task)?;
605        current_task
606            .check_ptrace_access_mode(PTRACE_MODE_READ_FSCREDS, &task)
607            .map_err(|_| errno!(EACCES))?;
608
609        Ok(VecDirectory::new_file(fds_to_directory_entries(task.files()?.get_all_fds())))
610    }
611
612    fn lookup(
613        &self,
614        node: &FsNode,
615        current_task: &CurrentTask,
616        name: &FsStr,
617    ) -> Result<FsNodeHandle, Errno> {
618        let task = Task::from_weak(&self.task)?;
619        let fd = FdNumber::from_fs_str(name).map_err(|_| errno!(ENOENT))?;
620        let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
621        let pos = file.offset.read();
622        let flags = file.flags();
623        let mut data = format!("pos:\t{}\nflags:\t0{:o}\n", pos, flags.bits()).into_bytes();
624        if let Some(extra_fdinfo) = file.extra_fdinfo(current_task) {
625            data.extend_from_slice(extra_fdinfo.as_slice());
626        }
627        Ok(node.fs().create_node_and_allocate_node_id(
628            BytesFile::new_node(data),
629            FsNodeInfo::new(mode!(IFREG, 0o444), task.real_fscred()),
630        ))
631    }
632}
633
634fn fds_to_directory_entries(fds: Vec<FdNumber>) -> Vec<VecDirectoryEntry> {
635    fds.into_iter()
636        .map(|fd| VecDirectoryEntry {
637            entry_type: DirectoryEntryType::DIR,
638            name: fd.raw().to_string().into(),
639            inode: None,
640        })
641        .collect()
642}
643
644/// Directory that lists the task IDs (tid) in a process. Located at `/proc/<pid>/task/`.
645struct TaskListDirectory {
646    thread_group: Weak<ThreadGroup>,
647}
648
649impl TaskListDirectory {
650    fn thread_group(&self) -> Result<Arc<ThreadGroup>, Errno> {
651        self.thread_group.upgrade().ok_or_else(|| errno!(ESRCH))
652    }
653}
654
655impl FsNodeOps for TaskListDirectory {
656    fs_node_impl_dir_readonly!();
657
658    fn create_file_ops(
659        &self,
660        _node: &FsNode,
661        _current_task: &CurrentTask,
662        _flags: OpenFlags,
663    ) -> Result<Box<dyn FileOps>, Errno> {
664        Ok(VecDirectory::new_file(
665            self.thread_group()?
666                .read()
667                .task_ids()
668                .map(|tid| VecDirectoryEntry {
669                    entry_type: DirectoryEntryType::DIR,
670                    name: tid.to_string().into(),
671                    inode: None,
672                })
673                .collect(),
674        ))
675    }
676
677    fn lookup(
678        &self,
679        node: &FsNode,
680        _current_task: &CurrentTask,
681        name: &FsStr,
682    ) -> Result<FsNodeHandle, Errno> {
683        let thread_group = self.thread_group()?;
684        let tid = std::str::from_utf8(name)
685            .map_err(|_| errno!(ENOENT))?
686            .parse::<pid_t>()
687            .map_err(|_| errno!(ENOENT))?;
688        // Make sure the tid belongs to this process.
689        if !thread_group.read().contains_task(tid) {
690            return error!(ENOENT);
691        }
692
693        let pid_state = thread_group.kernel.pids.read();
694        let task = pid_state.get_task(tid).map_err(|_| errno!(ENOENT))?;
695        std::mem::drop(pid_state);
696
697        Ok(tid_directory(&node.fs(), &task))
698    }
699}
700
701#[derive(Clone)]
702struct CgroupFile(Weak<Task>);
703impl CgroupFile {
704    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
705        DynamicFile::new_node(Self(task))
706    }
707}
708impl DynamicFileSource for CgroupFile {
709    fn generate(
710        &self,
711        _current_task: &CurrentTask,
712        sink: &mut DynamicFileBuf,
713    ) -> Result<(), Errno> {
714        let task = Task::from_weak(&self.0)?;
715        let cgroup1 = task.kernel().cgroups.cgroup1.lock();
716        for (key, root) in &cgroup1.hierarchies {
717            let mut parts: Vec<&str> = key.controllers.iter().map(|c| c.as_str()).collect();
718            let name_storage;
719            if let Some(name) = &key.name {
720                name_storage = format!("name={}", name);
721                parts.push(&name_storage);
722            }
723            let controller_str = parts.join(",");
724            let cgroup = root.get_cgroup(task.thread_group());
725            let path = path_from_root(cgroup)?;
726            sink.write(format!("{}:{}:{}\n", root.hierarchy_id, controller_str, path).as_bytes());
727        }
728        let cgroup = task.kernel().cgroups.cgroup2.get_cgroup(task.thread_group());
729        let path = path_from_root(cgroup)?;
730        sink.write(format!("0::{}\n", path).as_bytes());
731        Ok(())
732    }
733}
734
735fn fill_buf_from_addr_range(
736    task: &Task,
737    range_start: UserAddress,
738    range_end: UserAddress,
739    sink: &mut DynamicFileBuf,
740) -> Result<(), Errno> {
741    #[allow(clippy::manual_saturating_arithmetic)]
742    let len = range_end.ptr().checked_sub(range_start.ptr()).unwrap_or(0);
743    // NB: If this is exercised in a hot-path, we can plumb the reading task
744    // (`CurrentTask`) here to perform a copy without going through the VMO when
745    // unified aspaces is enabled.
746    let buf = task.read_memory_partial_to_vec(range_start, len)?;
747    sink.write(&buf[..]);
748    Ok(())
749}
750
751/// `CmdlineFile` implements `proc/<pid>/cmdline` file.
752#[derive(Clone)]
753pub struct CmdlineFile {
754    task: Weak<Task>,
755}
756impl CmdlineFile {
757    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
758        DynamicFile::new_node(Self { task })
759    }
760}
761impl DynamicFileSource for CmdlineFile {
762    fn generate(
763        &self,
764        _current_task: &CurrentTask,
765        sink: &mut DynamicFileBuf,
766    ) -> Result<(), Errno> {
767        // Opened cmdline file should still be functional once the task is a zombie.
768        let Some(task) = self.task.upgrade() else {
769            return Ok(());
770        };
771        // /proc/<pid>/cmdline is empty for kthreads.
772        let Ok(mm) = task.mm() else {
773            return Ok(());
774        };
775        let (start, end) = {
776            let mm_state = mm.state.read();
777            (mm_state.argv_start, mm_state.argv_end)
778        };
779        fill_buf_from_addr_range(&task, start, end, sink)
780    }
781}
782
783struct PtraceCheckedNode {}
784
785impl PtraceCheckedNode {
786    pub fn new_node<F, O>(task: Weak<Task>, mode: PtraceAccessMode, create_ops: F) -> impl FsNodeOps
787    where
788        F: Fn(&CurrentTask, Arc<Task>) -> Result<O, Errno> + Send + Sync + 'static,
789        O: FileOps,
790    {
791        SimpleFileNode::new(move |current_task: &CurrentTask| {
792            let task = Task::from_weak(&task)?;
793            // proc-pid nodes for kthreads do not require ptrace access checks.
794            if task.mm().is_ok() {
795                current_task.check_ptrace_access_mode(mode, &task).map_err(|_| errno!(EACCES))?;
796            }
797            create_ops(current_task, task)
798        })
799    }
800}
801
802/// `EnvironFile` implements `proc/<pid>/environ` file.
803#[derive(Clone)]
804pub struct EnvironFile {
805    task: Weak<Task>,
806}
807impl EnvironFile {
808    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
809        PtraceCheckedNode::new_node(task, PTRACE_MODE_READ_FSCREDS, |_, task| {
810            Ok(DynamicFile::new(Self { task: Arc::downgrade(&task) }))
811        })
812    }
813}
814impl DynamicFileSource for EnvironFile {
815    fn generate(
816        &self,
817        _current_task: &CurrentTask,
818        sink: &mut DynamicFileBuf,
819    ) -> Result<(), Errno> {
820        let task = Task::from_weak(&self.task)?;
821        // /proc/<pid>/environ is empty for kthreads.
822        let Ok(mm) = task.mm() else {
823            return Ok(());
824        };
825        let (start, end) = {
826            let mm_state = mm.state.read();
827            (mm_state.environ_start, mm_state.environ_end)
828        };
829        fill_buf_from_addr_range(&task, start, end, sink)
830    }
831}
832
833/// `AuxvFile` implements `proc/<pid>/auxv` file.
834#[derive(Clone)]
835pub struct AuxvFile {
836    task: Weak<Task>,
837}
838impl AuxvFile {
839    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
840        PtraceCheckedNode::new_node(task, PTRACE_MODE_READ_FSCREDS, |_, task| {
841            Ok(DynamicFile::new(Self { task: Arc::downgrade(&task) }))
842        })
843    }
844}
845impl DynamicFileSource for AuxvFile {
846    fn generate(
847        &self,
848        _current_task: &CurrentTask,
849        sink: &mut DynamicFileBuf,
850    ) -> Result<(), Errno> {
851        let task = Task::from_weak(&self.task)?;
852        // /proc/<pid>/auxv is empty for kthreads.
853        let Ok(mm) = task.mm() else {
854            return Ok(());
855        };
856        let (start, end) = {
857            let mm_state = mm.state.read();
858            (mm_state.auxv_start, mm_state.auxv_end)
859        };
860        fill_buf_from_addr_range(&task, start, end, sink)
861    }
862}
863
864/// `CommFile` implements `proc/<pid>/comm` file.
865pub struct CommFile {
866    task: Weak<Task>,
867    info: TaskPersistentInfo,
868}
869impl CommFile {
870    pub fn new_node(task: Weak<Task>, info: TaskPersistentInfo) -> impl FsNodeOps {
871        SimpleFileNode::new(move |_| {
872            Ok(DynamicFile::new(CommFile { task: task.clone(), info: info.clone() }))
873        })
874    }
875}
876
877impl DynamicFileSource for CommFile {
878    fn generate(
879        &self,
880        _current_task: &CurrentTask,
881        sink: &mut DynamicFileBuf,
882    ) -> Result<(), Errno> {
883        sink.write(self.info.command_guard().comm_name());
884        sink.write(b"\n");
885        Ok(())
886    }
887
888    fn write(
889        &self,
890        current_task: &CurrentTask,
891        _offset: usize,
892        data: &mut dyn InputBuffer,
893    ) -> Result<usize, Errno> {
894        let task = Task::from_weak(&self.task)?;
895        if !Arc::ptr_eq(&task.thread_group(), &current_task.thread_group()) {
896            return error!(EINVAL);
897        }
898        // What happens if userspace writes to this file in multiple syscalls? We need more
899        // detailed tests to see when the data is actually committed back to the task.
900        let bytes = data.read_all()?;
901        task.set_command_name(TaskCommand::new(&bytes));
902        Ok(bytes.len())
903    }
904}
905
906/// `IoFile` implements `proc/<pid>/io` file.
907#[derive(Clone)]
908pub struct IoFile {}
909impl IoFile {
910    pub fn new_node() -> impl FsNodeOps {
911        DynamicFile::new_node(Self {})
912    }
913}
914impl DynamicFileSource for IoFile {
915    fn generate(
916        &self,
917        _current_task: &CurrentTask,
918        sink: &mut DynamicFileBuf,
919    ) -> Result<(), Errno> {
920        track_stub!(TODO("https://fxbug.dev/322874250"), "/proc/pid/io");
921        sink.write(b"rchar: 0\n");
922        sink.write(b"wchar: 0\n");
923        sink.write(b"syscr: 0\n");
924        sink.write(b"syscw: 0\n");
925        sink.write(b"read_bytes: 0\n");
926        sink.write(b"write_bytes: 0\n");
927        sink.write(b"cancelled_write_bytes: 0\n");
928        Ok(())
929    }
930}
931
932/// `LimitsFile` implements `proc/<pid>/limits` file.
933#[derive(Clone)]
934pub struct LimitsFile(Weak<Task>);
935impl LimitsFile {
936    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
937        DynamicFile::new_node(Self(task))
938    }
939}
940impl DynamicFileSource for LimitsFile {
941    fn generate_locked(
942        &self,
943        _current_task: &CurrentTask,
944        sink: &mut DynamicFileBuf,
945    ) -> Result<(), Errno> {
946        let task = Task::from_weak(&self.0)?;
947        let limits = task.thread_group().limits.lock();
948
949        let write_limit = |sink: &mut DynamicFileBuf, value| {
950            if value == RLIM_INFINITY as u64 {
951                sink.write(format!("{:<20}", "unlimited").as_bytes());
952            } else {
953                sink.write(format!("{:<20}", value).as_bytes());
954            }
955        };
956        sink.write(
957            format!("{:<25}{:<20}{:<20}{:<10}\n", "Limit", "Soft Limit", "Hard Limit", "Units")
958                .as_bytes(),
959        );
960        for resource in Resource::ALL {
961            let desc = resource.desc();
962            let limit = limits.get(resource);
963            sink.write(format!("{:<25}", desc.name).as_bytes());
964            write_limit(sink, limit.rlim_cur);
965            write_limit(sink, limit.rlim_max);
966            if !desc.unit.is_empty() {
967                sink.write(format!("{:<10}", desc.unit).as_bytes());
968            }
969            sink.write(b"\n");
970        }
971        Ok(())
972    }
973}
974
975/// `MemFile` implements `proc/<pid>/mem` file.
976pub struct MemFile {
977    mm: Weak<MemoryManager>,
978
979    // TODO: https://fxbug.dev/442459337 - Tear-down MemoryManager internals on process exit, to
980    // avoid extension of the MM lifetime prolonging access to memory via "/proc/pid/mem", etc
981    // beyond that of the actual process/address-space.
982    task: Weak<Task>,
983}
984
985impl MemFile {
986    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
987        PtraceCheckedNode::new_node(task, PTRACE_MODE_ATTACH_FSCREDS, |_, task| {
988            let mm = task.mm().ok().as_ref().map(Arc::downgrade).unwrap_or_default();
989            Ok(Self { mm, task: Arc::downgrade(&task) })
990        })
991    }
992}
993
994impl FileOps for MemFile {
995    fileops_impl_noop_sync!();
996
997    fn is_seekable(&self) -> bool {
998        true
999    }
1000
1001    fn seek(
1002        &self,
1003        _file: &FileObject,
1004        _current_task: &CurrentTask,
1005        current_offset: off_t,
1006        target: SeekTarget,
1007    ) -> Result<off_t, Errno> {
1008        default_seek(current_offset, target, || error!(EINVAL))
1009    }
1010
1011    fn read(
1012        &self,
1013        _file: &FileObject,
1014        current_task: &CurrentTask,
1015        offset: usize,
1016        data: &mut dyn OutputBuffer,
1017    ) -> Result<usize, Errno> {
1018        let Some(_task) = self.task.upgrade() else {
1019            return Ok(0);
1020        };
1021        let Some(mm) = self.mm.upgrade() else {
1022            return Ok(0);
1023        };
1024        let mut addr = UserAddress::from(offset as u64);
1025        data.write_each(&mut |bytes| {
1026            let read_bytes = if current_task.has_same_address_space(Some(&mm)) {
1027                current_task.read_memory_partial(addr, bytes)
1028            } else {
1029                mm.syscall_read_memory_partial(addr, bytes)
1030            }
1031            .map_err(|_| errno!(EIO))?;
1032            let actual = read_bytes.len();
1033            addr = (addr + actual)?;
1034            Ok(actual)
1035        })
1036    }
1037
1038    fn write(
1039        &self,
1040        _file: &FileObject,
1041        current_task: &CurrentTask,
1042        offset: usize,
1043        data: &mut dyn InputBuffer,
1044    ) -> Result<usize, Errno> {
1045        let Some(_task) = self.task.upgrade() else {
1046            return Ok(0);
1047        };
1048        let Some(mm) = self.mm.upgrade() else {
1049            return Ok(0);
1050        };
1051        let addr = UserAddress::from(offset as u64);
1052        let mut written = 0;
1053        let result = data.peek_each(&mut |bytes| {
1054            let actual = if current_task.has_same_address_space(Some(&mm)) {
1055                current_task.write_memory_partial((addr + written)?, bytes)
1056            } else {
1057                mm.syscall_write_memory_partial((addr + written)?, bytes)
1058            }
1059            .map_err(|_| errno!(EIO))?;
1060            written += actual;
1061            Ok(actual)
1062        });
1063        data.advance(written)?;
1064        result
1065    }
1066}
1067
1068const STUBBED_MEM_BYTES: usize = 4096;
1069
1070// Workaround for b/525059309: Zircon VMAR walks (`ZX_INFO_VMAR_MAPS`) are extremely
1071// slow and cause Perfetto's `traced_probes` watchdog timeouts when sweeping thread status
1072// files. We bypass them when the reader is `traced_probes` by returning a 1 page/KB stub.
1073fn should_skip_memory_stats(current_task: &CurrentTask) -> bool {
1074    current_task.persistent_info.command_guard().comm_name() == b"traced_probes"
1075}
1076fn stub_memory_stats() -> MemoryStats {
1077    MemoryStats {
1078        vm_size: STUBBED_MEM_BYTES,
1079        vm_rss: STUBBED_MEM_BYTES,
1080        vm_rss_hwm: STUBBED_MEM_BYTES,
1081        rss_anonymous: STUBBED_MEM_BYTES,
1082        rss_file: 0,
1083        rss_shared: 0,
1084        vm_data: 0,
1085        vm_stack: STUBBED_MEM_BYTES,
1086        vm_exe: STUBBED_MEM_BYTES,
1087        vm_swap: 0,
1088        vm_lck: 0,
1089    }
1090}
1091
1092#[derive(Clone)]
1093pub struct StatFile {
1094    task: Weak<Task>,
1095    scope: TaskEntryScope,
1096}
1097
1098impl StatFile {
1099    pub fn new_node(task: Weak<Task>, scope: TaskEntryScope) -> impl FsNodeOps {
1100        DynamicFile::new_node(Self { task, scope })
1101    }
1102}
1103impl DynamicFileSource for StatFile {
1104    fn generate_locked(
1105        &self,
1106        current_task: &CurrentTask,
1107        sink: &mut DynamicFileBuf,
1108    ) -> Result<(), Errno> {
1109        let task = Task::from_weak(&self.task)?;
1110
1111        // All fields and their types as specified in the man page.
1112        // Unimplemented fields are set to 0 here.
1113        let pid: pid_t; // 1
1114        let comm: TaskCommand;
1115        let state: char;
1116        let ppid: pid_t;
1117        let pgrp: pid_t; // 5
1118        let session: pid_t;
1119        let tty_nr: i32;
1120        let tpgid: i32 = 0;
1121        let flags: u32 = 0;
1122        let minflt: u64 = 0; // 10
1123        let cminflt: u64 = 0;
1124        let majflt: u64 = 0;
1125        let cmajflt: u64 = 0;
1126        let utime: i64;
1127        let stime: i64; // 15
1128        let cutime: i64;
1129        let cstime: i64;
1130        let priority: i64 = 0;
1131        let nice: i64;
1132        let num_threads: i64; // 20
1133        let itrealvalue: i64 = 0;
1134        let mut starttime: u64 = 0;
1135        let mut vsize: usize = 0;
1136        let mut rss: usize = 0;
1137        let mut rsslim: u64 = 0; // 25
1138        let mut startcode: u64 = 0;
1139        let mut endcode: u64 = 0;
1140        let mut startstack: usize = 0;
1141        let mut kstkesp: u64 = 0;
1142        let mut kstkeip: u64 = 0; // 30
1143        let signal: u64 = 0;
1144        let blocked: u64 = 0;
1145        let siginore: u64 = 0;
1146        let sigcatch: u64 = 0;
1147        let mut wchan: u64 = 0; // 35
1148        let nswap: u64 = 0;
1149        let cnswap: u64 = 0;
1150        let exit_signal: i32 = 0;
1151        let processor: i32 = 0;
1152        let rt_priority: u32 = 0; // 40
1153        let policy: u32 = 0;
1154        let delayacct_blkio_ticks: u64 = 0;
1155        let guest_time: u64 = 0;
1156        let cguest_time: i64 = 0;
1157        let mut start_data: u64 = 0; // 45
1158        let mut end_data: u64 = 0;
1159        let mut start_brk: u64 = 0;
1160        let mut arg_start: usize = 0;
1161        let mut arg_end: usize = 0;
1162        let mut env_start: usize = 0; // 50
1163        let mut env_end: usize = 0;
1164        let mut exit_code: i32 = 0;
1165
1166        pid = task.get_tid();
1167        comm = task.command();
1168        state = task.state_code().code_char();
1169        nice = task.read().scheduler_state.normal_priority().as_nice() as i64;
1170
1171        {
1172            let thread_group = task.thread_group().read();
1173            ppid = thread_group.get_ppid();
1174            pgrp = thread_group.process_group.leader;
1175            session = thread_group.process_group.session.leader;
1176
1177            // TTY device ID.
1178            {
1179                let session = thread_group.process_group.session.read();
1180                tty_nr = session
1181                    .controlling_terminal
1182                    .as_ref()
1183                    .map(|t| t.terminal.device().bits())
1184                    .unwrap_or(0) as i32;
1185            }
1186
1187            cutime = duration_to_scheduler_clock(thread_group.children_time_stats.user_time);
1188            cstime = duration_to_scheduler_clock(thread_group.children_time_stats.system_time);
1189
1190            num_threads = thread_group.tasks_count() as i64;
1191        }
1192
1193        let time_stats = match self.scope {
1194            TaskEntryScope::Task => task.time_stats(),
1195            TaskEntryScope::ThreadGroup => task.thread_group().time_stats(),
1196        };
1197        utime = duration_to_scheduler_clock(time_stats.user_time);
1198        stime = duration_to_scheduler_clock(time_stats.system_time);
1199
1200        if let Ok(info) = task.thread_group().process.info() {
1201            starttime =
1202                duration_to_scheduler_clock(info.start_time - zx::MonotonicInstant::ZERO) as u64;
1203        }
1204
1205        if let Ok(mm) = task.mm() {
1206            // TODO(b/525059309): Bypassed for traced_probes due to VMAR walk slowness. Re-enable when optimized.
1207            let mem_stats = if should_skip_memory_stats(current_task) {
1208                stub_memory_stats()
1209            } else {
1210                mm.get_stats(current_task)
1211            };
1212            let page_size = *PAGE_SIZE as usize;
1213            vsize = mem_stats.vm_size;
1214            rss = mem_stats.vm_rss / page_size;
1215            rsslim = task.thread_group().limits.lock().get(Resource::RSS).rlim_max;
1216
1217            {
1218                let mm_state = mm.state.read();
1219                startstack = mm_state.stack_start.ptr();
1220                arg_start = mm_state.argv_start.ptr();
1221                arg_end = mm_state.argv_end.ptr();
1222                env_start = mm_state.environ_start.ptr();
1223                env_end = mm_state.environ_end.ptr();
1224            }
1225        }
1226
1227        // The man page describes that the following fields have "... values displayed as 0" if the
1228        // caller does not have ptrace read access to the target.
1229        // In practice the `startcode` and `endcode` fields appear to be displayed as 1.
1230        if !current_task
1231            .check_ptrace_access_mode(PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT, &task)
1232            .is_ok()
1233        {
1234            startcode = 1;
1235            endcode = 1;
1236            startstack = 0;
1237            kstkesp = 0;
1238            kstkeip = 0;
1239            wchan = 0;
1240            start_data = 0;
1241            end_data = 0;
1242            start_brk = 0;
1243            arg_start = 0;
1244            arg_end = 0;
1245            env_start = 0;
1246            env_end = 0;
1247            exit_code = 0;
1248        }
1249
1250        writeln!(
1251            sink,
1252            "{pid} ({comm}) {state} {ppid} {pgrp} {session} {tty_nr} {tpgid} {flags} {minflt} {cminflt} {majflt} {cmajflt} {utime} {stime} {cutime} {cstime} {priority} {nice} {num_threads} {itrealvalue} {starttime} {vsize} {rss} {rsslim} {startcode} {endcode} {startstack} {kstkesp} {kstkeip} {signal} {blocked} {siginore} {sigcatch} {wchan} {nswap} {cnswap} {exit_signal} {processor} {rt_priority} {policy} {delayacct_blkio_ticks} {guest_time} {cguest_time} {start_data} {end_data} {start_brk} {arg_start} {arg_end} {env_start} {env_end} {exit_code}"
1253        )?;
1254
1255        Ok(())
1256    }
1257}
1258
1259#[derive(Clone)]
1260pub struct StatmFile {
1261    task: Weak<Task>,
1262}
1263impl StatmFile {
1264    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1265        DynamicFile::new_node(Self { task })
1266    }
1267}
1268impl DynamicFileSource for StatmFile {
1269    fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
1270        // /proc/<pid>/statm reports zeroes for kthreads.
1271        let task = Task::from_weak(&self.task)?;
1272        // TODO(b/525059309): Bypassed for traced_probes due to VMAR walk slowness. Re-enable when optimized.
1273        let mem_stats = if should_skip_memory_stats(current_task) {
1274            stub_memory_stats()
1275        } else {
1276            match task.mm() {
1277                Ok(mm) => mm.get_stats(current_task),
1278                Err(_) => Default::default(),
1279            }
1280        };
1281        let page_size = *PAGE_SIZE as usize;
1282
1283        // 5th and 7th fields are deprecated and should be set to 0.
1284        writeln!(
1285            sink,
1286            "{} {} {} {} 0 {} 0",
1287            mem_stats.vm_size / page_size,
1288            mem_stats.vm_rss / page_size,
1289            mem_stats.rss_shared / page_size,
1290            mem_stats.vm_exe / page_size,
1291            (mem_stats.vm_data + mem_stats.vm_stack) / page_size
1292        )?;
1293        Ok(())
1294    }
1295}
1296
1297#[derive(Clone)]
1298pub struct StatusFile(Weak<Task>);
1299impl StatusFile {
1300    pub fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1301        DynamicFile::new_node(Self(task))
1302    }
1303}
1304impl DynamicFileSource for StatusFile {
1305    fn generate(&self, current_task: &CurrentTask, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
1306        let start_monotonic = zx::MonotonicInstant::get();
1307        let start_boot = zx::BootInstant::get();
1308        let task = &self.0.upgrade();
1309        let (tgid, pid, creds_string) = {
1310            if let Some(task) = task {
1311                track_stub!(TODO("https://fxbug.dev/297440106"), "/proc/pid/status zombies");
1312                // Collect everything stored in info in this block.  There is a lock ordering
1313                // issue with the task lock acquired below, and cloning info is
1314                // expensive.
1315                write!(sink, "Name:\t")?;
1316                sink.write(task.persistent_info.command_guard().comm_name());
1317                let creds = task.persistent_info.real_creds();
1318                (
1319                    Some(task.persistent_info.pid()),
1320                    Some(task.persistent_info.tid()),
1321                    Some(format!(
1322                        "Uid:\t{}\t{}\t{}\t{}\nGid:\t{}\t{}\t{}\t{}\nGroups:\t{}",
1323                        creds.uid,
1324                        creds.euid,
1325                        creds.saved_uid,
1326                        creds.fsuid,
1327                        creds.gid,
1328                        creds.egid,
1329                        creds.saved_gid,
1330                        creds.fsgid,
1331                        creds.groups.iter().map(|n| n.to_string()).join(" ")
1332                    )),
1333                )
1334            } else {
1335                (None, None, None)
1336            }
1337        };
1338
1339        writeln!(sink)?;
1340
1341        if let Some(task) = task {
1342            if let Ok(running_state) = task.running_state() {
1343                writeln!(sink, "Umask:\t0{:03o}", running_state.fs().umask().bits())?;
1344            }
1345            let task_state = task.read();
1346            writeln!(sink, "SigBlk:\t{:016x}", task_state.signal_mask().0)?;
1347            writeln!(sink, "SigPnd:\t{:016x}", task_state.task_specific_pending_signals().0)?;
1348            writeln!(
1349                sink,
1350                "ShdPnd:\t{:x}",
1351                task.thread_group().pending_signals.lock().pending().0
1352            )?;
1353            writeln!(sink, "NoNewPrivs:\t{}", task_state.no_new_privs() as u8)?;
1354
1355            // Since version 3.8 all nonexistent capabilities are reported as not-enabled.
1356            let creds = task.real_creds();
1357            writeln!(sink, "CapInh:\t{:016x}", creds.cap_inheritable)?;
1358            writeln!(sink, "CapPrm:\t{:016x}", creds.cap_permitted)?;
1359            writeln!(sink, "CapEff:\t{:016x}", creds.cap_effective)?;
1360            writeln!(sink, "CapBnd:\t{:016x}", creds.cap_bounding)?;
1361            writeln!(sink, "CapAmb:\t{:016x}", creds.cap_ambient)?;
1362        }
1363
1364        let state_code =
1365            if let Some(task) = task { task.state_code() } else { TaskStateCode::Zombie };
1366        writeln!(sink, "State:\t{} ({})", state_code.code_char(), state_code.name())?;
1367
1368        if let Some(tgid) = tgid {
1369            writeln!(sink, "Tgid:\t{}", tgid)?;
1370        }
1371        if let Some(pid) = pid {
1372            writeln!(sink, "Pid:\t{}", pid)?;
1373        }
1374        let (ppid, threads, tracer_pid) = if let Some(task) = task {
1375            let tracer_pid = task
1376                .read()
1377                .ptrace
1378                .as_ref()
1379                .map_or(0, |p| p.core_state.thread_group.upgrade().map_or(0, |tg| tg.leader));
1380            let task_group = task.thread_group().read();
1381            (task_group.get_ppid(), task_group.tasks_count(), tracer_pid)
1382        } else {
1383            (1, 1, 0)
1384        };
1385        writeln!(sink, "PPid:\t{}", ppid)?;
1386        writeln!(sink, "TracerPid:\t{}", tracer_pid)?;
1387
1388        if let Some(creds_string) = creds_string {
1389            writeln!(sink, "{}", creds_string)?;
1390        }
1391
1392        if let Some(task) = task {
1393            if let Ok(mm) = task.mm() {
1394                // TODO(b/525059309): Bypassed for traced_probes due to VMAR walk slowness. Re-enable when optimized.
1395                let mem_stats = if should_skip_memory_stats(current_task) {
1396                    stub_memory_stats()
1397                } else {
1398                    mm.get_stats(current_task)
1399                };
1400                writeln!(sink, "VmSize:\t{} kB", mem_stats.vm_size / 1024)?;
1401                writeln!(sink, "VmLck:\t{} kB", mem_stats.vm_lck / 1024)?;
1402                writeln!(sink, "VmRSS:\t{} kB", mem_stats.vm_rss / 1024)?;
1403                writeln!(sink, "RssAnon:\t{} kB", mem_stats.rss_anonymous / 1024)?;
1404                writeln!(sink, "RssFile:\t{} kB", mem_stats.rss_file / 1024)?;
1405                writeln!(sink, "RssShmem:\t{} kB", mem_stats.rss_shared / 1024)?;
1406                writeln!(sink, "VmData:\t{} kB", mem_stats.vm_data / 1024)?;
1407                writeln!(sink, "VmStk:\t{} kB", mem_stats.vm_stack / 1024)?;
1408                writeln!(sink, "VmExe:\t{} kB", mem_stats.vm_exe / 1024)?;
1409                writeln!(sink, "VmSwap:\t{} kB", mem_stats.vm_swap / 1024)?;
1410                writeln!(sink, "VmHWM:\t{} kB", mem_stats.vm_rss_hwm / 1024)?;
1411            }
1412            // Report seccomp filter status.
1413            let seccomp = task.seccomp_filter_state.get() as u8;
1414            writeln!(sink, "Seccomp:\t{}", seccomp)?;
1415        }
1416
1417        // There should be at least one thread in Zombie processes.
1418        writeln!(sink, "Threads:\t{}", std::cmp::max(1, threads))?;
1419
1420        let elapsed_monotonic = zx::MonotonicInstant::get() - start_monotonic;
1421        let elapsed_boot = zx::BootInstant::get() - start_boot;
1422        if elapsed_monotonic > zx::MonotonicDuration::from_millis(100)
1423            || elapsed_boot > zx::BootDuration::from_seconds(1)
1424        {
1425            let target_pid = task.as_ref().map(|t| t.persistent_info.pid()).unwrap_or(-1);
1426            let target_comm = task
1427                .as_ref()
1428                .map(|t| {
1429                    String::from_utf8_lossy(t.persistent_info.command_guard().comm_name())
1430                        .into_owned()
1431                })
1432                .unwrap_or_default();
1433            starnix_logging::log_warn!(
1434                "StatusFile::generate for task {} ({}) took {} ms (monotonic), {} ms (boot)",
1435                target_pid,
1436                target_comm,
1437                elapsed_monotonic.into_millis(),
1438                elapsed_boot.into_millis()
1439            );
1440        }
1441
1442        Ok(())
1443    }
1444}
1445
1446struct OomScoreFile(Weak<Task>);
1447
1448impl OomScoreFile {
1449    fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1450        BytesFile::new_node(Self(task))
1451    }
1452}
1453
1454impl BytesFileOps for OomScoreFile {
1455    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1456        let _task = Task::from_weak(&self.0)?;
1457        track_stub!(TODO("https://fxbug.dev/322873459"), "/proc/pid/oom_score");
1458        Ok(serialize_for_file(0).into())
1459    }
1460}
1461
1462// Redefine these constants as i32 to avoid conversions below.
1463const OOM_ADJUST_MAX: i32 = uapi::OOM_ADJUST_MAX as i32;
1464const OOM_SCORE_ADJ_MAX: i32 = uapi::OOM_SCORE_ADJ_MAX as i32;
1465
1466struct OomAdjFile(Weak<Task>);
1467impl OomAdjFile {
1468    fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1469        BytesFile::new_node(Self(task))
1470    }
1471}
1472
1473impl BytesFileOps for OomAdjFile {
1474    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1475        let value = parse_i32_file(&data)?;
1476        let oom_score_adj = if value == OOM_DISABLE {
1477            OOM_SCORE_ADJ_MIN
1478        } else {
1479            if !(OOM_ADJUST_MIN..=OOM_ADJUST_MAX).contains(&value) {
1480                return error!(EINVAL);
1481            }
1482            let fraction = (value - OOM_ADJUST_MIN) / (OOM_ADJUST_MAX - OOM_ADJUST_MIN);
1483            fraction * (OOM_SCORE_ADJ_MAX - OOM_SCORE_ADJ_MIN) + OOM_SCORE_ADJ_MIN
1484        };
1485        security::check_task_capable(current_task, CAP_SYS_RESOURCE)?;
1486        let task = Task::from_weak(&self.0)?;
1487        task.write().oom_score_adj = oom_score_adj;
1488        Ok(())
1489    }
1490
1491    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1492        let task = Task::from_weak(&self.0)?;
1493        let oom_score_adj = task.read().oom_score_adj;
1494        let oom_adj = if oom_score_adj == OOM_SCORE_ADJ_MIN {
1495            OOM_DISABLE
1496        } else {
1497            let fraction =
1498                (oom_score_adj - OOM_SCORE_ADJ_MIN) / (OOM_SCORE_ADJ_MAX - OOM_SCORE_ADJ_MIN);
1499            fraction * (OOM_ADJUST_MAX - OOM_ADJUST_MIN) + OOM_ADJUST_MIN
1500        };
1501        Ok(serialize_for_file(oom_adj).into())
1502    }
1503}
1504
1505struct OomScoreAdjFile(Weak<Task>);
1506
1507impl OomScoreAdjFile {
1508    fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1509        BytesFile::new_node(Self(task))
1510    }
1511}
1512
1513impl BytesFileOps for OomScoreAdjFile {
1514    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1515        let value = parse_i32_file(&data)?;
1516        if !(OOM_SCORE_ADJ_MIN..=OOM_SCORE_ADJ_MAX).contains(&value) {
1517            return error!(EINVAL);
1518        }
1519        security::check_task_capable(current_task, CAP_SYS_RESOURCE)?;
1520        let task = Task::from_weak(&self.0)?;
1521        task.write().oom_score_adj = value;
1522        Ok(())
1523    }
1524
1525    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1526        let task = Task::from_weak(&self.0)?;
1527        let oom_score_adj = task.read().oom_score_adj;
1528        Ok(serialize_for_file(oom_score_adj).into())
1529    }
1530}
1531
1532struct TimerslackNsFile(Weak<Task>);
1533
1534impl TimerslackNsFile {
1535    fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1536        BytesFile::new_node(Self(task))
1537    }
1538}
1539
1540impl BytesFileOps for TimerslackNsFile {
1541    fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1542        let target_task = Task::from_weak(&self.0)?;
1543        let same_task =
1544            current_task.task.thread_group().leader == target_task.thread_group().leader;
1545        if !same_task {
1546            security::check_task_capable(current_task, CAP_SYS_NICE)?;
1547            security::check_task_setscheduler_access(current_task, &target_task)?;
1548        };
1549
1550        let value = parse_unsigned_file(&data)?;
1551        target_task.write().set_timerslack_ns(value);
1552        Ok(())
1553    }
1554
1555    fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1556        let target_task = Task::from_weak(&self.0)?;
1557        let same_task =
1558            current_task.task.thread_group().leader == target_task.thread_group().leader;
1559        if !same_task {
1560            security::check_task_capable(current_task, CAP_SYS_NICE)?;
1561            security::check_task_getscheduler_access(current_task, &target_task)?;
1562        };
1563
1564        let timerslack_ns = target_task.read().timerslack_ns;
1565        Ok(serialize_for_file(timerslack_ns).into())
1566    }
1567}
1568
1569struct ClearRefsFile(Weak<Task>);
1570
1571impl ClearRefsFile {
1572    fn new_node(task: Weak<Task>) -> impl FsNodeOps {
1573        BytesFile::new_node(Self(task))
1574    }
1575}
1576
1577impl BytesFileOps for ClearRefsFile {
1578    fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
1579        let _task = Task::from_weak(&self.0)?;
1580        track_stub!(TODO("https://fxbug.dev/396221597"), "/proc/pid/clear_refs");
1581        Ok(())
1582    }
1583}