Skip to main content

coreshift_core/
uid.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! Ownership and UID lookup helpers.
6//!
7//! These helpers provide cheap ownership probes for filesystem paths and
8//! `/proc/<pid>` directories before higher layers do more expensive work.
9
10use crate::CoreError;
11use crate::error::syscall_ret;
12use std::ffi::CString;
13use std::os::unix::ffi::OsStrExt;
14use std::path::Path;
15
16/// Filesystem identity derived from `stat(2)`.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct PathStat {
19    /// Owning UID from `st_uid`.
20    pub uid: u32,
21    /// Inode number from `st_ino`.
22    pub inode: u64,
23    /// Change time seconds from `st_ctime`.
24    pub ctime_sec: i64,
25    /// Change time nanoseconds from `st_ctime_nsec`.
26    pub ctime_nsec: i64,
27    /// Modification time seconds from `st_mtime`.
28    pub mtime_sec: i64,
29    /// Modification time nanoseconds from `st_mtime_nsec`.
30    pub mtime_nsec: i64,
31}
32
33/// Return the owning UID for a filesystem path.
34///
35/// This performs a `stat(2)` call and returns the owner UID from the resulting
36/// metadata. Missing files, permission errors, and invalid path bytes are
37/// surfaced as [`CoreError`].
38pub fn path_uid(path: impl AsRef<Path>) -> Result<u32, CoreError> {
39    Ok(path_stat(path)?.uid)
40}
41
42/// Return identity metadata for a filesystem path, following symbolic links.
43///
44/// This performs a `stat(2)` call and captures the fields used by hot-path
45/// procfs callers to detect identity changes cheaply without opening procfs
46/// text files.
47pub fn path_stat(path: impl AsRef<Path>) -> Result<PathStat, CoreError> {
48    stat_path(path.as_ref(), "stat", true)
49}
50
51/// Return identity metadata for a filesystem path without following symbolic links.
52pub fn path_lstat(path: impl AsRef<Path>) -> Result<PathStat, CoreError> {
53    stat_path(path.as_ref(), "lstat", false)
54}
55
56/// Return the owning UID for `/proc/<pid>`.
57///
58/// This is a cheap ownership probe that can be useful before reading procfs
59/// files such as `/proc/<pid>/cmdline` in hot paths. Processes may disappear
60/// at any time, so callers should treat `ENOENT` as a normal race.
61pub fn proc_uid(pid: i32) -> Result<u32, CoreError> {
62    proc_uid_at("/proc", pid)
63}
64
65/// Return the owning UID for `/proc/<pid>` under an explicit procfs root.
66///
67/// This is a cheap ownership probe for tests, alternate proc mounts, or
68/// callers that need to inspect a procfs tree other than the host `/proc`.
69pub fn proc_uid_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<u32, CoreError> {
70    Ok(proc_stat_at(proc_root, pid)?.uid)
71}
72
73/// Return identity metadata for `/proc/<pid>`.
74pub fn proc_stat(pid: i32) -> Result<PathStat, CoreError> {
75    proc_stat_at("/proc", pid)
76}
77
78/// Return identity metadata for `/proc/<pid>` under an explicit procfs root.
79pub fn proc_stat_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<PathStat, CoreError> {
80    let path = proc_root.as_ref().join(pid.to_string());
81    stat_path(&path, "stat", true)
82}
83
84/// Return the effective UID of the current process.
85#[inline(always)]
86pub fn effective_uid() -> u32 {
87    unsafe { libc::geteuid() }
88}
89
90/// Change the owner of a path while leaving the group unchanged when `gid` is `None`.
91pub fn chown_path(path: impl AsRef<Path>, uid: u32, gid: Option<u32>) -> Result<(), CoreError> {
92    let path = CString::new(path.as_ref().as_os_str().as_bytes())
93        .map_err(|_| CoreError::sys(libc::EINVAL, "chown"))?;
94    let gid = gid.unwrap_or(u32::MAX);
95    let ret = unsafe { libc::chown(path.as_ptr(), uid, gid) };
96    syscall_ret(ret, "chown")
97}
98
99fn stat_path(path: &Path, op: &'static str, follow_symlink: bool) -> Result<PathStat, CoreError> {
100    let path =
101        CString::new(path.as_os_str().as_bytes()).map_err(|_| CoreError::sys(libc::EINVAL, op))?;
102    let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
103    let ret = if follow_symlink {
104        unsafe { libc::stat(path.as_ptr(), &mut stat_buf) }
105    } else {
106        unsafe { libc::lstat(path.as_ptr(), &mut stat_buf) }
107    };
108    syscall_ret(ret, op)?;
109    Ok(PathStat {
110        uid: stat_buf.st_uid,
111        inode: stat_buf.st_ino as _,
112        ctime_sec: stat_buf.st_ctime as _,
113        ctime_nsec: stat_buf.st_ctime_nsec as _,
114        mtime_sec: stat_buf.st_mtime as _,
115        mtime_nsec: stat_buf.st_mtime_nsec as _,
116    })
117}