diff --git a/native/src/Cargo.lock b/native/src/Cargo.lock index f53df0216..274b5cb97 100644 --- a/native/src/Cargo.lock +++ b/native/src/Cargo.lock @@ -27,6 +27,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" name = "base" version = "0.0.0" dependencies = [ + "anyhow", "cfg-if", "cxx", "cxx-gen", diff --git a/native/src/base/Cargo.toml b/native/src/base/Cargo.toml index 371b53329..26a05c956 100644 --- a/native/src/base/Cargo.toml +++ b/native/src/base/Cargo.toml @@ -14,3 +14,4 @@ cxx = { workspace = true } libc = { workspace = true } cfg-if = { workspace = true } thiserror = { workspace = true } +anyhow = { workspace = true } \ No newline at end of file diff --git a/native/src/base/cxx_extern.rs b/native/src/base/cxx_extern.rs new file mode 100644 index 000000000..bdcbc133f --- /dev/null +++ b/native/src/base/cxx_extern.rs @@ -0,0 +1,39 @@ +// Functions listed here are just to export to C++ + +use crate::{fd_path, mkdirs, realpath, rm_rf, slice_from_ptr_mut, Directory, ResultExt}; +use anyhow::Context; +use cxx::private::c_char; +use libc::mode_t; +use std::ffi::CStr; +use std::io; +use std::os::fd::{OwnedFd, RawFd}; + +pub fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { + fd_path(fd, buf) + .context("fd_path failed") + .log() + .map_or(-1_isize, |v| v as isize) +} + +#[no_mangle] +unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { + realpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz)).map_or(-1, |v| v as isize) +} + +#[export_name = "mkdirs"] +unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 { + mkdirs(CStr::from_ptr(path), mode).map_or(-1, |_| 0) +} + +#[export_name = "rm_rf"] +unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool { + rm_rf(CStr::from_ptr(path)).map_or(false, |_| true) +} + +#[no_mangle] +unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool { + fn inner(fd: OwnedFd) -> io::Result<()> { + Directory::try_from(fd)?.remove_all() + } + inner(fd).map_or(false, |_| true) +} diff --git a/native/src/base/files.cpp b/native/src/base/files.cpp index fa7b3a828..a7147b9ac 100644 --- a/native/src/base/files.cpp +++ b/native/src/base/files.cpp @@ -62,23 +62,6 @@ static walk_result pre_order_walk(int dirfd, const Func &fn) { return CONTINUE; } -static void remove_at(int dirfd, struct dirent *entry) { - unlinkat(dirfd, entry->d_name, entry->d_type == DT_DIR ? AT_REMOVEDIR : 0); -} - -void rm_rf(const char *path) { - struct stat st; - if (lstat(path, &st) < 0) - return; - if (S_ISDIR(st.st_mode)) - frm_rf(xopen(path, O_RDONLY | O_CLOEXEC)); - remove(path); -} - -void frm_rf(int dirfd) { - post_order_walk(dirfd, remove_at); -} - void mv_path(const char *src, const char *dest) { file_attr attr; getattr(src, &attr); diff --git a/native/src/base/files.hpp b/native/src/base/files.hpp index 41a79f636..cd799de2d 100644 --- a/native/src/base/files.hpp +++ b/native/src/base/files.hpp @@ -55,12 +55,13 @@ extern "C" { int mkdirs(const char *path, mode_t mode); ssize_t canonical_path(const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz); +bool rm_rf(const char *path); +bool frm_rf(int dirfd); } // extern "C" using rust::fd_path; int fd_pathat(int dirfd, const char *name, char *path, size_t size); -void rm_rf(const char *path); void mv_path(const char *src, const char *dest); void mv_dir(int src, int dest); void cp_afc(const char *src, const char *dest); @@ -89,7 +90,6 @@ void file_readline(const char *file, const std::function void parse_prop_file(FILE *fp, const std::function &fn); void parse_prop_file(const char *file, const std::function &fn); -void frm_rf(int dirfd); void clone_dir(int src, int dest); std::vector parse_mount_info(const char *pid); std::string find_apk_path(const char *pkg); diff --git a/native/src/base/files.rs b/native/src/base/files.rs index 8ae5bad22..13f478497 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -1,53 +1,25 @@ use mem::MaybeUninit; use std::cmp::min; use std::ffi::CStr; +use std::fs::File; use std::io::{BufRead, Read, Seek, SeekFrom, Write}; +use std::marker::PhantomData; +use std::ops::Deref; +use std::os::fd::{AsFd, BorrowedFd, IntoRawFd}; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; -use std::{io, mem}; +use std::{io, mem, slice}; -use libc::{c_char, c_uint, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH}; +use libc::{c_char, c_uint, dirent, mode_t, EEXIST, ENOENT, O_CLOEXEC, O_PATH, O_RDONLY}; -use crate::{bfmt_cstr, errno, error, xopen}; +use crate::{bfmt_cstr, copy_cstr, cstr, errno, error}; -pub mod unsafe_impl { - use std::ffi::CStr; - - use libc::c_char; - - use crate::slice_from_ptr_mut; - - pub unsafe fn readlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { - let r = libc::readlink(path, buf.cast(), bufsz - 1); - if r >= 0 { - *buf.offset(r) = b'\0'; - } - r - } - - #[no_mangle] - unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { - super::realpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz)) - } -} - -pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option { +pub fn __open_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> io::Result { unsafe { let fd = libc::open(path.as_ptr(), flags, mode as c_uint); if fd >= 0 { - Some(OwnedFd::from_raw_fd(fd)) + Ok(OwnedFd::from_raw_fd(fd)) } else { - None - } - } -} - -pub fn __xopen_fd_impl(path: &CStr, flags: i32, mode: mode_t) -> Option { - unsafe { - let fd = xopen(path.as_ptr(), flags, mode); - if fd >= 0 { - Some(OwnedFd::from_raw_fd(fd)) - } else { - None + Err(io::Error::last_os_error()) } } } @@ -62,78 +34,74 @@ macro_rules! open_fd { }; } -#[macro_export] -macro_rules! xopen_fd { - ($path:expr, $flags:expr) => { - $crate::__xopen_fd_impl($path, $flags, 0) - }; - ($path:expr, $flags:expr, $mode:expr) => { - $crate::__xopen_fd_impl($path, $flags, $mode) - }; +pub unsafe fn readlink_unsafe(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { + let r = libc::readlink(path, buf.cast(), bufsz - 1); + if r >= 0 { + *buf.offset(r) = b'\0'; + } + r } -pub fn readlink(path: &CStr, data: &mut [u8]) -> isize { - unsafe { unsafe_impl::readlink(path.as_ptr(), data.as_mut_ptr(), data.len()) } +pub fn readlink(path: &CStr, data: &mut [u8]) -> io::Result { + let r = unsafe { readlink_unsafe(path.as_ptr(), data.as_mut_ptr(), data.len()) }; + if r < 0 { + return Err(io::Error::last_os_error()); + } + Ok(r as usize) } -pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> isize { - let mut fd_buf: [u8; 40] = [0; 40]; +pub fn fd_path(fd: RawFd, buf: &mut [u8]) -> io::Result { + let mut fd_buf = [0_u8; 40]; let fd_path = bfmt_cstr!(&mut fd_buf, "/proc/self/fd/{}", fd); readlink(fd_path, buf) } // Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp -pub fn realpath(path: &CStr, buf: &mut [u8]) -> isize { - if let Some(fd) = open_fd!(path, O_PATH | O_CLOEXEC) { - let mut st1: libc::stat; - let mut st2: libc::stat; - let mut skip_check = false; - unsafe { - st1 = mem::zeroed(); - if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 { - // This shall only fail on Linux < 3.6 - skip_check = true; - } +pub fn realpath(path: &CStr, buf: &mut [u8]) -> io::Result { + let fd = open_fd!(path, O_PATH | O_CLOEXEC)?; + let mut st1: libc::stat; + let mut st2: libc::stat; + let mut skip_check = false; + unsafe { + st1 = mem::zeroed(); + if libc::fstat(fd.as_raw_fd(), &mut st1) < 0 { + // This shall only fail on Linux < 3.6 + skip_check = true; } - let len = fd_path(fd.as_raw_fd(), buf); - unsafe { - st2 = mem::zeroed(); - if libc::stat(buf.as_ptr().cast(), &mut st2) < 0 - || (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino)) - { - *errno() = ENOENT; - return -1; - } - } - len - } else { - *errno() = ENOENT; - -1 } + let len = fd_path(fd.as_raw_fd(), buf)?; + unsafe { + st2 = mem::zeroed(); + if libc::stat(buf.as_ptr().cast(), &mut st2) < 0 + || (!skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino)) + { + *errno() = ENOENT; + return Err(io::Error::last_os_error()); + } + } + Ok(len) } -extern "C" { - fn strscpy(dst: *mut c_char, src: *const c_char, size: usize) -> usize; -} - -#[no_mangle] -pub unsafe extern "C" fn mkdirs(path: *const c_char, mode: mode_t) -> i32 { +pub fn mkdirs(path: &CStr, mode: mode_t) -> io::Result<()> { let mut buf = [0_u8; 4096]; - let ptr: *mut c_char = buf.as_mut_ptr().cast(); - let len = strscpy(ptr, path, buf.len()); - let mut curr = &mut buf[1..len]; - while let Some(p) = curr.iter().position(|c| *c == b'/') { - curr[p] = b'\0'; - if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST { - return -1; + let len = copy_cstr(&mut buf, path); + let buf = &mut buf[..len]; + let mut off = 1; + unsafe { + while let Some(p) = buf[off..].iter().position(|c| *c == b'/') { + buf[off + p] = b'\0'; + if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST { + return Err(io::Error::last_os_error()); + } + 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()); } - curr[p] = b'/'; - curr = &mut curr[(p + 1)..]; } - if libc::mkdir(ptr, mode) < 0 && *errno() != EEXIST { - return -1; - } - 0 + *errno() = 0; + Ok(()) } pub trait ReadExt { @@ -221,3 +189,276 @@ impl WriteExt for T { Ok(()) } } + +pub struct DirEntry<'a> { + dir: &'a Directory<'a>, + entry: &'a dirent, + name_len: usize, +} + +impl DirEntry<'_> { + pub fn d_name(&self) -> &CStr { + unsafe { + CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( + self.d_name.as_ptr().cast(), + self.name_len, + )) + } + } + + pub fn path(&self, buf: &mut [u8]) -> io::Result { + let mut len = self.dir.path(buf)?; + buf[len] = b'/'; + len += 1; + len += copy_cstr(&mut buf[len..], self.d_name()); + Ok(len) + } + + pub fn is_dir(&self) -> bool { + self.d_type == libc::DT_DIR + } + + pub fn is_file(&self) -> bool { + self.d_type == libc::DT_REG + } + + pub fn is_lnk(&self) -> bool { + self.d_type == libc::DT_LNK + } + + pub fn unlink(&self) -> io::Result<()> { + let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 }; + unsafe { + if libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag) < 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) + } + + pub fn open_as_dir(&self) -> io::Result { + if !self.is_dir() { + return Err(io::Error::from(io::ErrorKind::NotADirectory)); + } + unsafe { + let fd = libc::openat( + self.dir.as_raw_fd(), + self.d_name.as_ptr(), + O_RDONLY | O_CLOEXEC, + ); + if fd < 0 { + return Err(io::Error::last_os_error()); + } + Directory::try_from(OwnedFd::from_raw_fd(fd)) + } + } + + pub fn open_as_file(&self, flags: i32) -> io::Result { + if self.is_dir() { + return Err(io::Error::from(io::ErrorKind::IsADirectory)); + } + unsafe { + let fd = libc::openat( + self.dir.as_raw_fd(), + self.d_name.as_ptr(), + flags | O_CLOEXEC, + ); + if fd < 0 { + return Err(io::Error::last_os_error()); + } + Ok(File::from_raw_fd(fd)) + } + } +} + +impl Deref for DirEntry<'_> { + type Target = dirent; + + fn deref(&self) -> &dirent { + self.entry + } +} + +pub struct Directory<'a> { + dirp: *mut libc::DIR, + _phantom: PhantomData<&'a libc::DIR>, +} + +pub enum WalkResult { + Continue, + Abort, + Skip, +} + +impl<'a> Directory<'a> { + pub fn open(path: &CStr) -> io::Result { + let dirp = unsafe { libc::opendir(path.as_ptr()) }; + if dirp.is_null() { + return Err(io::Error::last_os_error()); + } + Ok(Directory { + dirp, + _phantom: PhantomData, + }) + } + + pub fn read(&mut self) -> io::Result>> { + *errno() = 0; + let e = unsafe { libc::readdir(self.dirp) }; + if e.is_null() { + return if *errno() != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(None) + }; + } + // Skip both "." and ".." + unsafe { + let entry = &*e; + let d_name = CStr::from_ptr(entry.d_name.as_ptr()); + return if d_name == cstr!(".") || d_name == cstr!("..") { + self.read() + } else { + let e = DirEntry { + dir: self, + entry, + name_len: d_name.to_bytes().len(), + }; + Ok(Some(e)) + }; + } + } + + pub fn rewind(&mut self) { + unsafe { libc::rewinddir(self.dirp) } + } + + pub fn path(&self, buf: &mut [u8]) -> io::Result { + fd_path(self.as_raw_fd(), buf) + } + + pub fn post_order_walk io::Result>( + &mut self, + mut f: F, + ) -> io::Result { + self.post_order_walk_impl(&mut f) + } + + pub fn pre_order_walk io::Result>( + &mut self, + mut f: F, + ) -> io::Result { + self.pre_order_walk_impl(&mut f) + } + + pub fn remove_all(&mut self) -> io::Result<()> { + self.post_order_walk(|e| { + e.unlink()?; + Ok(WalkResult::Continue) + })?; + Ok(()) + } +} + +impl Directory<'_> { + fn post_order_walk_impl io::Result>( + &mut self, + f: &mut F, + ) -> io::Result { + use WalkResult::*; + loop { + match self.read()? { + None => return Ok(Continue), + Some(ref e) => { + if e.is_dir() { + let mut dir = e.open_as_dir()?; + if let Abort = dir.post_order_walk_impl(f)? { + return Ok(Abort); + } + } + match f(e)? { + Abort => return Ok(Abort), + Skip => return Ok(Continue), + Continue => {} + } + } + } + } + } + + fn pre_order_walk_impl io::Result>( + &mut self, + f: &mut F, + ) -> io::Result { + use WalkResult::*; + loop { + match self.read()? { + None => return Ok(Continue), + Some(ref e) => match f(e)? { + Abort => return Ok(Abort), + Skip => return Ok(Continue), + Continue => { + if e.is_dir() { + let mut dir = e.open_as_dir()?; + if let Abort = dir.pre_order_walk_impl(f)? { + return Ok(Abort); + } + } + } + }, + } + } + } +} + +impl TryFrom for Directory<'_> { + type Error = io::Error; + + fn try_from(fd: OwnedFd) -> io::Result { + let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }; + if dirp.is_null() { + return Err(io::Error::last_os_error()); + } + Ok(Directory { + dirp, + _phantom: PhantomData, + }) + } +} + +impl AsRawFd for Directory<'_> { + fn as_raw_fd(&self) -> RawFd { + unsafe { libc::dirfd(self.dirp) } + } +} + +impl<'a> AsFd for Directory<'a> { + fn as_fd(&self) -> BorrowedFd<'a> { + unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } + } +} + +impl Drop for Directory<'_> { + fn drop(&mut self) { + unsafe { + libc::closedir(self.dirp); + } + } +} + +pub fn rm_rf(path: &CStr) -> io::Result<()> { + unsafe { + let mut stat: libc::stat = mem::zeroed(); + if libc::lstat(path.as_ptr(), &mut stat) < 0 { + return Err(io::Error::last_os_error()); + } + if (stat.st_mode & libc::S_IFMT as u32) == libc::S_IFDIR as u32 { + let mut dir = Directory::open(path)?; + dir.remove_all()?; + } + if libc::remove(path.as_ptr()) < 0 { + return Err(io::Error::last_os_error()); + } + } + Ok(()) +} diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index d07fdf126..0bb5e08b4 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -4,11 +4,13 @@ pub use libc; +use cxx_extern::*; pub use files::*; pub use logging::*; pub use misc::*; pub use xwrap::*; +mod cxx_extern; mod files; mod logging; mod misc; @@ -34,6 +36,7 @@ pub mod ffi { #[namespace = "rust"] extern "Rust" { fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32; + #[rust_name = "fd_path_for_cxx"] fn fd_path(fd: i32, buf: &mut [u8]) -> isize; } } diff --git a/native/src/base/misc.rs b/native/src/base/misc.rs index b597de748..4e6e67457 100644 --- a/native/src/base/misc.rs +++ b/native/src/base/misc.rs @@ -6,13 +6,21 @@ use std::{fmt, slice}; use thiserror::Error; -pub fn copy_str(dest: &mut [u8], src: &[u8]) -> usize { +pub fn copy_str>(dest: &mut [u8], src: T) -> usize { + let src = src.as_ref(); let len = min(src.len(), dest.len() - 1); dest[..len].copy_from_slice(&src[..len]); dest[len] = b'\0'; len } +pub fn copy_cstr(dest: &mut [u8], src: &CStr) -> usize { + let src = src.to_bytes_with_nul(); + let len = min(src.len(), dest.len()); + dest[..len].copy_from_slice(&src[..len]); + len +} + struct BufFmtWriter<'a> { buf: &'a mut [u8], used: usize, @@ -31,7 +39,7 @@ impl<'a> fmt::Write for BufFmtWriter<'a> { // Silent truncate return Ok(()); } - self.used += copy_str(&mut self.buf[self.used..], s.as_bytes()); + self.used += copy_str(&mut self.buf[self.used..], s); // Silent truncate Ok(()) } @@ -57,6 +65,7 @@ macro_rules! bfmt { macro_rules! bfmt_cstr { ($buf:expr, $($args:tt)*) => {{ let len = $crate::fmt_to_buf($buf, format_args!($($args)*)); + #[allow(unused_unsafe)] unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(&$buf[..(len + 1)]) } }}; } @@ -70,7 +79,10 @@ macro_rules! cstr { !$str.bytes().any(|b| b == b'\0'), "cstr argument contains embedded NUL bytes", ); - unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes()) } + #[allow(unused_unsafe)] + unsafe { + std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes()) + } }}; } diff --git a/native/src/base/xwrap.rs b/native/src/base/xwrap.rs index 8f0590eda..c24cc65bd 100644 --- a/native/src/base/xwrap.rs +++ b/native/src/base/xwrap.rs @@ -2,22 +2,26 @@ use std::ffi::CStr; use std::os::unix::io::RawFd; use std::ptr; +use anyhow::Context; use libc::{ c_char, c_uint, c_ulong, c_void, dev_t, mode_t, nfds_t, off_t, pollfd, sockaddr, socklen_t, ssize_t, SYS_dup3, }; -use crate::{cstr, errno, error, mkdirs, perror, ptr_to_str, raw_cstr, realpath}; +use crate::{cstr, errno, error, mkdirs, perror, ptr_to_str, raw_cstr, ResultExt}; mod unsafe_impl { use std::ffi::CStr; use std::os::unix::io::RawFd; + use anyhow::Context; use cfg_if::cfg_if; use libc::{c_char, nfds_t, off_t, pollfd}; - use crate::unsafe_impl::readlink; - use crate::{perror, ptr_to_str, slice_from_ptr, slice_from_ptr_mut}; + use crate::{ + perror, ptr_to_str, readlink, readlink_unsafe, slice_from_ptr, slice_from_ptr_mut, + ResultExt, + }; #[no_mangle] unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize { @@ -36,12 +40,15 @@ mod unsafe_impl { #[no_mangle] unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { - super::xrealpath(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz)) + readlink(CStr::from_ptr(path), slice_from_ptr_mut(buf, bufsz)) + .with_context(|| format!("realpath {} failed", ptr_to_str(path))) + .log() + .map_or(-1, |v| v as isize) } #[no_mangle] pub unsafe extern "C" fn xreadlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize { - let r = readlink(path, buf, bufsz); + let r = readlink_unsafe(path, buf, bufsz); if r < 0 { perror!("readlink"); } @@ -572,11 +579,10 @@ pub unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 { #[no_mangle] pub unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 { - let r = mkdirs(path, mode); - if r < 0 { - perror!("mkdirs {}", ptr_to_str(path)); - } - r + mkdirs(CStr::from_ptr(path), mode) + .with_context(|| format!("mkdirs {}", ptr_to_str(path))) + .log() + .map_or(-1, |_| 0) } #[no_mangle] @@ -629,14 +635,6 @@ pub fn xpoll(fds: &mut [pollfd], timeout: i32) -> i32 { unsafe { unsafe_impl::xpoll(fds.as_mut_ptr(), fds.len() as nfds_t, timeout) } } -pub fn xrealpath(path: &CStr, buf: &mut [u8]) -> isize { - let r = realpath(path, buf); - if r < 0 { - perror!("realpath {}", path.to_str().unwrap_or("")) - } - r -} - #[no_mangle] pub unsafe extern "C" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 { let r = libc::mknod(pathname, mode, dev);