1#![doc = include_str!("../README.md")]
2#![cfg(any(target_os = "linux", target_os = "android"))]
3#![no_std]
4
5use core::mem::MaybeUninit;
6use once_cell::sync::OnceCell;
7use rustix::cstr;
8use rustix::fd::{AsFd, BorrowedFd, OwnedFd};
9use rustix::ffi::CStr;
10use rustix::fs::{
11 fstat, fstatfs, major, openat, readlinkat_raw, renameat, seek, FileType, FsWord, Mode, OFlags,
12 RawDir, SeekFrom, Stat, CWD, PROC_SUPER_MAGIC,
13};
14use rustix::io;
15use rustix::path::DecInt;
16
17const PROC_ROOT_INO: u64 = 1;
19
20#[derive(Copy, Clone, Debug)]
22enum Kind {
23 Proc,
24 Pid,
25 Fd,
26 File,
27 Symlink,
28}
29
30fn check_proc_entry(
32 kind: Kind,
33 entry: BorrowedFd<'_>,
34 proc_stat: Option<&Stat>,
35) -> io::Result<Stat> {
36 let entry_stat = fstat(entry)?;
37 check_proc_entry_with_stat(kind, entry, entry_stat, proc_stat)
38}
39
40fn check_proc_entry_with_stat(
42 kind: Kind,
43 entry: BorrowedFd<'_>,
44 entry_stat: Stat,
45 proc_stat: Option<&Stat>,
46) -> io::Result<Stat> {
47 check_procfs(entry)?;
49
50 match kind {
51 Kind::Proc => check_proc_root(entry, &entry_stat)?,
52 Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?,
53 Kind::File => check_proc_file(&entry_stat, proc_stat)?,
54 Kind::Symlink => check_proc_symlink(&entry_stat, proc_stat)?,
55 }
56
57 match kind {
61 Kind::Symlink => {
62 }
64 _ => {
65 let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 };
66 if entry_stat.st_mode & 0o777 & !expected_mode != 0 {
67 return Err(io::Errno::NOTSUP);
68 }
69 }
70 }
71
72 match kind {
73 Kind::Fd => {
74 if entry_stat.st_nlink != 2 {
78 return Err(io::Errno::NOTSUP);
79 }
80 }
81 Kind::Pid | Kind::Proc => {
82 if entry_stat.st_nlink <= 2 {
85 return Err(io::Errno::NOTSUP);
86 }
87 }
88 Kind::File => {
89 if entry_stat.st_nlink != 1 {
92 return Err(io::Errno::NOTSUP);
93 }
94 }
95 Kind::Symlink => {
96 if entry_stat.st_nlink != 1 {
99 return Err(io::Errno::NOTSUP);
100 }
101 }
102 }
103
104 Ok(entry_stat)
105}
106
107fn check_proc_root(entry: BorrowedFd<'_>, stat: &Stat) -> io::Result<()> {
108 assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
111
112 if stat.st_ino != PROC_ROOT_INO {
114 return Err(io::Errno::NOTSUP);
115 }
116
117 if major(stat.st_dev) != 0 {
120 return Err(io::Errno::NOTSUP);
121 }
122
123 if !is_mountpoint(entry) {
125 return Err(io::Errno::NOTSUP);
126 }
127
128 Ok(())
129}
130
131fn check_proc_subdir(
132 entry: BorrowedFd<'_>,
133 stat: &Stat,
134 proc_stat: Option<&Stat>,
135) -> io::Result<()> {
136 assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory);
139
140 check_proc_nonroot(stat, proc_stat)?;
141
142 if is_mountpoint(entry) {
144 return Err(io::Errno::NOTSUP);
145 }
146
147 Ok(())
148}
149
150fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
151 if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile {
153 return Err(io::Errno::NOTSUP);
154 }
155
156 check_proc_nonroot(stat, proc_stat)?;
157
158 Ok(())
159}
160
161fn check_proc_symlink(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
162 if FileType::from_raw_mode(stat.st_mode) != FileType::Symlink {
164 return Err(io::Errno::NOTSUP);
165 }
166
167 check_proc_nonroot(stat, proc_stat)?;
168
169 Ok(())
170}
171
172fn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
173 if stat.st_ino == PROC_ROOT_INO {
175 return Err(io::Errno::NOTSUP);
176 }
177
178 if stat.st_dev != proc_stat.unwrap().st_dev {
180 return Err(io::Errno::NOTSUP);
181 }
182
183 Ok(())
184}
185
186fn check_procfs(file: BorrowedFd<'_>) -> io::Result<()> {
188 let statfs = fstatfs(file)?;
189 let f_type = statfs.f_type;
190 if f_type != FsWord::from(PROC_SUPER_MAGIC) {
191 return Err(io::Errno::NOTSUP);
192 }
193
194 Ok(())
195}
196
197fn is_mountpoint(file: BorrowedFd<'_>) -> bool {
199 let err = renameat(file, cstr!("../."), file, cstr!(".")).unwrap_err();
202 match err {
203 io::Errno::XDEV => true, io::Errno::BUSY => false, _ => panic!("Unexpected error from `renameat`: {:?}", err),
206 }
207}
208
209fn proc_opendirat<P: rustix::path::Arg, Fd: AsFd>(dirfd: Fd, path: P) -> io::Result<OwnedFd> {
211 let oflags =
214 OFlags::RDONLY | OFlags::NOFOLLOW | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOCTTY;
215 openat(dirfd, path, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)
216}
217
218fn proc() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
228 static PROC: StaticFd = StaticFd::new();
229
230 PROC.get_or_try_init(|| {
234 let proc = proc_opendirat(CWD, cstr!("/proc"))?;
236 let proc_stat =
237 check_proc_entry(Kind::Proc, proc.as_fd(), None).map_err(|_err| io::Errno::NOTSUP)?;
238
239 Ok(new_static_fd(proc, proc_stat))
240 })
241 .map(|(fd, stat)| (fd.as_fd(), stat))
242}
243
244#[allow(unsafe_code)]
254fn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
255 static PROC_SELF: StaticFd = StaticFd::new();
256
257 PROC_SELF
259 .get_or_try_init(|| {
260 let (proc, proc_stat) = proc()?;
261
262 let self_symlink = open_and_check_file(proc, proc_stat, cstr!("self"), Kind::Symlink)?;
266 let mut buf = [MaybeUninit::<u8>::uninit(); 20];
267 let (init, _uninit) = readlinkat_raw(self_symlink, cstr!(""), &mut buf)?;
268 let pid: &[u8] = unsafe { core::mem::transmute(init) };
269
270 let proc_self = proc_opendirat(proc, pid)?;
273 let proc_self_stat = check_proc_entry(Kind::Pid, proc_self.as_fd(), Some(proc_stat))
274 .map_err(|_err| io::Errno::NOTSUP)?;
275
276 Ok(new_static_fd(proc_self, proc_self_stat))
277 })
278 .map(|(owned, stat)| (owned.as_fd(), stat))
279}
280
281#[cfg_attr(docsrs, doc(cfg(feature = "procfs")))]
291pub fn proc_self_fd() -> io::Result<BorrowedFd<'static>> {
292 static PROC_SELF_FD: StaticFd = StaticFd::new();
293
294 PROC_SELF_FD
296 .get_or_try_init(|| {
297 let (_, proc_stat) = proc()?;
298
299 let (proc_self, _proc_self_stat) = proc_self()?;
300
301 let proc_self_fd = proc_opendirat(proc_self, cstr!("fd"))?;
303 let proc_self_fd_stat =
304 check_proc_entry(Kind::Fd, proc_self_fd.as_fd(), Some(proc_stat))
305 .map_err(|_err| io::Errno::NOTSUP)?;
306
307 Ok(new_static_fd(proc_self_fd, proc_self_fd_stat))
308 })
309 .map(|(owned, _stat)| owned.as_fd())
310}
311
312type StaticFd = OnceCell<(OwnedFd, Stat)>;
313
314#[inline]
315fn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) {
316 (fd, stat)
317}
318
319fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
330 static PROC_SELF_FDINFO: StaticFd = StaticFd::new();
331
332 PROC_SELF_FDINFO
333 .get_or_try_init(|| {
334 let (_, proc_stat) = proc()?;
335
336 let (proc_self, _proc_self_stat) = proc_self()?;
337
338 let proc_self_fdinfo = proc_opendirat(proc_self, cstr!("fdinfo"))?;
340 let proc_self_fdinfo_stat =
341 check_proc_entry(Kind::Fd, proc_self_fdinfo.as_fd(), Some(proc_stat))
342 .map_err(|_err| io::Errno::NOTSUP)?;
343
344 Ok((proc_self_fdinfo, proc_self_fdinfo_stat))
345 })
346 .map(|(owned, stat)| (owned.as_fd(), stat))
347}
348
349#[inline]
359#[cfg_attr(docsrs, doc(cfg(feature = "procfs")))]
360pub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
361 _proc_self_fdinfo(fd.as_fd())
362}
363
364fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
365 let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?;
366 let fd_str = DecInt::from_fd(fd);
367 open_and_check_file(
368 proc_self_fdinfo,
369 proc_self_fdinfo_stat,
370 fd_str.as_c_str(),
371 Kind::File,
372 )
373}
374
375#[inline]
387#[cfg_attr(docsrs, doc(cfg(feature = "procfs")))]
388pub fn proc_self_pagemap() -> io::Result<OwnedFd> {
389 proc_self_file(cstr!("pagemap"))
390}
391
392#[inline]
402#[cfg_attr(docsrs, doc(cfg(feature = "procfs")))]
403pub fn proc_self_maps() -> io::Result<OwnedFd> {
404 proc_self_file(cstr!("maps"))
405}
406
407#[inline]
417#[cfg_attr(docsrs, doc(cfg(feature = "procfs")))]
418pub fn proc_self_status() -> io::Result<OwnedFd> {
419 proc_self_file(cstr!("status"))
420}
421
422fn proc_self_file(name: &CStr) -> io::Result<OwnedFd> {
424 let (proc_self, proc_self_stat) = proc_self()?;
425 open_and_check_file(proc_self, proc_self_stat, name, Kind::File)
426}
427
428fn open_and_check_file(
430 dir: BorrowedFd<'_>,
431 dir_stat: &Stat,
432 name: &CStr,
433 kind: Kind,
434) -> io::Result<OwnedFd> {
435 let (_, proc_stat) = proc()?;
436
437 let mut oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY;
444 if let Kind::Symlink = kind {
445 oflags |= OFlags::PATH;
447 }
448 let file = openat(dir, name, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?;
449 let file_stat = fstat(&file)?;
450
451 let expected_type = match kind {
462 Kind::File => FileType::RegularFile,
463 Kind::Symlink => FileType::Symlink,
464 _ => unreachable!(),
465 };
466
467 let mut found_file = false;
468 let mut found_dot = false;
469
470 let oflags =
473 OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY | OFlags::DIRECTORY;
474 let dir = openat(dir, cstr!("."), oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?;
475 let check_dir_stat = fstat(&dir)?;
476 if check_dir_stat.st_dev != dir_stat.st_dev || check_dir_stat.st_ino != dir_stat.st_ino {
477 return Err(io::Errno::NOTSUP);
478 }
479
480 seek(&dir, SeekFrom::Start(0))?;
482
483 let mut buf = [MaybeUninit::uninit(); 2048];
484 let mut iter = RawDir::new(dir, &mut buf);
485 while let Some(entry) = iter.next() {
486 let entry = entry.map_err(|_err| io::Errno::NOTSUP)?;
487 if entry.ino() == file_stat.st_ino
488 && entry.file_type() == expected_type
489 && entry.file_name() == name
490 {
491 let _ = check_proc_entry_with_stat(kind, file.as_fd(), file_stat, Some(proc_stat))?;
493
494 found_file = true;
495 } else if entry.ino() == dir_stat.st_ino
496 && entry.file_type() == FileType::Directory
497 && entry.file_name() == cstr!(".")
498 {
499 found_dot = true;
501 }
502 }
503
504 if found_file && found_dot {
505 Ok(file)
506 } else {
507 Err(io::Errno::NOTSUP)
508 }
509}