Skip to main content

starnix_modules_procfs/
pid_directory.rs

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