Provide richer error messages

Make sure most syscall/libc calls results are mapped to OsResult
that can produce more detailed error messages.
This commit is contained in:
topjohnwu 2025-04-14 18:05:14 -07:00 committed by John Wu
parent c1e061603b
commit 7bd901273c
13 changed files with 541 additions and 326 deletions

View File

@ -1,6 +1,5 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST // Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use std::io;
use std::os::fd::{BorrowedFd, OwnedFd, RawFd}; use std::os::fd::{BorrowedFd, OwnedFd, RawFd};
use cfg_if::cfg_if; use cfg_if::cfg_if;
@ -9,8 +8,8 @@ use libc::{c_char, mode_t};
use crate::files::map_file_at; use crate::files::map_file_at;
pub(crate) use crate::xwrap::*; pub(crate) use crate::xwrap::*;
use crate::{ use crate::{
CxxResultExt, Directory, FsPath, Utf8CStr, clone_attr, cstr, cstr_buf, fclone_attr, fd_path, CxxResultExt, Directory, FsPath, OsResultStatic, Utf8CStr, clone_attr, cstr, cstr_buf,
map_fd, map_file, slice_from_ptr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr,
}; };
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
@ -57,7 +56,7 @@ unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool { unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
fn inner(fd: OwnedFd) -> io::Result<()> { fn inner(fd: OwnedFd) -> OsResultStatic<()> {
Directory::try_from(fd)?.remove_all() Directory::try_from(fd)?.remove_all()
} }
inner(fd).is_ok() inner(fd).is_ok()

View File

@ -1,14 +1,14 @@
use crate::cxx_extern::readlinkat_for_cxx; use crate::cxx_extern::readlinkat_for_cxx;
use crate::{ use crate::{
FileAttr, FsPath, LibcReturn, Utf8CStr, Utf8CStrBuf, cstr, cstr_buf, errno, fd_path, FileAttr, FsPath, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf, cstr,
fd_set_attr, cstr_buf, errno, fd_path, fd_set_attr,
}; };
use libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, dirent}; use libc::{EEXIST, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, dirent, mode_t};
use std::ffi::CStr; use std::ffi::CStr;
use std::fs::File; use std::fs::File;
use std::ops::Deref; use std::ops::Deref;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::{io, mem, slice}; use std::{mem, slice};
pub struct DirEntry<'a> { pub struct DirEntry<'a> {
dir: &'a Directory, dir: &'a Directory,
@ -26,7 +26,12 @@ impl DirEntry<'_> {
} }
} }
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { #[inline(always)]
fn utf8_name(&self) -> Option<&str> {
self.name().to_str().ok()
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
self.dir.path(buf)?; self.dir.path(buf)?;
buf.push_str("/"); buf.push_str("/");
buf.push_lossy(self.name().to_bytes()); buf.push_lossy(self.name().to_bytes());
@ -61,15 +66,19 @@ impl DirEntry<'_> {
self.d_type == libc::DT_SOCK self.d_type == libc::DT_SOCK
} }
pub fn unlink(&self) -> io::Result<()> { pub fn unlink(&self) -> OsResult<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 }; let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe { unsafe {
libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?; libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err(
"unlinkat",
self.utf8_name(),
None,
)?;
} }
Ok(()) Ok(())
} }
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
buf.clear(); buf.clear();
unsafe { unsafe {
let r = readlinkat_for_cxx( let r = readlinkat_for_cxx(
@ -78,52 +87,66 @@ impl DirEntry<'_> {
buf.as_mut_ptr().cast(), buf.as_mut_ptr().cast(),
buf.capacity(), buf.capacity(),
) )
.check_os_err()? as usize; .as_os_result("readlinkat", self.utf8_name(), None)? as usize;
buf.set_len(r); buf.set_len(r);
} }
Ok(()) Ok(())
} }
unsafe fn open_fd(&self, flags: i32) -> io::Result<RawFd> { pub fn open_as_dir(&self) -> OsResult<Directory> {
unsafe { self.dir.open_raw_fd(self.name(), flags, 0) }
}
pub fn open_as_dir(&self) -> io::Result<Directory> {
if !self.is_dir() { if !self.is_dir() {
return Err(io::Error::from(io::ErrorKind::NotADirectory)); return Err(OsError::with_os_error(
libc::ENOTDIR,
"fdopendir",
self.utf8_name(),
None,
));
} }
unsafe { Directory::try_from(OwnedFd::from_raw_fd(self.open_fd(O_RDONLY)?)) } self.dir.openat_as_dir(self.name())
} }
pub fn open_as_file(&self, flags: i32) -> io::Result<File> { pub fn open_as_file(&self, flags: i32) -> OsResult<File> {
if self.is_dir() { if self.is_dir() {
return Err(io::Error::from(io::ErrorKind::IsADirectory)); return Err(OsError::with_os_error(
libc::EISDIR,
"open_as_file",
self.utf8_name(),
None,
));
} }
unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) } self.dir.openat_as_file(self.name(), flags, 0)
} }
pub fn get_attr(&self) -> io::Result<FileAttr> { pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut path = cstr_buf::default(); let mut path = cstr_buf::default();
self.path(&mut path)?; self.path(&mut path)?;
FsPath::from(&path).get_attr() FsPath::from(&path)
.get_attr()
.map_err(|e| e.set_args(self.utf8_name(), None))
} }
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { pub fn set_attr(&self, attr: &FileAttr) -> OsResult<()> {
let mut path = cstr_buf::default(); let mut path = cstr_buf::default();
self.path(&mut path)?; self.path(&mut path)?;
FsPath::from(&path).set_attr(attr) FsPath::from(&path)
.set_attr(attr)
.map_err(|e| e.set_args(self.utf8_name(), None))
} }
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
let mut path = cstr_buf::default(); let mut path = cstr_buf::default();
self.path(&mut path)?; self.path(&mut path)?;
FsPath::from(&path).get_secontext(con) FsPath::from(&path)
.get_secontext(con)
.map_err(|e| e.set_args(self.utf8_name(), None))
} }
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> { pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
let mut path = cstr_buf::default(); let mut path = cstr_buf::default();
self.path(&mut path)?; self.path(&mut path)?;
FsPath::from(&path).set_secontext(con) FsPath::from(&path)
.set_secontext(con)
.map_err(|e| e.set_args(self.utf8_name(), Some(con)))
} }
} }
@ -146,17 +169,18 @@ pub enum WalkResult {
} }
impl Directory { impl Directory {
pub fn open(path: &Utf8CStr) -> io::Result<Directory> { pub fn open(path: &Utf8CStr) -> OsResult<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?; let dirp = unsafe { libc::opendir(path.as_ptr()) };
let dirp = dirp.as_os_result("opendir", Some(path), None)?;
Ok(Directory { dirp }) Ok(Directory { dirp })
} }
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> { pub fn read(&mut self) -> OsResult<'static, Option<DirEntry>> {
*errno() = 0; *errno() = 0;
let e = unsafe { libc::readdir(self.dirp) }; let e = unsafe { libc::readdir(self.dirp) };
if e.is_null() { if e.is_null() {
return if *errno() != 0 { return if *errno() != 0 {
Err(io::Error::last_os_error()) Err(OsError::last_os_error("readdir", None, None))
} else { } else {
Ok(None) Ok(None)
}; };
@ -182,17 +206,33 @@ impl Directory {
unsafe { libc::rewinddir(self.dirp) } unsafe { libc::rewinddir(self.dirp) }
} }
unsafe fn open_raw_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result<RawFd> { fn openat<'a>(&self, name: &'a CStr, flags: i32, mode: u32) -> OsResult<'a, OwnedFd> {
unsafe { unsafe {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err() libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode)
.as_os_result("openat", name.to_str().ok(), None)
.map(|fd| OwnedFd::from_raw_fd(fd))
} }
} }
pub fn open_fd(&self, name: &Utf8CStr, flags: i32, mode: i32) -> io::Result<OwnedFd> { pub fn openat_as_dir<'a>(&self, name: &'a CStr) -> OsResult<'a, Directory> {
let fd = self.openat(name, O_RDONLY, 0)?;
Directory::try_from(fd).map_err(|e| e.set_args(name.to_str().ok(), None))
}
pub fn openat_as_file<'a>(&self, name: &'a CStr, flags: i32, mode: u32) -> OsResult<'a, File> {
let fd = self.openat(name, flags, mode)?;
Ok(File::from(fd))
}
pub fn mkdirat<'a>(&self, name: &'a CStr, mode: u32) -> OsResult<'a, ()> {
unsafe { unsafe {
self.open_raw_fd(name.as_cstr(), flags, mode) if libc::mkdirat(self.as_raw_fd(), name.as_ptr(), mode as mode_t) < 0
.map(|fd| OwnedFd::from_raw_fd(fd)) && *errno() != EEXIST
{
return Err(OsError::last_os_error("mkdirat", name.to_str().ok(), None));
}
} }
Ok(())
} }
pub fn contains_path(&self, path: &CStr) -> bool { pub fn contains_path(&self, path: &CStr) -> bool {
@ -210,25 +250,25 @@ impl Directory {
} }
} }
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
fd_path(self.as_raw_fd(), buf) fd_path(self.as_raw_fd(), buf)
} }
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>( pub fn post_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self, &mut self,
mut f: F, mut f: F,
) -> io::Result<WalkResult> { ) -> OsResultStatic<WalkResult> {
self.post_order_walk_impl(&mut f) self.post_order_walk_impl(&mut f)
} }
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>( pub fn pre_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self, &mut self,
mut f: F, mut f: F,
) -> io::Result<WalkResult> { ) -> OsResultStatic<WalkResult> {
self.pre_order_walk_impl(&mut f) self.pre_order_walk_impl(&mut f)
} }
pub fn remove_all(&mut self) -> io::Result<()> { pub fn remove_all(&mut self) -> OsResultStatic<()> {
self.post_order_walk(|e| { self.post_order_walk(|e| {
e.unlink()?; e.unlink()?;
Ok(WalkResult::Continue) Ok(WalkResult::Continue)
@ -236,60 +276,47 @@ impl Directory {
Ok(()) Ok(())
} }
pub fn copy_into(&mut self, dir: &Directory) -> io::Result<()> { pub fn copy_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
while let Some(ref e) = self.read()? { while let Some(ref e) = self.read()? {
let attr = e.get_attr()?; let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
if e.is_dir() { if e.is_dir() {
unsafe { dir.mkdirat(e.name(), 0o777)?;
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let mut src = e.open_as_dir()?; let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?; let dest = dir.openat_as_dir(e.name())?;
src.copy_into(&dest)?; src.copy_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?; fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() { } else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?; let mut src = e.open_as_file(O_RDONLY)?;
let mut dest = unsafe { let mut dest = dir.openat_as_file(e.name(), O_WRONLY | O_CREAT | O_TRUNC, 0o777)?;
File::from_raw_fd(dir.open_raw_fd(
e.name(),
O_WRONLY | O_CREAT | O_TRUNC,
0o777,
)?)
};
std::io::copy(&mut src, &mut dest)?; std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?; fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() { } else if e.is_symlink() {
let mut path = cstr_buf::default(); let mut target = cstr_buf::default();
e.read_link(&mut path)?; e.read_link(&mut target)?;
unsafe { unsafe {
libc::symlinkat(path.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr()) libc::symlinkat(target.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr())
.as_os_err()?; .check_os_err("symlinkat", Some(&target), e.utf8_name())?;
} }
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
new_entry.set_attr(&attr)?; new_entry.set_attr(&attr)?;
} }
} }
Ok(()) Ok(())
} }
pub fn move_into(&mut self, dir: &Directory) -> io::Result<()> { pub fn move_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd(); let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? { while let Some(ref e) = self.read()? {
if e.is_dir() && dir.contains_path(e.name()) { if e.is_dir() && dir.contains_path(e.name()) {
// Destination folder exists, needs recursive move // Destination folder exists, needs recursive move
let mut src = e.open_as_dir()?; let mut src = e.open_as_dir()?;
let new_entry = DirEntry { let dest = dir.openat_as_dir(e.name())?;
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let dest = new_entry.open_as_dir()?;
src.move_into(&dest)?; src.move_into(&dest)?;
return e.unlink(); return Ok(e.unlink()?);
} }
unsafe { unsafe {
@ -299,27 +326,20 @@ impl Directory {
dir.as_raw_fd(), dir.as_raw_fd(),
e.d_name.as_ptr(), e.d_name.as_ptr(),
) )
.as_os_err()?; .check_os_err("renameat", e.utf8_name(), None)?;
} }
} }
Ok(()) Ok(())
} }
pub fn link_into(&mut self, dir: &Directory) -> io::Result<()> { pub fn link_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd(); let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? { while let Some(ref e) = self.read()? {
if e.is_dir() { if e.is_dir() {
unsafe { dir.mkdirat(e.name(), 0o777)?;
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let attr = e.get_attr()?; let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let mut src = e.open_as_dir()?; let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?; let dest = dir.openat_as_dir(e.name())?;
src.link_into(&dest)?; src.link_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?; fd_set_attr(dest.as_raw_fd(), &attr)?;
} else { } else {
@ -331,7 +351,7 @@ impl Directory {
e.d_name.as_ptr(), e.d_name.as_ptr(),
0, 0,
) )
.as_os_err()?; .check_os_err("linkat", e.utf8_name(), None)?;
} }
} }
} }
@ -340,10 +360,10 @@ impl Directory {
} }
impl Directory { impl Directory {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>( fn post_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self, &mut self,
f: &mut F, f: &mut F,
) -> io::Result<WalkResult> { ) -> OsResultStatic<WalkResult> {
use WalkResult::*; use WalkResult::*;
loop { loop {
match self.read()? { match self.read()? {
@ -365,10 +385,10 @@ impl Directory {
} }
} }
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>( fn pre_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self, &mut self,
f: &mut F, f: &mut F,
) -> io::Result<WalkResult> { ) -> OsResultStatic<WalkResult> {
use WalkResult::*; use WalkResult::*;
loop { loop {
match self.read()? { match self.read()? {
@ -391,10 +411,11 @@ impl Directory {
} }
impl TryFrom<OwnedFd> for Directory { impl TryFrom<OwnedFd> for Directory {
type Error = io::Error; type Error = OsError<'static>;
fn try_from(fd: OwnedFd) -> io::Result<Self> { fn try_from(fd: OwnedFd) -> OsResult<'static, Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }.check_os_err()?; let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
let dirp = dirp.as_os_result("fdopendir", None, None)?;
Ok(Directory { dirp }) Ok(Directory { dirp })
} }
} }

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, Utf8CStr, Utf8CStrBuf, cstr_buf, errno, Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, OsError, OsResult, OsResultStatic,
error, Utf8CStr, Utf8CStrBuf, cstr_buf, errno, error,
}; };
use bytemuck::{Pod, bytes_of, bytes_of_mut}; use bytemuck::{Pod, bytes_of, bytes_of_mut};
use libc::{ use libc::{
@ -19,28 +19,6 @@ use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::path::Path; use std::path::Path;
use std::{io, mem, ptr, slice}; use std::{io, mem, ptr, slice};
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).check_os_err()?;
Ok(OwnedFd::from_raw_fd(fd))
}
}
#[macro_export]
macro_rules! open_fd {
($path:expr, $flags:expr) => {
$crate::__open_fd_impl($path, $flags, 0)
};
($path:expr, $flags:expr, $mode:expr) => {
$crate::__open_fd_impl($path, $flags, $mode)
};
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
let path = FsPathBuf::default().join("/proc/self/fd").join_fmt(fd);
path.read_link(buf)
}
pub trait ReadExt { pub trait ReadExt {
fn skip(&mut self, len: usize) -> io::Result<()>; fn skip(&mut self, len: usize) -> io::Result<()>;
fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()>; fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()>;
@ -137,6 +115,32 @@ impl<T: Write> WriteExt for T {
} }
} }
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> OsResult<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).as_os_result(
"open",
Some(path),
None,
)?;
Ok(OwnedFd::from_raw_fd(fd))
}
}
#[macro_export]
macro_rules! open_fd {
($path:expr, $flags:expr) => {
$crate::__open_fd_impl($path, $flags, 0)
};
($path:expr, $flags:expr, $mode:expr) => {
$crate::__open_fd_impl($path, $flags, $mode)
};
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
let path = FsPathBuf::default().join("/proc/self/fd").join_fmt(fd);
path.read_link(buf).map_err(|e| e.set_args(None, None))
}
pub struct FileAttr { pub struct FileAttr {
pub st: libc::stat, pub st: libc::stat,
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
@ -194,11 +198,11 @@ impl FsPath {
unsafe { mem::transmute(self) } unsafe { mem::transmute(self) }
} }
pub fn open(&self, flags: i32) -> io::Result<File> { pub fn open(&self, flags: i32) -> OsResult<File> {
Ok(File::from(open_fd!(self, flags)?)) Ok(File::from(open_fd!(self, flags)?))
} }
pub fn create(&self, flags: i32, mode: mode_t) -> io::Result<File> { pub fn create(&self, flags: i32, mode: mode_t) -> OsResult<File> {
Ok(File::from(open_fd!(self, O_CREAT | flags, mode)?)) Ok(File::from(open_fd!(self, O_CREAT | flags, mode)?))
} }
@ -209,75 +213,80 @@ impl FsPath {
} }
} }
pub fn rename_to<T: AsRef<Utf8CStr>>(&self, name: T) -> io::Result<()> { pub fn rename_to<'a>(&'a self, name: &'a FsPath) -> OsResult<'a, ()> {
unsafe { libc::rename(self.as_ptr(), name.as_ref().as_ptr()).as_os_err() } unsafe {
libc::rename(self.as_ptr(), name.as_ptr()).check_os_err(
"rename",
Some(self),
Some(name),
)
}
} }
pub fn remove(&self) -> io::Result<()> { pub fn remove(&self) -> OsResult<()> {
unsafe { libc::remove(self.as_ptr()).as_os_err() } unsafe { libc::remove(self.as_ptr()).check_os_err("remove", Some(self), None) }
} }
pub fn remove_all(&self) -> io::Result<()> { pub fn remove_all(&self) -> OsResultStatic<()> {
let attr = self.get_attr()?; let attr = self.get_attr()?;
if attr.is_dir() { if attr.is_dir() {
let mut dir = Directory::try_from(open_fd!(self, O_RDONLY | O_CLOEXEC)?)?; let mut dir = Directory::try_from(open_fd!(self, O_RDONLY | O_CLOEXEC)?)?;
dir.remove_all()?; dir.remove_all()?;
} }
self.remove() Ok(self.remove()?)
} }
#[allow(clippy::unnecessary_cast)] #[allow(clippy::unnecessary_cast)]
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
buf.clear(); buf.clear();
unsafe { unsafe {
let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1) let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1)
.check_os_err()? as isize; .as_os_result("readlink", Some(self), None)? as isize;
*(buf.as_mut_ptr().offset(r) as *mut u8) = b'\0'; *(buf.as_mut_ptr().offset(r) as *mut u8) = b'\0';
buf.set_len(r as usize); buf.set_len(r as usize);
} }
Ok(()) Ok(())
} }
pub fn mkdir(&self, mode: mode_t) -> io::Result<()> { pub fn mkdir(&self, mode: mode_t) -> OsResult<()> {
unsafe { unsafe {
if libc::mkdir(self.as_ptr(), mode) < 0 { if libc::mkdir(self.as_ptr(), mode) < 0 {
if *errno() == EEXIST { if *errno() == EEXIST {
libc::chmod(self.as_ptr(), mode).as_os_err()?; libc::chmod(self.as_ptr(), mode).check_os_err("chmod", Some(self), None)?;
} else { } else {
return Err(io::Error::last_os_error()); return Err(OsError::last_os_error("mkdir", Some(self), None));
} }
} }
} }
Ok(()) Ok(())
} }
pub fn mkdirs(&self, mode: mode_t) -> io::Result<()> { pub fn mkdirs(&self, mode: mode_t) -> OsResultStatic<()> {
if self.is_empty() { if self.is_empty() {
return Ok(()); return Ok(());
} }
let mut arr = cstr_buf::default();
arr.push_str(self); let mut path = FsPathBuf::default();
let mut off = 1; let mut components = self.split('/').filter(|s| !s.is_empty());
unsafe { loop {
let buf = arr.as_bytes_mut(); let Some(s) = components.next() else {
while let Some(p) = buf[off..].iter().position(|c| *c == b'/') { break;
buf[off + p] = b'\0'; };
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST { path = path.join(s);
return Err(io::Error::last_os_error());
unsafe {
if libc::mkdir(path.as_ptr(), mode) < 0 && *errno() != EEXIST {
return Err(OsError::last_os_error("mkdir", Some(&path), None))?;
} }
buf[off + p] = b'/';
off += p + 1;
}
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
} }
} }
*errno() = 0; *errno() = 0;
Ok(()) Ok(())
} }
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp // Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
let fd = open_fd!(self, O_PATH | O_CLOEXEC)?; let fd = open_fd!(self, O_PATH | O_CLOEXEC)?;
let mut st1: libc::stat; let mut st1: libc::stat;
let mut st2: libc::stat; let mut st2: libc::stat;
@ -292,19 +301,19 @@ impl FsPath {
fd_path(fd.as_raw_fd(), buf)?; fd_path(fd.as_raw_fd(), buf)?;
unsafe { unsafe {
st2 = mem::zeroed(); st2 = mem::zeroed();
libc::stat(buf.as_ptr(), &mut st2).as_os_err()?; libc::stat(buf.as_ptr(), &mut st2).check_os_err("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) { if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
*errno() = ENOENT; *errno() = ENOENT;
return Err(io::Error::last_os_error()); return Err(OsError::last_os_error("realpath", Some(self), None));
} }
} }
Ok(()) Ok(())
} }
pub fn get_attr(&self) -> io::Result<FileAttr> { pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut attr = FileAttr::new(); let mut attr = FileAttr::new();
unsafe { unsafe {
libc::lstat(self.as_ptr(), &mut attr.st).as_os_err()?; libc::lstat(self.as_ptr(), &mut attr.st).check_os_err("lstat", Some(self), None)?;
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?; self.get_secontext(&mut attr.con)?;
@ -312,12 +321,20 @@ impl FsPath {
Ok(attr) Ok(attr)
} }
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe { unsafe {
if !attr.is_symlink() { if !attr.is_symlink() {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?; libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
"chmod",
Some(self),
None,
)?;
} }
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?; libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"lchown",
Some(self),
None,
)?;
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
if !attr.con.is_empty() { if !attr.con.is_empty() {
@ -327,7 +344,7 @@ impl FsPath {
Ok(()) Ok(())
} }
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
unsafe { unsafe {
let sz = libc::lgetxattr( let sz = libc::lgetxattr(
self.as_ptr(), self.as_ptr(),
@ -338,7 +355,7 @@ impl FsPath {
if sz < 1 { if sz < 1 {
con.clear(); con.clear();
if *errno() != libc::ENODATA { if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error()); return Err(OsError::last_os_error("lgetxattr", Some(self), None));
} }
} else { } else {
con.set_len((sz - 1) as usize); con.set_len((sz - 1) as usize);
@ -347,7 +364,7 @@ impl FsPath {
Ok(()) Ok(())
} }
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> { pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe { unsafe {
libc::lsetxattr( libc::lsetxattr(
self.as_ptr(), self.as_ptr(),
@ -356,11 +373,11 @@ impl FsPath {
con.len() + 1, con.len() + 1,
0, 0,
) )
.as_os_err() .check_os_err("lsetxattr", Some(self), Some(con))
} }
} }
pub fn copy_to(&self, path: &FsPath) -> io::Result<()> { pub fn copy_to(&self, path: &FsPath) -> OsResultStatic<()> {
let attr = self.get_attr()?; let attr = self.get_attr()?;
if attr.is_dir() { if attr.is_dir() {
path.mkdir(0o777)?; path.mkdir(0o777)?;
@ -378,7 +395,11 @@ impl FsPath {
let mut buf = cstr_buf::default(); let mut buf = cstr_buf::default();
self.read_link(&mut buf)?; self.read_link(&mut buf)?;
unsafe { unsafe {
libc::symlink(buf.as_ptr(), path.as_ptr()).as_os_err()?; libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
"symlink",
Some(&buf),
Some(path),
)?;
} }
} }
} }
@ -386,7 +407,7 @@ impl FsPath {
Ok(()) Ok(())
} }
pub fn move_to(&self, path: &FsPath) -> io::Result<()> { pub fn move_to(&self, path: &FsPath) -> OsResultStatic<()> {
if path.exists() { if path.exists() {
let attr = path.get_attr()?; let attr = path.get_attr()?;
if attr.is_dir() { if attr.is_dir() {
@ -397,7 +418,8 @@ impl FsPath {
path.remove()?; path.remove()?;
} }
} }
self.rename_to(path) self.rename_to(path)?;
Ok(())
} }
pub fn parent(&self, buf: &mut dyn Utf8CStrBuf) -> bool { pub fn parent(&self, buf: &mut dyn Utf8CStrBuf) -> bool {
@ -414,26 +436,39 @@ impl FsPath {
} }
// ln self path // ln self path
pub fn link_to(&self, path: &FsPath) -> io::Result<()> { pub fn link_to(&self, path: &FsPath) -> OsResultStatic<()> {
let attr = self.get_attr()?; let attr = self.get_attr()?;
if attr.is_dir() { if attr.is_dir() {
path.mkdir(0o777)?; path.mkdir(0o777)?;
path.set_attr(&attr)?; path.set_attr(&attr)?;
let mut src = Directory::open(self)?; let mut src = Directory::open(self)?;
let dest = Directory::open(path)?; let dest = Directory::open(path)?;
src.link_into(&dest) Ok(src.link_into(&dest)?)
} else { } else {
unsafe { libc::link(self.as_ptr(), path.as_ptr()).as_os_err() } unsafe {
libc::link(self.as_ptr(), path.as_ptr()).check_os_err(
"link",
Some(self),
Some(path),
)?;
}
Ok(())
} }
} }
// ln -s target self // ln -s target self
pub fn create_symlink_to(&self, target: &FsPath) -> io::Result<()> { pub fn create_symlink_to<'a>(&'a self, target: &'a FsPath) -> OsResult<'a, ()> {
unsafe { libc::symlink(target.as_ptr(), self.as_ptr()).as_os_err() } unsafe {
libc::symlink(target.as_ptr(), self.as_ptr()).check_os_err(
"symlink",
Some(target),
Some(self),
)
}
} }
pub fn mkfifo(&self, mode: mode_t) -> io::Result<()> { pub fn mkfifo(&self, mode: mode_t) -> OsResult<()> {
unsafe { libc::mkfifo(self.as_ptr(), mode).as_os_err() } unsafe { libc::mkfifo(self.as_ptr(), mode).check_os_err("mkfifo", Some(self), None) }
} }
} }
@ -442,10 +477,10 @@ impl FsPathFollow {
unsafe { libc::access(self.as_ptr(), F_OK) == 0 } unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
} }
pub fn get_attr(&self) -> io::Result<FileAttr> { pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut attr = FileAttr::new(); let mut attr = FileAttr::new();
unsafe { unsafe {
libc::stat(self.as_ptr(), &mut attr.st).as_os_err()?; libc::stat(self.as_ptr(), &mut attr.st).check_os_err("stat", Some(self), None)?;
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?; self.get_secontext(&mut attr.con)?;
@ -453,10 +488,18 @@ impl FsPathFollow {
Ok(attr) Ok(attr)
} }
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe { unsafe {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?; libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?; "chmod",
Some(self),
None,
)?;
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"chown",
Some(self),
None,
)?;
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
if !attr.con.is_empty() { if !attr.con.is_empty() {
@ -466,7 +509,7 @@ impl FsPathFollow {
Ok(()) Ok(())
} }
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> { pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
unsafe { unsafe {
let sz = libc::getxattr( let sz = libc::getxattr(
self.as_ptr(), self.as_ptr(),
@ -477,7 +520,7 @@ impl FsPathFollow {
if sz < 1 { if sz < 1 {
con.clear(); con.clear();
if *errno() != libc::ENODATA { if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error()); return Err(OsError::last_os_error("getxattr", Some(self), None));
} }
} else { } else {
con.set_len((sz - 1) as usize); con.set_len((sz - 1) as usize);
@ -486,7 +529,7 @@ impl FsPathFollow {
Ok(()) Ok(())
} }
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> { pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe { unsafe {
libc::setxattr( libc::setxattr(
self.as_ptr(), self.as_ptr(),
@ -495,86 +538,97 @@ impl FsPathFollow {
con.len() + 1, con.len() + 1,
0, 0,
) )
.as_os_err() .check_os_err("setxattr", Some(self), Some(con))
} }
} }
} }
pub fn fd_get_attr(fd: RawFd) -> io::Result<FileAttr> { pub fn fd_get_attr(fd: RawFd) -> OsResult<'static, FileAttr> {
let mut attr = FileAttr::new(); let mut attr = FileAttr::new();
unsafe { unsafe {
libc::fstat(fd, &mut attr.st).as_os_err()?; libc::fstat(fd, &mut attr.st).check_os_err("fstat", None, None)?;
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
{ fd_get_secontext(fd, &mut attr.con)?;
let sz = libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
attr.con.as_mut_ptr().cast(),
attr.con.capacity(),
);
if sz < 1 {
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
}
} else {
attr.con.set_len((sz - 1) as usize);
}
}
} }
Ok(attr) Ok(attr)
} }
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> io::Result<()> { pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<()> {
unsafe { unsafe {
libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).as_os_err()?; 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).as_os_err()?; libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).check_os_err("fchown", None, None)?;
#[cfg(feature = "selinux")] #[cfg(feature = "selinux")]
if !attr.con.is_empty() { if !attr.con.is_empty() {
libc::fsetxattr( fd_set_secontext(fd, &attr.con)?;
fd,
XATTR_NAME_SELINUX.as_ptr(),
attr.con.as_ptr().cast(),
attr.con.len() + 1,
0,
)
.as_os_err()?;
} }
} }
Ok(()) Ok(())
} }
pub fn clone_attr(a: &FsPath, b: &FsPath) -> io::Result<()> { pub fn fd_get_secontext(fd: RawFd, con: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
let attr = a.get_attr()?; unsafe {
b.set_attr(&attr) let sz = libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
if *errno() != libc::ENODATA {
return Err(OsError::last_os_error("fgetxattr", None, None));
}
} else {
con.set_len((sz - 1) as usize);
}
}
Ok(())
} }
pub fn fclone_attr(a: RawFd, b: RawFd) -> io::Result<()> { 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)
}
}
pub fn clone_attr<'a>(a: &'a FsPath, b: &'a FsPath) -> OsResult<'a, ()> {
let attr = a.get_attr()?;
b.set_attr(&attr).map_err(|e| e.set_args(Some(b), None))
}
pub fn fclone_attr(a: RawFd, b: RawFd) -> OsResult<'static, ()> {
let attr = fd_get_attr(a)?; let attr = fd_get_attr(a)?;
fd_set_attr(b, &attr) fd_set_attr(b, &attr).map_err(|e| e.set_args(None, None))
} }
pub struct MappedFile(&'static mut [u8]); pub struct MappedFile(&'static mut [u8]);
impl MappedFile { impl MappedFile {
pub fn open(path: &Utf8CStr) -> io::Result<MappedFile> { pub fn open(path: &Utf8CStr) -> OsResult<MappedFile> {
Ok(MappedFile(map_file(path, false)?)) Ok(MappedFile(map_file(path, false)?))
} }
pub fn open_rw(path: &Utf8CStr) -> io::Result<MappedFile> { pub fn open_rw(path: &Utf8CStr) -> OsResult<MappedFile> {
Ok(MappedFile(map_file(path, true)?)) Ok(MappedFile(map_file(path, true)?))
} }
pub fn openat<T: AsFd>(dir: &T, path: &Utf8CStr) -> io::Result<MappedFile> { pub fn openat<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {
Ok(MappedFile(map_file_at(dir.as_fd(), path, false)?)) Ok(MappedFile(map_file_at(dir.as_fd(), path, false)?))
} }
pub fn openat_rw<T: AsFd>(dir: &T, path: &Utf8CStr) -> io::Result<MappedFile> { 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)?)) Ok(MappedFile(map_file_at(dir.as_fd(), path, true)?))
} }
pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<MappedFile> { pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<MappedFile> {
Ok(MappedFile(map_fd(fd, sz, rw)?)) Ok(MappedFile(map_fd(fd, sz, rw)?))
} }
} }
@ -605,15 +659,15 @@ unsafe extern "C" {
} }
// We mark the returned slice static because it is valid until explicitly unmapped // We mark the returned slice static because it is valid until explicitly unmapped
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8]> { pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<&'static mut [u8]> {
unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) } unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) }
} }
pub(crate) fn map_file_at( pub(crate) fn map_file_at<'a>(
dirfd: BorrowedFd, dirfd: BorrowedFd,
path: &Utf8CStr, path: &'a Utf8CStr,
rw: bool, rw: bool,
) -> io::Result<&'static mut [u8]> { ) -> OsResult<'a, &'static mut [u8]> {
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
const BLKGETSIZE64: u32 = 0x80081272; const BLKGETSIZE64: u32 = 0x80081272;
@ -623,23 +677,29 @@ pub(crate) fn map_file_at(
let flag = if rw { O_RDWR } else { O_RDONLY }; let flag = if rw { O_RDWR } else { O_RDONLY };
let fd = unsafe { let fd = unsafe {
OwnedFd::from_raw_fd( OwnedFd::from_raw_fd(
libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).check_os_err()?, libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).as_os_result(
"openat",
Some(path),
None,
)?,
) )
}; };
let attr = fd_get_attr(fd.as_raw_fd())?; let attr = fd_get_attr(fd.as_raw_fd())?;
let sz = if attr.is_block_device() { let sz = if attr.is_block_device() {
let mut sz = 0_u64; let mut sz = 0_u64;
unsafe { ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?; unsafe {
ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz).check_os_err("ioctl", Some(path), None)?;
}
sz sz
} else { } else {
attr.st.st_size as u64 attr.st.st_size as u64
}; };
map_fd(fd.as_fd(), sz as usize, rw) 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) -> io::Result<&'static mut [u8]> { pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<'static, &'static mut [u8]> {
let flag = if rw { let flag = if rw {
libc::MAP_SHARED libc::MAP_SHARED
} else { } else {
@ -655,7 +715,7 @@ pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static
0, 0,
); );
if ptr == libc::MAP_FAILED { if ptr == libc::MAP_FAILED {
return Err(io::Error::last_os_error()); return Err(OsError::last_os_error("mmap", None, None));
} }
Ok(slice::from_raw_parts_mut(ptr.cast(), sz)) Ok(slice::from_raw_parts_mut(ptr.cast(), sz))
} }

