| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use std::fmt::Display; | |
| use std::fs::File; | |
| use std::io; | |
| use std::io::BufRead; | |
| use std::io::BufReader; | |
| /// Error of parsing /proc/pid/status | |
| #[derive(Debug)] | |
| pub enum Error { | |
| NotFound(u32), | |
| FileCorrupt, | |
| Io(io::Error), | |
| } | |
| impl std::error::Error for Error { | |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | |
| match self { | |
| Self::NotFound(_) => None, | |
| Self::FileCorrupt => None, | |
| Self::Io(e) => Some(e), | |
| } | |
| } | |
| } | |
| impl Display for Error { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| match self { | |
| Self::NotFound(pid) => f.write_fmt(format_args!("/proc/{pid}/status is not found")), | |
| Self::FileCorrupt => f.write_str("/proc/pid/status invalid format"), | |
| Self::Io(e) => f.write_fmt(format_args!("procfs: {e}")), | |
| } | |
| } | |
| } | |
| pub type Result<T> = std::result::Result<T, Error>; | |
| enum UidIndex { | |
| Real = 0, | |
| Effective = 1, | |
| } | |
| /// Load real user id of the process from /proc/pid/status | |
| pub fn load_ruid(pid: u32) -> Result<u32> { | |
| let mut file = open_status_file(pid)?; | |
| load_uid(&mut file, UidIndex::Real) | |
| } | |
| /// Load effective user id of the process from /proc/pid/status | |
| pub fn load_euid(pid: u32) -> Result<u32> { | |
| let mut file = open_status_file(pid)?; | |
| load_uid(&mut file, UidIndex::Effective) | |
| } | |
| fn open_status_file(pid: u32) -> Result<File> { | |
| File::open(format!("/proc/{}/status", pid)).map_err(|e| { | |
| if e.kind() == io::ErrorKind::NotFound { | |
| Error::NotFound(pid) | |
| } else { | |
| Error::Io(e) | |
| } | |
| }) | |
| } | |
| fn load_uid(file: &mut File, i_uid: UidIndex) -> Result<u32> { | |
| let r = BufReader::with_capacity(1024, file); | |
| for line in r.lines() { | |
| let line = line.map_err(Error::Io)?; | |
| const UID_TAG: &str = "Uid:"; | |
| if let Some(uids) = line.strip_prefix(UID_TAG) { | |
| return uids | |
| .split_whitespace() | |
| .nth(i_uid as usize) | |
| .and_then(|uid| uid.parse().ok()) | |
| .ok_or(Error::FileCorrupt); | |
| } | |
| } | |
| Err(Error::FileCorrupt) | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use std::io::Seek; | |
| use std::io::SeekFrom; | |
| use std::io::Write; | |
| use super::*; | |
| use crate::proc::load_ruid; | |
| #[test] | |
| fn test_load_ruid() { | |
| assert!(load_ruid(std::process::id()).is_ok()); | |
| assert!(matches!( | |
| load_ruid(u32::MAX), | |
| Err(Error::NotFound(u32::MAX)) | |
| )); | |
| } | |
| #[test] | |
| fn test_load_euid() { | |
| assert!(load_euid(std::process::id()).is_ok()); | |
| assert!(matches!( | |
| load_euid(u32::MAX), | |
| Err(Error::NotFound(u32::MAX)) | |
| )); | |
| } | |
| #[test] | |
| fn test_load_uid() { | |
| const STATUS_FILE: &str = "Name: resourced | |
| Umask: 0022 | |
| State: S (sleeping) | |
| Tgid: 12153 | |
| Ngid: 0 | |
| Pid: 12153 | |
| PPid: 1 | |
| TracerPid: 0 | |
| Uid: 20170 20171 20172 20173 | |
| Gid: 20174 20175 20176 20177"; | |
| let mut file = tempfile::tempfile().unwrap(); | |
| file.write_all(STATUS_FILE.as_bytes()).unwrap(); | |
| file.seek(SeekFrom::Start(0)).unwrap(); | |
| assert_eq!(load_uid(&mut file, UidIndex::Real).unwrap(), 20170); | |
| file.seek(SeekFrom::Start(0)).unwrap(); | |
| assert_eq!(load_uid(&mut file, UidIndex::Effective).unwrap(), 20171); | |
| } | |
| } |