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`].
38/// Return the owning UID for a filesystem path.
39///
40/// This performs a `stat(2)` call and returns the owner UID from the resulting
41/// metadata. Missing files, permission errors, and invalid path bytes are
42/// surfaced as [`CoreError`].
43///
44/// ### Errors
45/// - `EACCES`: Permission denied for a component of the path prefix.
46/// - `ENOENT`: The path does not exist.
47pub fn path_uid(path: impl AsRef<Path>) -> Result<u32, CoreError> {
48 Ok(path_stat(path)?.uid)
49}
50
51/// Return identity metadata for a filesystem path, following symbolic links.
52///
53/// This performs a `stat(2)` call and captures the fields used by hot-path
54/// procfs callers to detect identity changes cheaply without opening procfs
55/// text files.
56///
57/// ### Errors
58/// Same as [`path_uid`].
59pub fn path_stat(path: impl AsRef<Path>) -> Result<PathStat, CoreError> {
60 stat_path(path.as_ref(), "stat", true)
61}
62
63/// Return identity metadata for a filesystem path without following symbolic links.
64pub fn path_lstat(path: impl AsRef<Path>) -> Result<PathStat, CoreError> {
65 stat_path(path.as_ref(), "lstat", false)
66}
67
68/// Return the owning UID for `/proc/<pid>`.
69///
70/// This is a cheap ownership probe that can be useful before reading procfs
71/// files such as `/proc/<pid>/cmdline` in hot paths. Processes may disappear
72/// at any time, so callers should treat `ENOENT` as a normal race.
73pub fn proc_uid(pid: i32) -> Result<u32, CoreError> {
74 proc_uid_at("/proc", pid)
75}
76
77/// Return the owning UID for `/proc/<pid>` under an explicit procfs root.
78///
79/// This is a cheap ownership probe for tests, alternate proc mounts, or
80/// callers that need to inspect a procfs tree other than the host `/proc`.
81pub fn proc_uid_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<u32, CoreError> {
82 Ok(proc_stat_at(proc_root, pid)?.uid)
83}
84
85/// Return identity metadata for `/proc/<pid>`.
86pub fn proc_stat(pid: i32) -> Result<PathStat, CoreError> {
87 proc_stat_at("/proc", pid)
88}
89
90/// Return identity metadata for `/proc/<pid>` under an explicit procfs root.
91pub fn proc_stat_at(proc_root: impl AsRef<Path>, pid: i32) -> Result<PathStat, CoreError> {
92 let path = proc_root.as_ref().join(pid.to_string());
93 stat_path(&path, "stat", true)
94}
95
96/// Return the effective UID of the current process.
97#[inline(always)]
98pub fn effective_uid() -> u32 {
99 unsafe { libc::geteuid() }
100}
101
102/// Change the owner of a path while leaving the group unchanged when `gid` is `None`.
103///
104/// ### Errors
105/// - `EACCES`: Permission denied for a component of the path prefix.
106/// - `EPERM`: The caller does not have permission to change the owner.
107/// - `ENOENT`: The path does not exist.
108pub fn chown_path(path: impl AsRef<Path>, uid: u32, gid: Option<u32>) -> Result<(), CoreError> {
109 let path = CString::new(path.as_ref().as_os_str().as_bytes())
110 .map_err(|_| CoreError::sys(libc::EINVAL, "chown"))?;
111 let gid = gid.unwrap_or(u32::MAX);
112 let ret = unsafe { libc::chown(path.as_ptr(), uid, gid) };
113 syscall_ret(ret, "chown")
114}
115
116fn stat_path(path: &Path, op: &'static str, follow_symlink: bool) -> Result<PathStat, CoreError> {
117 let path =
118 CString::new(path.as_os_str().as_bytes()).map_err(|_| CoreError::sys(libc::EINVAL, op))?;
119 let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
120 let ret = if follow_symlink {
121 unsafe { libc::stat(path.as_ptr(), &mut stat_buf) }
122 } else {
123 unsafe { libc::lstat(path.as_ptr(), &mut stat_buf) }
124 };
125 syscall_ret(ret, op)?;
126 Ok(PathStat {
127 uid: stat_buf.st_uid,
128 inode: stat_buf.st_ino as _,
129 ctime_sec: stat_buf.st_ctime as _,
130 ctime_nsec: stat_buf.st_ctime_nsec as _,
131 mtime_sec: stat_buf.st_mtime as _,
132 mtime_nsec: stat_buf.st_mtime_nsec as _,
133 })
134}