| // Copyright 2025, The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! Libraries for Linux /proc/* |
| |
| use anyhow::{anyhow, Context, Error}; |
| use std::collections::HashMap; |
| use std::fs; |
| use std::io::ErrorKind; |
| use std::path::Path; |
| |
| #[derive(Debug)] |
| pub struct ProcHelper { |
| pid_to_comm: HashMap<u32, String>, |
| inode_to_pid: HashMap<u64, u32>, |
| } |
| |
| impl ProcHelper { |
| fn get_socket_inode(fd: &Path) -> Option<u64> { |
| let link = fs::read_link(fd).ok()?; |
| let path_str = link.to_str()?; |
| if path_str.starts_with("socket:[") { |
| Some(path_str[8..path_str.len() - 1].parse().ok()?) |
| } else { |
| None |
| } |
| } |
| |
| // Iterate over /proc/*/fd to build inode lookup table while skipping files/dirs |
| // without permissions. |
| pub fn new() -> Result<Self, Error> { |
| let mut pid_to_comm: HashMap<u32, String> = Default::default(); |
| let mut inode_to_pid: HashMap<u64, u32> = Default::default(); |
| |
| let options = glob::MatchOptions { |
| case_sensitive: true, |
| require_literal_separator: true, |
| require_literal_leading_dot: true, |
| }; |
| for comm in glob::glob_with("/proc/*/comm", options).unwrap() { |
| let comm = comm?; |
| let process = comm.parent().ok_or(anyhow!("Failed to read parent of {comm:?}"))?; |
| let Ok(pid) = process |
| .file_name() |
| .ok_or(anyhow!("Failed to get name of {process:?}"))? |
| .to_str() |
| .ok_or(anyhow!("Failed to read name of {process:?}"))? |
| .parse::<u32>() |
| else { |
| // Skip /proc/self/comm and /proc/thread-self/comm |
| continue; |
| }; |
| let comm = fs::read_to_string(&comm) |
| .with_context(|| format!("Failed to read comm {comm:?}"))? |
| .trim() |
| .to_string(); |
| pid_to_comm.insert(pid, comm); |
| |
| let fd_dir = process.join("fd"); |
| let fds = match fs::read_dir(&fd_dir) { |
| Ok(fds) => fds, |
| Err(e) if e.kind() == ErrorKind::PermissionDenied => continue, |
| e => e.with_context(|| format!("Failed to read dir {fd_dir:?}"))?, |
| }; |
| for fd in fds { |
| if let Some(inode) = Self::get_socket_inode(&fd?.path()) { |
| inode_to_pid.insert(inode, pid); |
| } |
| } |
| } |
| Ok(Self { pid_to_comm, inode_to_pid }) |
| } |
| |
| pub fn comm_with_inode(&self, inode: u64) -> Option<String> { |
| let pid = self.inode_to_pid.get(&inode)?; |
| self.pid_to_comm.get(pid).cloned() |
| } |
| } |