use crate::CoreError;
use crate::error::syscall_ret;
use std::ffi::CString;
use std::os::fd::{AsRawFd, FromRawFd};
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
#[inline(always)]
fn errno() -> i32 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcStatus {
pub name: String,
pub uid: u32,
}
pub fn read_proc_status(pid: i32) -> Result<ProcStatus, CoreError> {
read_proc_status_at("/proc", pid)
}
pub fn read_proc_status_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<ProcStatus, CoreError> {
let path = proc_root.as_ref().join(pid.to_string()).join("status");
let content = std::fs::read_to_string(path).map_err(|err| io_error(err, "read_proc_status"))?;
parse_proc_status(&content)
}
pub fn read_proc_cmdline(pid: i32) -> Result<String, CoreError> {
read_proc_cmdline_at("/proc", pid)
}
pub fn read_proc_cmdline_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<String, CoreError> {
let path = proc_root.as_ref().join(pid.to_string()).join("cmdline");
let bytes = std::fs::read(path).map_err(|err| io_error(err, "read_proc_cmdline"))?;
Ok(parse_proc_cmdline_bytes(&bytes))
}
pub(crate) fn parse_proc_cmdline_bytes(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes)
.trim_end_matches('\0')
.replace('\0', " ")
}
pub fn parse_proc_status(content: &str) -> Result<ProcStatus, CoreError> {
let mut name = None;
let mut uid = None;
for line in content.lines() {
if let Some(rest) = line.strip_prefix("Name:") {
name = Some(rest.trim().to_string());
} else if let Some(rest) = line.strip_prefix("Uid:") {
uid = rest
.split_whitespace()
.next()
.and_then(|value| value.parse::<u32>().ok());
}
if name.is_some() && uid.is_some() {
break;
}
}
match (name, uid) {
(Some(name), Some(uid)) => Ok(ProcStatus { name, uid }),
_ => Err(CoreError::sys(libc::EINVAL, "parse_proc_status")),
}
}
fn io_error(err: std::io::Error, op: &'static str) -> CoreError {
CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), op)
}
#[inline(always)]
pub fn clock_ticks_per_second() -> Result<u64, CoreError> {
let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
if ticks <= 0 {
let code = std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::EINVAL);
Err(CoreError::sys(code, "sysconf(_SC_CLK_TCK)"))
} else {
Ok(ticks as u64)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stat {
pub uid: u32,
pub inode: u64,
pub ctime_sec: i64,
pub ctime_nsec: i64,
pub mtime_sec: i64,
pub mtime_nsec: i64,
}
pub fn path_uid(path: impl AsRef<Path>) -> Result<u32, CoreError> {
Ok(path_stat(path)?.uid)
}
pub fn path_stat(path: impl AsRef<Path>) -> Result<Stat, CoreError> {
stat_path(path.as_ref(), "stat", true)
}
pub fn path_lstat(path: impl AsRef<Path>) -> Result<Stat, CoreError> {
stat_path(path.as_ref(), "lstat", false)
}
pub fn uid(pid: i32) -> Result<u32, CoreError> {
uid_at("/proc", pid)
}
pub fn uid_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<u32, CoreError> {
Ok(stat_at(proc_root, pid)?.uid)
}
pub fn stat(pid: i32) -> Result<Stat, CoreError> {
stat_at("/proc", pid)
}
pub fn stat_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<Stat, CoreError> {
let path = proc_root.as_ref().join(pid.to_string());
stat_path(&path, "stat", true)
}
#[inline(always)]
pub fn effective_uid() -> u32 {
unsafe { libc::geteuid() }
}
pub struct ProcDir {
fd: std::os::unix::io::OwnedFd,
}
impl ProcDir {
pub fn open(pid: i32) -> Result<Self, CoreError> {
Self::open_at("/proc", pid)
}
pub fn open_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<Self, CoreError> {
let path = proc_root.as_ref().join(pid.to_string());
let path = CString::new(path.as_os_str().as_bytes())
.map_err(|_| CoreError::sys(libc::EINVAL, "open proc dir"))?;
let fd = unsafe {
libc::open(
path.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if fd < 0 {
return Err(CoreError::sys(errno(), "open proc dir"));
}
let fd = unsafe { std::os::unix::io::OwnedFd::from_raw_fd(fd) };
Ok(Self { fd })
}
pub fn uid(&self) -> Result<u32, CoreError> {
Ok(self.stat()?.uid)
}
pub fn stat(&self) -> Result<Stat, CoreError> {
let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
let empty = CString::new("").map_err(|_| CoreError::sys(libc::EINVAL, "fstatat"))?;
let ret = unsafe {
libc::fstatat(
self.fd.as_raw_fd(),
empty.as_ptr(),
&mut stat_buf,
libc::AT_EMPTY_PATH,
)
};
if ret < 0 && errno() == libc::EINVAL {
let ret = unsafe { libc::fstat(self.fd.as_raw_fd(), &mut stat_buf) };
syscall_ret(ret, "fstat(proc dir)")?;
} else {
syscall_ret(ret, "fstatat(proc dir)")?;
}
Ok(Stat {
uid: stat_buf.st_uid,
inode: stat_buf.st_ino as _,
ctime_sec: stat_buf.st_ctime as _,
ctime_nsec: stat_buf.st_ctime_nsec as _,
mtime_sec: stat_buf.st_mtime as _,
mtime_nsec: stat_buf.st_mtime_nsec as _,
})
}
pub fn status(&self) -> Result<ProcStatus, CoreError> {
let content = self.read_file("status")?;
let content = String::from_utf8(content)
.map_err(|_| CoreError::sys(libc::EINVAL, "decode proc status"))?;
parse_proc_status(&content)
}
pub fn cmdline(&self) -> Result<String, CoreError> {
let bytes = self.read_file("cmdline")?;
Ok(parse_proc_cmdline_bytes(&bytes))
}
fn read_file(&self, name: &str) -> Result<Vec<u8>, CoreError> {
let name =
CString::new(name).map_err(|_| CoreError::sys(libc::EINVAL, "openat(proc file)"))?;
let fd = unsafe {
libc::openat(
self.fd.as_raw_fd(),
name.as_ptr(),
libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if fd < 0 {
return Err(CoreError::sys(errno(), "openat(proc file)"));
}
let mut bytes = Vec::new();
let mut buf = [0u8; 4096];
loop {
let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) };
if n < 0 {
if errno() == libc::EINTR {
continue;
}
let e = errno();
unsafe { libc::close(fd) };
return Err(CoreError::sys(e, "read(proc file)"));
}
if n == 0 {
break;
}
bytes.extend_from_slice(&buf[..n as usize]);
}
unsafe { libc::close(fd) };
Ok(bytes)
}
}
pub fn chown(path: impl AsRef<Path>, uid: u32, gid: Option<u32>) -> Result<(), CoreError> {
let path = CString::new(path.as_ref().as_os_str().as_bytes())
.map_err(|_| CoreError::sys(libc::EINVAL, "chown"))?;
let gid = gid.unwrap_or(u32::MAX);
let ret = unsafe { libc::chown(path.as_ptr(), uid, gid) };
syscall_ret(ret, "chown")
}
fn stat_path(path: &Path, op: &'static str, follow_symlink: bool) -> Result<Stat, CoreError> {
let path =
CString::new(path.as_os_str().as_bytes()).map_err(|_| CoreError::sys(libc::EINVAL, op))?;
let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
let ret = if follow_symlink {
unsafe { libc::stat(path.as_ptr(), &mut stat_buf) }
} else {
unsafe { libc::lstat(path.as_ptr(), &mut stat_buf) }
};
syscall_ret(ret, op)?;
Ok(Stat {
uid: stat_buf.st_uid,
inode: stat_buf.st_ino as _,
ctime_sec: stat_buf.st_ctime as _,
ctime_nsec: stat_buf.st_ctime_nsec as _,
mtime_sec: stat_buf.st_mtime as _,
mtime_nsec: stat_buf.st_mtime_nsec as _,
})
}