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