Files
Magisk/native/src/base/files.rs

901 lines
25 KiB
Rust
Raw Normal View History

use crate::{
2025-09-08 11:24:33 -07:00
Directory, FsPathFollow, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf,
2025-04-22 02:55:53 -07:00
cstr, errno, error,
};
2025-03-24 01:35:16 -07:00
use bytemuck::{Pod, bytes_of, bytes_of_mut};
2025-09-08 23:59:29 -07:00
use libc::{c_uint, makedev, mode_t};
2025-10-18 00:00:04 -07:00
use nix::errno::Errno;
use nix::fcntl::{AT_FDCWD, OFlag};
use nix::sys::stat::{FchmodatFlags, Mode};
use nix::unistd::{AccessFlags, Gid, Uid};
2025-01-07 00:53:21 -08:00
use num_traits::AsPrimitive;
2023-05-04 18:49:33 -07:00
use std::cmp::min;
2022-09-15 01:17:05 -07:00
use std::ffi::CStr;
use std::fmt::Display;
2023-06-09 02:00:37 -07:00
use std::fs::File;
2024-02-27 18:14:30 -08:00
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
2025-09-11 01:16:26 -07:00
use std::mem::MaybeUninit;
use std::os::fd::{AsFd, BorrowedFd};
2024-04-10 22:36:47 -07:00
use std::os::unix::ffi::OsStrExt;
2025-09-08 23:59:29 -07:00
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
2024-04-10 22:36:47 -07:00
use std::path::Path;
use std::{io, mem, ptr, slice};
2022-09-15 01:17:05 -07:00
2023-05-26 14:07:11 -07:00
pub trait ReadExt {
fn skip(&mut self, len: usize) -> io::Result<()>;
2023-09-14 13:10:09 -07:00
fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()>;
2023-05-26 14:07:11 -07:00
}
impl<T: Read> ReadExt for T {
fn skip(&mut self, mut len: usize) -> io::Result<()> {
let mut buf = MaybeUninit::<[u8; 4096]>::uninit();
let buf = unsafe { buf.assume_init_mut() };
while len > 0 {
let l = min(buf.len(), len);
self.read_exact(&mut buf[..l])?;
len -= l;
}
Ok(())
}
2023-06-20 00:19:40 -07:00
2023-09-14 13:10:09 -07:00
fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()> {
self.read_exact(bytes_of_mut(data))
2023-06-20 00:19:40 -07:00
}
2023-05-26 14:07:11 -07:00
}
pub trait ReadSeekExt {
fn skip(&mut self, len: usize) -> io::Result<()>;
}
impl<T: Read + Seek> ReadSeekExt for T {
fn skip(&mut self, len: usize) -> io::Result<()> {
if self.seek(SeekFrom::Current(len as i64)).is_err() {
// If the file is not actually seekable, fallback to read
ReadExt::skip(self, len)?;
}
Ok(())
}
}
2023-05-24 19:11:56 -07:00
pub trait BufReadExt {
fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, f: F);
fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, f: F);
2023-05-24 19:11:56 -07:00
}
impl<T: BufRead> BufReadExt for T {
fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {
2023-05-24 19:11:56 -07:00
let mut buf = String::new();
loop {
match self.read_line(&mut buf) {
Ok(0) => break,
Ok(_) => {
if !f(&mut buf) {
break;
}
}
Err(e) => {
error!("{}", e);
break;
}
};
buf.clear();
}
}
fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {
self.for_each_line(|line| {
// Reserve an additional byte, because this string will be manually
// null terminated on the C++ side, and it may need more space.
line.reserve(1);
2023-05-24 19:11:56 -07:00
let line = line.trim();
if line.starts_with('#') {
return true;
}
if let Some((key, value)) = line.split_once('=') {
2023-06-20 00:19:40 -07:00
return f(key.trim(), value.trim());
2023-05-24 19:11:56 -07:00
}
2023-05-30 22:23:11 -07:00
true
2023-05-24 19:11:56 -07:00
});
}
}
2023-05-04 18:49:33 -07:00
pub trait WriteExt {
fn write_zeros(&mut self, len: usize) -> io::Result<()>;
2025-01-13 12:29:42 +08:00
fn write_pod<F: Pod>(&mut self, data: &F) -> io::Result<()>;
2023-05-04 18:49:33 -07:00
}
impl<T: Write> WriteExt for T {
fn write_zeros(&mut self, mut len: usize) -> io::Result<()> {
2023-05-30 22:23:11 -07:00
let buf = [0_u8; 4096];
2023-05-04 18:49:33 -07:00
while len > 0 {
let l = min(buf.len(), len);
2023-05-30 22:23:11 -07:00
self.write_all(&buf[..l])?;
2023-05-04 18:49:33 -07:00
len -= l;
}
Ok(())
}
2025-01-13 12:29:42 +08:00
fn write_pod<F: Pod>(&mut self, data: &F) -> io::Result<()> {
self.write_all(bytes_of(data))
}
2023-05-04 18:49:33 -07:00
}
2023-06-09 02:00:37 -07:00
pub enum FileOrStd {
StdIn,
StdOut,
StdErr,
File(File),
}
impl FileOrStd {
pub fn as_file(&self) -> &File {
let raw_fd_ref: &'static RawFd = match self {
FileOrStd::StdIn => &0,
FileOrStd::StdOut => &1,
FileOrStd::StdErr => &2,
FileOrStd::File(file) => return file,
};
// SAFETY: File is guaranteed to have the same ABI as RawFd
unsafe { mem::transmute(raw_fd_ref) }
}
}
2025-09-08 23:59:29 -07:00
fn open_fd(path: &Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'_, OwnedFd> {
nix::fcntl::open(path, flags, Mode::from_bits_truncate(mode)).into_os_result(
"open",
Some(path),
None,
)
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
2025-04-23 14:16:46 -07:00
let path = cstr::buf::new::<64>()
.join_path("/proc/self/fd")
.join_path_fmt(fd);
path.read_link(buf).map_err(|e| e.set_args(None, None))
}
2023-09-26 20:18:37 -07:00
pub struct FileAttr {
pub st: libc::stat,
2023-10-17 13:29:15 -07:00
#[cfg(feature = "selinux")]
2025-02-18 15:39:59 -08:00
pub con: crate::Utf8CStrBufArr<128>,
2023-09-26 20:18:37 -07:00
}
2025-08-08 17:27:45 -07:00
impl Default for FileAttr {
fn default() -> Self {
Self::new()
}
}
2023-10-17 13:29:15 -07:00
impl FileAttr {
2025-08-08 17:27:45 -07:00
pub fn new() -> Self {
2023-10-17 13:29:15 -07:00
FileAttr {
st: unsafe { mem::zeroed() },
#[cfg(feature = "selinux")]
2025-02-18 15:39:59 -08:00
con: crate::Utf8CStrBufArr::new(),
2023-10-17 13:29:15 -07:00
}
}
2024-04-09 19:34:14 -07:00
#[inline(always)]
#[allow(clippy::unnecessary_cast)]
fn is(&self, mode: mode_t) -> bool {
2024-04-16 19:45:01 -07:00
(self.st.st_mode & libc::S_IFMT as c_uint) as mode_t == mode
2024-04-09 19:34:14 -07:00
}
pub fn is_dir(&self) -> bool {
self.is(libc::S_IFDIR)
}
pub fn is_file(&self) -> bool {
self.is(libc::S_IFREG)
}
pub fn is_symlink(&self) -> bool {
self.is(libc::S_IFLNK)
}
pub fn is_block_device(&self) -> bool {
self.is(libc::S_IFBLK)
}
pub fn is_char_device(&self) -> bool {
self.is(libc::S_IFCHR)
}
pub fn is_fifo(&self) -> bool {
self.is(libc::S_IFIFO)
}
pub fn is_socket(&self) -> bool {
self.is(libc::S_IFSOCK)
}
2025-05-08 21:00:40 -07:00
pub fn is_whiteout(&self) -> bool {
self.is_char_device() && self.st.st_rdev == 0
}
2023-09-26 20:18:37 -07:00
}
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
2023-06-09 02:00:37 -07:00
2025-09-08 11:24:33 -07:00
// Low-level methods, we should track the caller when error occurs, so return OsResult.
impl Utf8CStr {
pub fn follow_link(&self) -> &FsPathFollow {
unsafe { mem::transmute(self) }
2025-04-21 18:00:43 -07:00
}
2025-09-08 23:59:29 -07:00
pub fn open(&self, flags: OFlag) -> OsResult<'_, File> {
2025-04-23 14:16:46 -07:00
Ok(File::from(open_fd(self, flags, 0)?))
}
2025-09-08 23:59:29 -07:00
pub fn create(&self, flags: OFlag, mode: mode_t) -> OsResult<'_, File> {
Ok(File::from(open_fd(self, OFlag::O_CREAT | flags, mode)?))
}
pub fn exists(&self) -> bool {
2025-09-08 23:59:29 -07:00
nix::sys::stat::lstat(self).is_ok()
}
pub fn rename_to<'a>(&'a self, name: &'a Utf8CStr) -> OsResult<'a, ()> {
2025-09-08 23:59:29 -07:00
nix::fcntl::renameat(AT_FDCWD, self, AT_FDCWD, name).check_os_err(
"rename",
Some(self),
Some(name),
)
}
2025-08-28 16:15:59 -07:00
pub fn remove(&self) -> OsResult<'_, ()> {
unsafe { libc::remove(self.as_ptr()).check_os_err("remove", Some(self), None) }
}
2025-02-17 01:46:19 -08:00
#[allow(clippy::unnecessary_cast)]
2025-08-28 16:15:59 -07:00
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
buf.clear();
unsafe {
2025-02-18 15:39:59 -08:00
let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1)
2025-09-08 10:55:57 -07:00
.into_os_result("readlink", Some(self), None)? as isize;
2025-02-15 10:56:59 -08:00
*(buf.as_mut_ptr().offset(r) as *mut u8) = b'\0';
}
buf.rebuild().ok();
Ok(())
}
2025-08-28 16:15:59 -07:00
pub fn mkdir(&self, mode: mode_t) -> OsResult<'_, ()> {
2025-09-08 23:59:29 -07:00
match nix::unistd::mkdir(self, Mode::from_bits_truncate(mode)) {
Ok(_) | Err(Errno::EEXIST) => Ok(()),
Err(e) => Err(OsError::new(e, "mkdir", Some(self), None)),
2023-09-26 20:18:37 -07:00
}
}
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
2025-08-28 16:15:59 -07:00
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
2025-09-08 23:59:29 -07:00
let fd = self.open(OFlag::O_PATH | OFlag::O_CLOEXEC)?;
let mut skip_check = false;
2025-09-08 23:59:29 -07:00
let st1 = match nix::sys::stat::fstat(&fd) {
Ok(st) => st,
Err(_) => {
// This will only fail on Linux < 3.6
skip_check = true;
2025-09-08 23:59:29 -07:00
unsafe { mem::zeroed() }
}
2025-09-08 23:59:29 -07:00
};
fd_path(fd.as_raw_fd(), buf)?;
2025-09-08 23:59:29 -07:00
let st2 = nix::sys::stat::stat(buf.as_cstr()).into_os_result("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
return Err(OsError::new(Errno::ENOENT, "realpath", Some(self), None));
}
Ok(())
}
2023-09-26 20:18:37 -07:00
2025-08-28 16:15:59 -07:00
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
2025-09-08 23:59:29 -07:00
#[allow(unused_mut)]
let mut attr = FileAttr {
st: nix::sys::stat::lstat(self).into_os_result("lstat", Some(self), None)?,
2023-10-17 13:29:15 -07:00
#[cfg(feature = "selinux")]
2025-09-08 23:59:29 -07:00
con: cstr::buf::new(),
};
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
2023-09-26 20:18:37 -07:00
Ok(attr)
}
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
2025-09-08 23:59:29 -07:00
if !attr.is_symlink()
2025-09-14 22:12:48 -07:00
&& let Err(e) = self.follow_link().chmod((attr.st.st_mode & 0o777).as_())
2025-09-08 23:59:29 -07:00
{
// Double check if self is symlink before reporting error
let self_attr = self.get_attr()?;
if !self_attr.is_symlink() {
2025-09-14 22:12:48 -07:00
return Err(e);
2023-09-26 20:18:37 -07:00
}
2025-09-08 23:59:29 -07:00
}
unsafe {
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"lchown",
Some(self),
None,
)?;
2025-09-08 23:59:29 -07:00
}
2023-10-17 13:29:15 -07:00
2025-09-08 23:59:29 -07:00
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
2023-09-26 20:18:37 -07:00
}
Ok(())
}
2025-08-28 16:15:59 -07:00
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
con.clear();
let result = unsafe {
libc::lgetxattr(
2025-02-18 15:39:59 -08:00
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
2025-02-18 15:39:59 -08:00
con.as_mut_ptr().cast(),
con.capacity(),
)
.check_err()
};
match result {
Ok(_) => {
con.rebuild().ok();
Ok(())
2025-02-18 15:39:59 -08:00
}
Err(Errno::ENODATA) => Ok(()),
Err(e) => Err(OsError::new(e, "lgetxattr", Some(self), None)),
2025-02-18 15:39:59 -08:00
}
}
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
2025-02-18 15:39:59 -08:00
unsafe {
libc::lsetxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
2025-02-18 15:39:59 -08:00
con.as_ptr().cast(),
con.len() + 1,
0,
)
.check_os_err("lsetxattr", Some(self), Some(con))
2025-02-18 15:39:59 -08:00
}
}
2025-09-08 11:24:33 -07:00
pub fn parent_dir(&self) -> Option<&str> {
Path::new(self.as_str())
.parent()
.map(Path::as_os_str)
// SAFETY: all substring of self is valid UTF-8
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })
}
pub fn file_name(&self) -> Option<&str> {
Path::new(self.as_str())
.file_name()
// SAFETY: all substring of self is valid UTF-8
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })
}
// ln -s target self
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
2025-09-08 23:59:29 -07:00
nix::unistd::symlinkat(target, AT_FDCWD, self).check_os_err(
"symlink",
Some(target),
Some(self),
)
2025-09-08 11:24:33 -07:00
}
pub fn mkfifo(&self, mode: mode_t) -> OsResult<'_, ()> {
2025-09-08 23:59:29 -07:00
nix::unistd::mkfifo(self, Mode::from_bits_truncate(mode)).check_os_err(
"mkfifo",
Some(self),
None,
)
2025-09-08 11:24:33 -07:00
}
}
// High-level helper methods, composed of multiple operations.
// We should treat these as application logic and log ASAP, so return LoggedResult.
impl Utf8CStr {
pub fn remove_all(&self) -> LoggedResult<()> {
let attr = match self.get_attr() {
Ok(attr) => attr,
Err(e) => {
return match e.errno {
// Allow calling remove_all on non-existence file
Errno::ENOENT => Ok(()),
_ => Err(e)?,
};
}
};
2025-09-08 11:24:33 -07:00
if attr.is_dir() {
2025-09-08 23:59:29 -07:00
let dir = Directory::open(self)?;
2025-09-08 11:24:33 -07:00
dir.remove_all()?;
}
Ok(self.remove()?)
}
pub fn mkdirs(&self, mode: mode_t) -> LoggedResult<()> {
if self.is_empty() {
return Ok(());
}
let mut path = cstr::buf::default();
let mut components = self.split('/').filter(|s| !s.is_empty());
if self.starts_with('/') {
path.append_path("/");
}
loop {
let Some(s) = components.next() else {
break;
};
path.append_path(s);
2025-09-08 23:59:29 -07:00
path.mkdir(mode)?;
2025-09-08 11:24:33 -07:00
}
*errno() = 0;
Ok(())
}
pub fn copy_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
2023-09-26 20:18:37 -07:00
let attr = self.get_attr()?;
2024-04-09 19:34:14 -07:00
if attr.is_dir() {
2023-09-26 20:18:37 -07:00
path.mkdir(0o777)?;
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
src.copy_into(&dest)?;
} else {
// It's OK if remove failed
path.remove().ok();
2024-04-09 19:34:14 -07:00
if attr.is_file() {
2025-09-08 23:59:29 -07:00
let mut src = self.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;
let mut dest = path.create(
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,
0o777,
)?;
2023-09-26 20:18:37 -07:00
std::io::copy(&mut src, &mut dest)?;
2024-04-09 19:34:14 -07:00
} else if attr.is_symlink() {
2025-04-22 02:55:53 -07:00
let mut buf = cstr::buf::default();
2023-09-26 20:18:37 -07:00
self.read_link(&mut buf)?;
unsafe {
libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
"symlink",
Some(&buf),
Some(path),
)?;
2023-09-26 20:18:37 -07:00
}
}
}
path.set_attr(&attr)?;
Ok(())
}
2025-09-08 11:24:33 -07:00
pub fn move_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
2023-09-26 20:18:37 -07:00
if path.exists() {
let attr = path.get_attr()?;
2024-04-09 19:34:14 -07:00
if attr.is_dir() {
2023-09-26 20:18:37 -07:00
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
return src.move_into(&dest);
} else {
path.remove()?;
}
}
self.rename_to(path)?;
Ok(())
2023-09-26 20:18:37 -07:00
}
2025-03-24 01:35:16 -07:00
// ln self path
2025-09-08 11:24:33 -07:00
pub fn link_to(&self, path: &Utf8CStr) -> LoggedResult<()> {
2023-09-26 20:18:37 -07:00
let attr = self.get_attr()?;
2024-04-09 19:34:14 -07:00
if attr.is_dir() {
2023-09-26 20:18:37 -07:00
path.mkdir(0o777)?;
path.set_attr(&attr)?;
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
Ok(src.link_into(&dest)?)
2023-09-26 20:18:37 -07:00
} else {
unsafe {
libc::link(self.as_ptr(), path.as_ptr()).check_os_err(
"link",
Some(self),
Some(path),
)?;
}
Ok(())
2023-09-26 20:18:37 -07:00
}
}
}
impl FsPathFollow {
pub fn exists(&self) -> bool {
2025-09-14 22:12:48 -07:00
nix::unistd::access(self.as_utf8_cstr(), AccessFlags::F_OK).is_ok()
}
pub fn chmod(&self, mode: mode_t) -> OsResult<'_, ()> {
nix::sys::stat::fchmodat(
AT_FDCWD,
self.as_utf8_cstr(),
Mode::from_bits_truncate(mode),
FchmodatFlags::FollowSymlink,
)
.check_os_err("chmod", Some(self), None)
}
2025-08-28 16:15:59 -07:00
pub fn get_attr(&self) -> OsResult<'_, FileAttr> {
2025-09-08 23:59:29 -07:00
#[allow(unused_mut)]
let mut attr = FileAttr {
2025-09-14 22:12:48 -07:00
st: nix::sys::stat::stat(self.as_utf8_cstr()).into_os_result(
"lstat",
Some(self),
None,
)?,
#[cfg(feature = "selinux")]
2025-09-08 23:59:29 -07:00
con: cstr::buf::new(),
};
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
Ok(attr)
}
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
2025-09-14 22:12:48 -07:00
self.chmod((attr.st.st_mode & 0o777).as_())?;
2025-09-08 23:59:29 -07:00
2025-09-11 01:16:26 -07:00
nix::unistd::chown(
2025-09-14 22:12:48 -07:00
self.as_utf8_cstr(),
2025-09-11 01:16:26 -07:00
Some(Uid::from(attr.st.st_uid)),
Some(Gid::from(attr.st.st_gid)),
)
.check_os_err("chown", Some(self), None)?;
2025-09-08 23:59:29 -07:00
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
self.set_secontext(&attr.con)?;
}
Ok(())
}
2025-08-28 16:15:59 -07:00
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {
con.clear();
let result = unsafe {
libc::getxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
)
.check_err()
};
match result {
Ok(_) => {
con.rebuild().ok();
Ok(())
}
Err(Errno::ENODATA) => Ok(()),
Err(e) => Err(OsError::new(e, "getxattr", Some(self), None)),
}
}
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::setxattr(
self.as_ptr(),
XATTR_NAME_SELINUX.as_ptr(),
con.as_ptr().cast(),
con.len() + 1,
0,
)
.check_os_err("setxattr", Some(self), Some(con))
}
}
}
pub trait FsPathBuilder {
fn join_path<T: AsRef<str>>(mut self, path: T) -> Self
where
Self: Sized,
{
self.append_path(path);
self
}
fn join_path_fmt<T: Display>(mut self, name: T) -> Self
where
Self: Sized,
{
self.append_path_fmt(name);
self
}
fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self;
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self;
}
fn append_path_impl(buf: &mut dyn Utf8CStrBuf, path: &str) {
if path.starts_with('/') {
buf.clear();
}
if !buf.is_empty() && !buf.ends_with('/') {
buf.push_str("/");
}
buf.push_str(path);
}
impl<S: Utf8CStrBuf + Sized> FsPathBuilder for S {
fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {
append_path_impl(self, path.as_ref());
self
}
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
2025-07-02 19:36:27 -07:00
self.write_fmt(format_args!("/{name}")).ok();
self
}
}
impl FsPathBuilder for dyn Utf8CStrBuf + '_ {
fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {
append_path_impl(self, path.as_ref());
self
}
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
2025-07-02 19:36:27 -07:00
self.write_fmt(format_args!("/{name}")).ok();
self
}
}
pub fn fd_get_attr(fd: RawFd) -> OsResult<'static, FileAttr> {
2023-10-17 13:29:15 -07:00
let mut attr = FileAttr::new();
2023-09-26 20:18:37 -07:00
unsafe {
libc::fstat(fd, &mut attr.st).check_os_err("fstat", None, None)?;
2023-10-17 13:29:15 -07:00
#[cfg(feature = "selinux")]
fd_get_secontext(fd, &mut attr.con)?;
2023-09-26 20:18:37 -07:00
}
Ok(attr)
}
2025-08-28 16:15:59 -07:00
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<'_, ()> {
2023-09-26 20:18:37 -07:00
unsafe {
libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).check_os_err("fchmod", None, None)?;
libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).check_os_err("fchown", None, None)?;
2023-10-17 13:29:15 -07:00
#[cfg(feature = "selinux")]
2023-09-26 20:18:37 -07:00
if !attr.con.is_empty() {
fd_set_secontext(fd, &attr.con)?;
2023-09-26 20:18:37 -07:00
}
}
Ok(())
}
pub fn fd_get_secontext(fd: RawFd, con: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
con.clear();
let result = unsafe {
libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
)
.check_err()
};
match result {
Ok(_) => {
con.rebuild().ok();
Ok(())
}
Err(Errno::ENODATA) => Ok(()),
Err(e) => Err(OsError::new(e, "fgetxattr", None, None)),
}
}
2025-08-28 16:15:59 -07:00
pub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<'_, ()> {
unsafe {
libc::fsetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
con.as_ptr().cast(),
con.len() + 1,
0,
)
.check_os_err("fsetxattr", Some(con), None)
}
}
2025-04-21 18:00:43 -07:00
pub fn clone_attr<'a>(a: &'a Utf8CStr, b: &'a Utf8CStr) -> OsResult<'a, ()> {
let attr = a.get_attr().map_err(|e| e.set_args(Some(a), None))?;
b.set_attr(&attr).map_err(|e| e.set_args(Some(b), None))
2023-09-26 20:18:37 -07:00
}
pub fn fclone_attr(a: RawFd, b: RawFd) -> OsResult<'static, ()> {
2023-09-26 20:18:37 -07:00
let attr = fd_get_attr(a)?;
fd_set_attr(b, &attr).map_err(|e| e.set_args(None, None))
}
pub struct MappedFile(&'static mut [u8]);
impl MappedFile {
2025-08-28 16:15:59 -07:00
pub fn open(path: &Utf8CStr) -> OsResult<'_, MappedFile> {
Ok(MappedFile(map_file(path, false)?))
}
2025-08-28 16:15:59 -07:00
pub fn open_rw(path: &Utf8CStr) -> OsResult<'_, MappedFile> {
Ok(MappedFile(map_file(path, true)?))
}
pub fn openat<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {
Ok(MappedFile(map_file_at(dir.as_fd(), path, false)?))
}
pub fn openat_rw<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {
Ok(MappedFile(map_file_at(dir.as_fd(), path, true)?))
}
pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<MappedFile> {
Ok(MappedFile(map_fd(fd, sz, rw)?))
}
}
impl AsRef<[u8]> for MappedFile {
fn as_ref(&self) -> &[u8] {
self.0
}
}
impl AsMut<[u8]> for MappedFile {
fn as_mut(&mut self) -> &mut [u8] {
self.0
}
}
impl Drop for MappedFile {
fn drop(&mut self) {
unsafe {
libc::munmap(self.0.as_mut_ptr().cast(), self.0.len());
}
}
}
2025-03-06 23:04:02 -08:00
unsafe extern "C" {
2023-09-26 20:18:37 -07:00
// Don't use the declaration from the libc crate as request should be u32 not i32
fn ioctl(fd: RawFd, request: u32, ...) -> i32;
}
// We mark the returned slice static because it is valid until explicitly unmapped
2025-08-28 16:15:59 -07:00
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<'_, &'static mut [u8]> {
2025-09-08 23:59:29 -07:00
map_file_at(AT_FDCWD, path, rw)
}
pub(crate) fn map_file_at<'a>(
dirfd: BorrowedFd,
path: &'a Utf8CStr,
rw: bool,
) -> OsResult<'a, &'static mut [u8]> {
2023-06-25 07:21:35 +08:00
#[cfg(target_pointer_width = "64")]
const BLKGETSIZE64: u32 = 0x80081272;
2023-06-25 07:21:35 +08:00
#[cfg(target_pointer_width = "32")]
const BLKGETSIZE64: u32 = 0x80041272;
2025-09-08 23:59:29 -07:00
let flag = if rw { OFlag::O_RDWR } else { OFlag::O_RDONLY };
let fd = nix::fcntl::openat(dirfd, path, flag | OFlag::O_CLOEXEC, Mode::empty())
.into_os_result("openat", Some(path), None)?;
let attr = fd_get_attr(fd.as_raw_fd())?;
2024-04-09 19:34:14 -07:00
let sz = if attr.is_block_device() {
let mut sz = 0_u64;
unsafe {
ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz).check_os_err("ioctl", Some(path), None)?;
}
sz
} else {
2024-04-09 19:34:14 -07:00
attr.st.st_size as u64
};
map_fd(fd.as_fd(), sz as usize, rw).map_err(|e| e.set_args(Some(path), None))
}
pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<'static, &'static mut [u8]> {
let flag = if rw {
libc::MAP_SHARED
} else {
libc::MAP_PRIVATE
};
unsafe {
let ptr = libc::mmap(
ptr::null_mut(),
sz,
libc::PROT_READ | libc::PROT_WRITE,
flag,
fd.as_raw_fd(),
0,
);
if ptr == libc::MAP_FAILED {
return Err(OsError::last_os_error("mmap", None, None));
2023-06-09 02:00:37 -07:00
}
Ok(slice::from_raw_parts_mut(ptr.cast(), sz))
2023-06-09 02:00:37 -07:00
}
}
2024-02-27 18:14:30 -08:00
#[allow(dead_code)]
pub struct MountInfo {
pub id: u32,
pub parent: u32,
pub device: u64,
pub root: String,
pub target: String,
pub vfs_option: String,
pub shared: u32,
pub master: u32,
pub propagation_from: u32,
pub unbindable: bool,
pub fs_type: String,
pub source: String,
pub fs_option: String,
}
#[allow(clippy::useless_conversion)]
fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
let mut iter = line.split_whitespace();
let id = iter.next()?.parse().ok()?;
let parent = iter.next()?.parse().ok()?;
let (maj, min) = iter.next()?.split_once(':')?;
let maj = maj.parse().ok()?;
let min = min.parse().ok()?;
let device = makedev(maj, min).into();
let root = iter.next()?.to_string();
let target = iter.next()?.to_string();
let vfs_option = iter.next()?.to_string();
let mut optional = iter.next()?;
let mut shared = 0;
let mut master = 0;
let mut propagation_from = 0;
let mut unbindable = false;
while optional != "-" {
if let Some(peer) = optional.strip_prefix("master:") {
master = peer.parse().ok()?;
} else if let Some(peer) = optional.strip_prefix("shared:") {
shared = peer.parse().ok()?;
} else if let Some(peer) = optional.strip_prefix("propagate_from:") {
propagation_from = peer.parse().ok()?;
} else if optional == "unbindable" {
unbindable = true;
}
optional = iter.next()?;
}
let fs_type = iter.next()?.to_string();
let source = iter.next()?.to_string();
let fs_option = iter.next()?.to_string();
Some(MountInfo {
id,
parent,
device,
root,
target,
vfs_option,
shared,
master,
propagation_from,
unbindable,
fs_type,
source,
fs_option,
})
}
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
let mut res = vec![];
2025-07-02 19:36:27 -07:00
let mut path = format!("/proc/{pid}/mountinfo");
2025-09-08 23:59:29 -07:00
if let Ok(file) = Utf8CStr::from_string(&mut path).open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {
BufReader::new(file).for_each_line(|line| {
2024-02-27 18:14:30 -08:00
parse_mount_info_line(line)
.map(|info| res.push(info))
.is_some()
});
}
res
}