View File

@ -7,7 +7,7 @@ use std::mem::ManuallyDrop;
use std::process::exit; use std::process::exit;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicPtr, Ordering}; use std::sync::atomic::{AtomicPtr, Ordering};
use std::{fmt, io, slice, str}; use std::{fmt, slice, str};
pub fn errno() -> &'static mut i32 { pub fn errno() -> &'static mut i32 {
unsafe { &mut *libc::__errno() } unsafe { &mut *libc::__errno() }
@ -37,52 +37,6 @@ pub unsafe fn slice_from_ptr_mut<'a, T>(buf: *mut T, len: usize) -> &'a mut [T]
} }
} }
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
{
fn is_error(&self) -> bool;
fn check_os_err(self) -> io::Result<Self> {
if self.is_error() {
Err(io::Error::last_os_error())
} else {
Ok(self)
}
}
fn as_os_err(self) -> io::Result<()> {
self.check_os_err()?;
Ok(())
}
}
macro_rules! impl_libc_return {
($($t:ty)*) => ($(
impl LibcReturn for $t {
#[inline]
fn is_error(&self) -> bool {
*self < 0
}
}
)*)
}
impl_libc_return! { i8 i16 i32 i64 isize }
impl<T> LibcReturn for *const T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
impl<T> LibcReturn for *mut T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
pub trait BytesExt { pub trait BytesExt {
fn find(&self, needle: &[u8]) -> Option<usize>; fn find(&self, needle: &[u8]) -> Option<usize>;
fn contains(&self, needle: &[u8]) -> bool { fn contains(&self, needle: &[u8]) -> bool {

View File

@ -1,9 +1,9 @@
use crate::{FsPath, LibcReturn, Utf8CStr}; use crate::{FsPath, LibcReturn, OsResult, Utf8CStr};
use libc::c_ulong; use libc::c_ulong;
use std::ptr; use std::ptr;
impl FsPath { impl FsPath {
pub fn bind_mount_to(&self, path: &FsPath) -> std::io::Result<()> { pub fn bind_mount_to<'a>(&'a self, path: &'a FsPath) -> OsResult<'a, ()> {
unsafe { unsafe {
libc::mount( libc::mount(
self.as_ptr(), self.as_ptr(),
@ -12,11 +12,11 @@ impl FsPath {
libc::MS_BIND, libc::MS_BIND,
ptr::null(), ptr::null(),
) )
.as_os_err() .check_os_err("bind_mount", Some(self), Some(path))
} }
} }
pub fn remount_with_flags(&self, flags: c_ulong) -> std::io::Result<()> { pub fn remount_with_flags(&self, flags: c_ulong) -> OsResult<()> {
unsafe { unsafe {
libc::mount( libc::mount(
ptr::null(), ptr::null(),
@ -25,11 +25,11 @@ impl FsPath {
libc::MS_BIND | libc::MS_REMOUNT | flags, libc::MS_BIND | libc::MS_REMOUNT | flags,
ptr::null(), ptr::null(),
) )
.as_os_err() .check_os_err("remount", Some(self), None)
} }
} }
pub fn remount_with_data(&self, data: &Utf8CStr) -> std::io::Result<()> { pub fn remount_with_data(&self, data: &Utf8CStr) -> OsResult<()> {
unsafe { unsafe {
libc::mount( libc::mount(
ptr::null(), ptr::null(),
@ -38,11 +38,11 @@ impl FsPath {
libc::MS_REMOUNT, libc::MS_REMOUNT,
data.as_ptr().cast(), data.as_ptr().cast(),
) )
.as_os_err() .check_os_err("remount", Some(self), None)
} }
} }
pub fn move_mount_to(&self, path: &FsPath) -> std::io::Result<()> { pub fn move_mount_to<'a>(&'a self, path: &'a FsPath) -> OsResult<'a, ()> {
unsafe { unsafe {
libc::mount( libc::mount(
self.as_ptr(), self.as_ptr(),
@ -51,15 +51,17 @@ impl FsPath {
libc::MS_MOVE, libc::MS_MOVE,
ptr::null(), ptr::null(),
) )
.as_os_err() .check_os_err("move_mount", Some(self), Some(path))
} }
} }
pub fn unmount(&self) -> std::io::Result<()> { pub fn unmount(&self) -> OsResult<()> {
unsafe { libc::umount2(self.as_ptr(), libc::MNT_DETACH).as_os_err() } unsafe {
libc::umount2(self.as_ptr(), libc::MNT_DETACH).check_os_err("unmount", Some(self), None)
}
} }
pub fn set_mount_private(&self, recursive: bool) -> std::io::Result<()> { pub fn set_mount_private(&self, recursive: bool) -> OsResult<()> {
let flag = if recursive { libc::MS_REC } else { 0 }; let flag = if recursive { libc::MS_REC } else { 0 };
unsafe { unsafe {
libc::mount( libc::mount(
@ -69,7 +71,7 @@ impl FsPath {
libc::MS_PRIVATE | flag, libc::MS_PRIVATE | flag,
ptr::null(), ptr::null(),
) )
.as_os_err() .check_os_err("set_mount_private", Some(self), None)
} }
} }
} }

View File

@ -1,9 +1,9 @@
use std::fmt; use crate::logging::Formatter;
use crate::{LogLevel, errno, log_with_args, log_with_formatter};
use std::fmt::Display; use std::fmt::Display;
use std::panic::Location; use std::panic::Location;
use std::{fmt, io};
use crate::logging::Formatter; use thiserror::Error;
use crate::{LogLevel, log_with_args, log_with_formatter};
// Error handling throughout the Rust codebase in Magisk: // Error handling throughout the Rust codebase in Magisk:
// //
@ -205,3 +205,191 @@ impl<T: Display> From<T> for LoggedError {
LoggedError::default() LoggedError::default()
} }
} }
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
{
fn is_error(&self) -> bool;
fn as_os_result<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, Self> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(self)
}
}
fn check_os_err<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, ()> {
self.as_os_result(name, arg1, arg2)?;
Ok(())
}
fn check_io_err(self) -> io::Result<()> {
if self.is_error() {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
macro_rules! impl_libc_return {
($($t:ty)*) => ($(
impl LibcReturn for $t {
#[inline]
fn is_error(&self) -> bool {
*self < 0
}
}
)*)
}
impl_libc_return! { i8 i16 i32 i64 isize }
impl<T> LibcReturn for *const T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
impl<T> LibcReturn for *mut T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
#[derive(Debug)]
enum OwnableStr<'a> {
None,
Borrowed(&'a str),
Owned(Box<str>),
}
impl OwnableStr<'_> {
fn into_owned(self) -> OwnableStr<'static> {
match self {
OwnableStr::None => OwnableStr::None,
OwnableStr::Borrowed(s) => OwnableStr::Owned(Box::from(s)),
OwnableStr::Owned(s) => OwnableStr::Owned(s),
}
}
fn ok(&self) -> Option<&str> {
match self {
OwnableStr::None => None,
OwnableStr::Borrowed(s) => Some(*s),
OwnableStr::Owned(s) => Some(s),
}
}
}
impl<'a> From<Option<&'a str>> for OwnableStr<'a> {
fn from(value: Option<&'a str>) -> Self {
value.map(OwnableStr::Borrowed).unwrap_or(OwnableStr::None)
}
}
#[derive(Debug)]
pub struct OsError<'a> {
code: i32,
name: &'static str,
arg1: OwnableStr<'a>,
arg2: OwnableStr<'a>,
}
impl OsError<'_> {
pub fn with_os_error<'a>(
code: i32,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
OsError {
code,
name,
arg1: OwnableStr::from(arg1),
arg2: OwnableStr::from(arg2),
}
}
pub fn last_os_error<'a>(
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
Self::with_os_error(*errno(), name, arg1, arg2)
}
pub fn set_args<'a>(self, arg1: Option<&'a str>, arg2: Option<&'a str>) -> OsError<'a> {
Self::with_os_error(self.code, self.name, arg1, arg2)
}
pub fn into_owned(self) -> OsError<'static> {
OsError {
code: *errno(),
name: self.name,
arg1: self.arg1.into_owned(),
arg2: self.arg2.into_owned(),
}
}
fn as_io_error(&self) -> io::Error {
io::Error::from_raw_os_error(self.code)
}
}
impl Display for OsError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = self.as_io_error();
if self.name.is_empty() {
write!(f, "{:#}", error)
} else {
match (self.arg1.ok(), self.arg2.ok()) {
(Some(arg1), Some(arg2)) => {
write!(f, "{} '{}' '{}': {:#}", self.name, arg1, arg2, error)
}
(Some(arg1), None) => {
write!(f, "{} '{}': {:#}", self.name, arg1, error)
}
_ => {
write!(f, "{}: {:#}", self.name, error)
}
}
}
}
}
impl std::error::Error for OsError<'_> {}
pub type OsResult<'a, T> = Result<T, OsError<'a>>;
#[derive(Debug, Error)]
pub enum OsErrorStatic {
#[error(transparent)]
Os(OsError<'static>),
#[error(transparent)]
Io(#[from] io::Error),
}
// Convert non-static OsError to static
impl<'a> From<OsError<'a>> for OsErrorStatic {
fn from(value: OsError<'a>) -> Self {
OsErrorStatic::Os(value.into_owned())
}
}
pub type OsResultStatic<T> = Result<T, OsErrorStatic>;

View File

@ -7,8 +7,8 @@ use num_traits::AsPrimitive;
use base::libc::{c_uint, dev_t}; use base::libc::{c_uint, dev_t};
use base::{ use base::{
FsPath, FsPathBuf, LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr, cstr, cstr_buf, FsPath, FsPathBuf, LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr, cstr, debug, info,
debug, info, libc, parse_mount_info, path, warn, libc, parse_mount_info, path, warn,
}; };
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR}; use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
@ -42,16 +42,12 @@ pub fn setup_mounts() {
let target = Utf8CStr::from_string(&mut target); let target = Utf8CStr::from_string(&mut target);
let mut preinit_dir = resolve_preinit_dir(target); let mut preinit_dir = resolve_preinit_dir(target);
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir); let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
let preinit_dir = FsPath::from(preinit_dir);
let r: LoggedResult<()> = try { let r: LoggedResult<()> = try {
FsPath::from(preinit_dir).mkdir(0o700)?; preinit_dir.mkdir(0o700)?;
let mut buf = cstr_buf::default(); mnt_path.mkdirs(0o755)?;
if mnt_path.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
mnt_path.remove().ok(); mnt_path.remove().ok();
unsafe { mnt_path.create_symlink_to(preinit_dir)?;
libc::symlink(preinit_dir.as_ptr(), mnt_path.as_ptr()).as_os_err()?
}
}; };
if r.is_ok() { if r.is_ok() {
linked = true; linked = true;
@ -187,17 +183,10 @@ pub fn find_preinit_device() -> String {
let preinit_dir = FsPath::from(Utf8CStr::from_string(&mut preinit_dir)); let preinit_dir = FsPath::from(Utf8CStr::from_string(&mut preinit_dir));
let _: LoggedResult<()> = try { let _: LoggedResult<()> = try {
preinit_dir.mkdirs(0o700)?; preinit_dir.mkdirs(0o700)?;
let mut buf = cstr_buf::default(); mirror_dir.mkdirs(0o755)?;
if mirror_dir.parent(&mut buf) { mirror_dir.unmount().ok();
FsPath::from(&buf).mkdirs(0o755)?; mirror_dir.remove().ok();
} mirror_dir.create_symlink_to(preinit_dir)?;
unsafe {
libc::umount2(mirror_dir.as_ptr(), libc::MNT_DETACH)
.as_os_err()
.ok(); // ignore error
mirror_dir.remove().ok();
libc::symlink(preinit_dir.as_ptr(), mirror_dir.as_ptr()).as_os_err()?;
}
}; };
if std::env::var_os("MAKEDEV").is_some() { if std::env::var_os("MAKEDEV").is_some() {
mirror_dir.clear(); mirror_dir.clear();
@ -208,7 +197,7 @@ pub fn find_preinit_device() -> String {
libc::S_IFBLK | 0o600, libc::S_IFBLK | 0o600,
info.device as dev_t, info.device as dev_t,
) )
.as_os_err() .check_io_err()
.log() .log()
.ok(); .ok();
} }

View File

@ -152,7 +152,7 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
res.log().unwrap_or(vec![]) res.log().unwrap_or(vec![])
} }
fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> LoggedResult<()> {
Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| { Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| {
if !e.is_dir() { if !e.is_dir() {
return Ok(Skip); return Ok(Skip);

View File

@ -84,13 +84,14 @@ fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()>
.join("prop.XXXXXX"); .join("prop.XXXXXX");
{ {
let mut f = unsafe { let mut f = unsafe {
let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?; mkstemp(tmp.as_mut_ptr())
File::from_raw_fd(fd) .as_os_result("mkstemp", None, None)
.map(|fd| File::from_raw_fd(fd))?
}; };
f.write_all(value.as_bytes())?; f.write_all(value.as_bytes())?;
} }
debug!("resetprop: write prop to [{}]", tmp); debug!("resetprop: write prop to [{}]", tmp);
tmp.rename_to(path)? tmp.rename_to(&path)?
} else { } else {
path.remove().silent()?; path.remove().silent()?;
debug!("resetprop: unlink [{}]", path); debug!("resetprop: unlink [{}]", path);
@ -113,14 +114,15 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
let mut tmp = FsPathBuf::default().join(concatcp!(PERSIST_PROP, ".XXXXXX")); let mut tmp = FsPathBuf::default().join(concatcp!(PERSIST_PROP, ".XXXXXX"));
{ {
let f = unsafe { let f = unsafe {
let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?; mkstemp(tmp.as_mut_ptr())
File::from_raw_fd(fd) .as_os_result("mkstemp", None, None)
.map(|fd| File::from_raw_fd(fd))?
}; };
debug!("resetprop: encode with protobuf [{}]", tmp); debug!("resetprop: encode with protobuf [{}]", tmp);
props.write_message(&mut Writer::new(BufWriter::new(f)))?; props.write_message(&mut Writer::new(BufWriter::new(f)))?;
} }
clone_attr(path!(PERSIST_PROP), &tmp)?; clone_attr(path!(PERSIST_PROP), &tmp)?;
tmp.rename_to(cstr!(PERSIST_PROP))?; tmp.rename_to(path!(PERSIST_PROP))?;
Ok(()) Ok(())
} }

View File

@ -189,7 +189,7 @@ impl MagiskD {
.join("zygisk"); .join("zygisk");
// Create the unloaded marker file // Create the unloaded marker file
if let Ok(dir) = Directory::open(&path) { if let Ok(dir) = Directory::open(&path) {
dir.open_fd(cstr!("unloaded"), O_CREAT | O_RDONLY, 0o644) dir.openat_as_file(cstr!("unloaded").as_cstr(), O_CREAT | O_RDONLY, 0o644)
.log() .log()
.ok(); .ok();
} }

View File

@ -130,7 +130,7 @@ impl MagiskInit {
null(), null(),
) )
} }
.as_os_err()?; .check_io_err()?;
self.mount_list.push("/proc".to_string()); self.mount_list.push("/proc".to_string());
} }
if !path!("/sys/block").exists() { if !path!("/sys/block").exists() {
@ -144,7 +144,7 @@ impl MagiskInit {
null(), null(),
) )
} }
.as_os_err()?; .check_io_err()?;
self.mount_list.push("/sys".to_string()); self.mount_list.push("/sys".to_string());
} }

