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