1use crate::CoreError;
12use crate::error::syscall_ret;
13use std::ffi::CString;
14use std::os::fd::{AsRawFd, FromRawFd};
15use std::os::unix::ffi::OsStrExt;
16use std::path::Path;
17
18#[inline(always)]
19fn errno() -> i32 {
20 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ProcStatus {
26 pub name: String,
28 pub uid: u32,
30}
31
32pub fn read_proc_status(pid: i32) -> Result<ProcStatus, CoreError> {
38 read_proc_status_at("/proc", pid)
39}
40
41pub fn read_proc_status_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<ProcStatus, CoreError> {
47 let path = proc_root.as_ref().join(pid.to_string()).join("status");
48 let content = std::fs::read_to_string(path).map_err(|err| io_error(err, "read_proc_status"))?;
49 parse_proc_status(&content)
50}
51
52pub fn read_proc_cmdline(pid: i32) -> Result<String, CoreError> {
61 read_proc_cmdline_at("/proc", pid)
62}
63
64pub fn read_proc_cmdline_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<String, CoreError> {
73 let path = proc_root.as_ref().join(pid.to_string()).join("cmdline");
74 let bytes = std::fs::read(path).map_err(|err| io_error(err, "read_proc_cmdline"))?;
75 Ok(parse_proc_cmdline_bytes(&bytes))
76}
77
78pub(crate) fn parse_proc_cmdline_bytes(bytes: &[u8]) -> String {
79 String::from_utf8_lossy(bytes)
80 .trim_end_matches('\0')
81 .replace('\0', " ")
82}
83
84pub fn parse_proc_status(content: &str) -> Result<ProcStatus, CoreError> {
86 let mut name = None;
87 let mut uid = None;
88
89 for line in content.lines() {
90 if let Some(rest) = line.strip_prefix("Name:") {
91 name = Some(rest.trim().to_string());
92 } else if let Some(rest) = line.strip_prefix("Uid:") {
93 uid = rest
94 .split_whitespace()
95 .next()
96 .and_then(|value| value.parse::<u32>().ok());
97 }
98
99 if name.is_some() && uid.is_some() {
100 break;
101 }
102 }
103
104 match (name, uid) {
105 (Some(name), Some(uid)) => Ok(ProcStatus { name, uid }),
106 _ => Err(CoreError::sys(libc::EINVAL, "parse_proc_status")),
107 }
108}
109
110fn io_error(err: std::io::Error, op: &'static str) -> CoreError {
111 CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), op)
112}
113
114#[inline(always)]
119pub fn clock_ticks_per_second() -> Result<u64, CoreError> {
120 let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
121 if ticks <= 0 {
122 let code = std::io::Error::last_os_error()
123 .raw_os_error()
124 .unwrap_or(libc::EINVAL);
125 Err(CoreError::sys(code, "sysconf(_SC_CLK_TCK)"))
126 } else {
127 Ok(ticks as u64)
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub struct Stat {
134 pub uid: u32,
136 pub inode: u64,
138 pub ctime_sec: i64,
140 pub ctime_nsec: i64,
142 pub mtime_sec: i64,
144 pub mtime_nsec: i64,
146}
147
148pub fn path_uid(path: impl AsRef<Path>) -> Result<u32, CoreError> {
163 Ok(path_stat(path)?.uid)
164}
165
166pub fn path_stat(path: impl AsRef<Path>) -> Result<Stat, CoreError> {
175 stat_path(path.as_ref(), "stat", true)
176}
177
178pub fn path_lstat(path: impl AsRef<Path>) -> Result<Stat, CoreError> {
180 stat_path(path.as_ref(), "lstat", false)
181}
182
183pub fn uid(pid: i32) -> Result<u32, CoreError> {
189 uid_at("/proc", pid)
190}
191
192pub fn uid_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<u32, CoreError> {
197 Ok(stat_at(proc_root, pid)?.uid)
198}
199
200pub fn stat(pid: i32) -> Result<Stat, CoreError> {
202 stat_at("/proc", pid)
203}
204
205pub fn stat_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<Stat, CoreError> {
207 let path = proc_root.as_ref().join(pid.to_string());
208 stat_path(&path, "stat", true)
209}
210
211#[inline(always)]
213pub fn effective_uid() -> u32 {
214 unsafe { libc::geteuid() }
215}
216
217pub struct ProcDir {
225 fd: std::os::unix::io::OwnedFd,
226}
227
228impl ProcDir {
229 pub fn open(pid: i32) -> Result<Self, CoreError> {
235 Self::open_at("/proc", pid)
236 }
237
238 pub fn open_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<Self, CoreError> {
240 let path = proc_root.as_ref().join(pid.to_string());
241 let path = CString::new(path.as_os_str().as_bytes())
242 .map_err(|_| CoreError::sys(libc::EINVAL, "open proc dir"))?;
243 let fd = unsafe {
246 libc::open(
247 path.as_ptr(),
248 libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
249 )
250 };
251 if fd < 0 {
252 return Err(CoreError::sys(errno(), "open proc dir"));
253 }
254 let fd = unsafe { std::os::unix::io::OwnedFd::from_raw_fd(fd) };
255 Ok(Self { fd })
256 }
257
258 pub fn uid(&self) -> Result<u32, CoreError> {
260 Ok(self.stat()?.uid)
261 }
262
263 pub fn stat(&self) -> Result<Stat, CoreError> {
265 let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
266 let empty = CString::new("").map_err(|_| CoreError::sys(libc::EINVAL, "fstatat"))?;
269 let ret = unsafe {
270 libc::fstatat(
271 self.fd.as_raw_fd(),
272 empty.as_ptr(),
273 &mut stat_buf,
274 libc::AT_EMPTY_PATH,
275 )
276 };
277 if ret < 0 && errno() == libc::EINVAL {
280 let ret = unsafe { libc::fstat(self.fd.as_raw_fd(), &mut stat_buf) };
281 syscall_ret(ret, "fstat(proc dir)")?;
282 } else {
283 syscall_ret(ret, "fstatat(proc dir)")?;
284 }
285 Ok(Stat {
286 uid: stat_buf.st_uid,
287 inode: stat_buf.st_ino as _,
288 ctime_sec: stat_buf.st_ctime as _,
289 ctime_nsec: stat_buf.st_ctime_nsec as _,
290 mtime_sec: stat_buf.st_mtime as _,
291 mtime_nsec: stat_buf.st_mtime_nsec as _,
292 })
293 }
294
295 pub fn status(&self) -> Result<ProcStatus, CoreError> {
297 let content = self.read_file("status")?;
298 let content = String::from_utf8(content)
299 .map_err(|_| CoreError::sys(libc::EINVAL, "decode proc status"))?;
300 parse_proc_status(&content)
301 }
302
303 pub fn cmdline(&self) -> Result<String, CoreError> {
305 let bytes = self.read_file("cmdline")?;
306 Ok(parse_proc_cmdline_bytes(&bytes))
307 }
308
309 fn read_file(&self, name: &str) -> Result<Vec<u8>, CoreError> {
310 let name =
311 CString::new(name).map_err(|_| CoreError::sys(libc::EINVAL, "openat(proc file)"))?;
312 let fd = unsafe {
313 libc::openat(
314 self.fd.as_raw_fd(),
315 name.as_ptr(),
316 libc::O_RDONLY | libc::O_CLOEXEC,
317 )
318 };
319 if fd < 0 {
320 return Err(CoreError::sys(errno(), "openat(proc file)"));
321 }
322 let mut bytes = Vec::new();
323 let mut buf = [0u8; 4096];
324 loop {
325 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) };
326 if n < 0 {
327 if errno() == libc::EINTR {
328 continue;
329 }
330 let e = errno();
331 unsafe { libc::close(fd) };
332 return Err(CoreError::sys(e, "read(proc file)"));
333 }
334 if n == 0 {
335 break;
336 }
337 bytes.extend_from_slice(&buf[..n as usize]);
338 }
339 unsafe { libc::close(fd) };
340 Ok(bytes)
341 }
342}
343
344pub fn chown(path: impl AsRef<Path>, uid: u32, gid: Option<u32>) -> Result<(), CoreError> {
351 let path = CString::new(path.as_ref().as_os_str().as_bytes())
352 .map_err(|_| CoreError::sys(libc::EINVAL, "chown"))?;
353 let gid = gid.unwrap_or(u32::MAX);
354 let ret = unsafe { libc::chown(path.as_ptr(), uid, gid) };
355 syscall_ret(ret, "chown")
356}
357
358fn stat_path(path: &Path, op: &'static str, follow_symlink: bool) -> Result<Stat, CoreError> {
359 let path =
360 CString::new(path.as_os_str().as_bytes()).map_err(|_| CoreError::sys(libc::EINVAL, op))?;
361 let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
362 let ret = if follow_symlink {
363 unsafe { libc::stat(path.as_ptr(), &mut stat_buf) }
364 } else {
365 unsafe { libc::lstat(path.as_ptr(), &mut stat_buf) }
366 };
367 syscall_ret(ret, op)?;
368 Ok(Stat {
369 uid: stat_buf.st_uid,
370 inode: stat_buf.st_ino as _,
371 ctime_sec: stat_buf.st_ctime as _,
372 ctime_nsec: stat_buf.st_ctime_nsec as _,
373 mtime_sec: stat_buf.st_mtime as _,
374 mtime_nsec: stat_buf.st_mtime_nsec as _,
375 })
376}