View File

@ -45,7 +45,7 @@ pub(crate) fn switch_root(path: &Utf8CStr) {
mounts.insert(info.target); mounts.insert(info.target);
} }
unsafe { unsafe {
chdir(path.as_ptr()).as_os_err()?; chdir(path.as_ptr()).check_io_err()?;
FsPath::from(path).move_mount_to(path!("/"))?; FsPath::from(path).move_mount_to(path!("/"))?;
chroot(raw_cstr!(".")); chroot(raw_cstr!("."));
} }
@ -89,7 +89,7 @@ impl MagiskInit {
raw_cstr!("mode=755").cast(), raw_cstr!("mode=755").cast(),
) )
} }
.as_os_err() .check_io_err()
.log_ok(); .log_ok();
path!("/init").copy_to(path!("/data/magiskinit")).log_ok(); path!("/init").copy_to(path!("/data/magiskinit")).log_ok();
@ -108,7 +108,7 @@ impl MagiskInit {
} }
unsafe { unsafe {
execve(raw_cstr!("/init"), self.argv.cast(), environ.cast()) execve(raw_cstr!("/init"), self.argv.cast(), environ.cast())
.as_os_err() .check_io_err()
.log_ok(); .log_ok();
exit(1); exit(1);
} }

View File

@ -151,7 +151,7 @@ impl MagiskInit {
0, 0,
ptr::null(), ptr::null(),
) )
.as_os_err()?; .check_io_err()?;
} }
} }
@ -174,7 +174,7 @@ impl MagiskInit {
} }
// Create a new process waiting for init operations // Create a new process waiting for init operations
let pid = unsafe { libc::fork().check_os_err()? }; let pid = unsafe { libc::fork() };
if pid != 0 { if pid != 0 {
return Ok(()); return Ok(());
} }