1use 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
56const TASK_ENTRIES: &[(&[u8], FileMode)] = &[
58 (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 (b"task", mode!(IFDIR, 0o555)),
93];
94
95fn 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
103pub 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 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
357impl 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 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
393pub fn pid_directory(
395 current_task: &CurrentTask,
396 fs: &FileSystemHandle,
397 task: &Arc<Task>,
398 tid: Pid,
399) -> FsNodeHandle {
400 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
409fn tid_directory(fs: &FileSystemHandle, task: &Arc<Task>) -> FsNodeHandle {
411 TaskDirectory::new(fs, task, TaskContainer::from_task(task))
412}
413
414struct 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 let file = task.files()?.get_allowing_opath(fd).map_err(|_| errno!(ENOENT))?;
452 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
485struct 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 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
541struct 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 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 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 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 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 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
667struct 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
732struct 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 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 let buf = task.read_memory_partial_to_vec(range_start, len)?;
863 sink.write(&buf[..]);
864 Ok(())
865}
866
867#[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 let Ok(task) = self.target.get_task() else {
885 return Ok(());
886 };
887 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 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#[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 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#[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 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#[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(), ¤t_task.thread_group()) {
1029 return error!(EINVAL);
1030 }
1031 let bytes = data.read_all()?;
1034 task.set_command_name(TaskCommand::new(&bytes));
1035 Ok(bytes.len())
1036 }
1037}
1038
1039#[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#[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
1110pub struct MemFile {
1112 mm: Weak<MemoryManager>,
1113
1114 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
1206fn 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 let pid: pid_t; let comm: TaskCommand;
1250 let state: char;
1251 let ppid: pid_t;
1252 let pgrp: pid_t; let session: pid_t;
1254 let tty_nr: i32;
1255 let tpgid: i32 = 0;
1256 let flags: u32 = 0;
1257 let minflt: u64 = 0; let cminflt: u64 = 0;
1259 let majflt: u64 = 0;
1260 let cmajflt: u64 = 0;
1261 let utime: i64;
1262 let stime: i64; let cutime: i64;
1264 let cstime: i64;
1265 let priority: i64 = 0;
1266 let nice: i64;
1267 let num_threads: i64; 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; 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; 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; 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; 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; 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; 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_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 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 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 let task = self.target.get_task()?;
1404 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 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 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 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 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 let seccomp = task.seccomp_filter_state.get() as u8;
1528 writeln!(sink, "Seccomp:\t{}", seccomp)?;
1529
1530 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
1573const 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}