From 6ff82c4e861af56bc8d2f52ad1219b0f6d106feb Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 7 Mar 2025 02:36:08 -0800 Subject: [PATCH] Introduce FsPathFollow Make sure all operations of FsPath do not follow symlinks, and provide a way to explicitly switch over to a set of operations that DO follow symlinks by FsPath::follow_link. --- native/src/base/cstr.rs | 20 ++ native/src/base/dir.rs | 420 ++++++++++++++++++++++++++++++ native/src/base/files.rs | 502 ++++++------------------------------ native/src/base/lib.rs | 2 + native/src/init/twostage.rs | 13 +- 5 files changed, 526 insertions(+), 431 deletions(-) create mode 100644 native/src/base/dir.rs diff --git a/native/src/base/cstr.rs b/native/src/base/cstr.rs index 620d36323..14b2de868 100644 --- a/native/src/base/cstr.rs +++ b/native/src/base/cstr.rs @@ -501,6 +501,25 @@ impl DerefMut for FsPath { } } +#[repr(transparent)] +pub struct FsPathFollow(Utf8CStr); + +impl Deref for FsPathFollow { + type Target = Utf8CStr; + + #[inline(always)] + fn deref(&self) -> &Utf8CStr { + &self.0 + } +} + +impl DerefMut for FsPathFollow { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Utf8CStr { + &mut self.0 + } +} + enum Utf8CStrBufOwned { Dynamic(Utf8CString), Fixed(Utf8CStrBufArr), @@ -667,6 +686,7 @@ macro_rules! impl_str { impl_str!( (Utf8CStr,) (FsPath,) + (FsPathFollow,) (FsPathBuf, const N: usize) (Utf8CStrBufRef<'_>,) (Utf8CStrBufArr, const N: usize) diff --git a/native/src/base/dir.rs b/native/src/base/dir.rs new file mode 100644 index 000000000..fd3610d87 --- /dev/null +++ b/native/src/base/dir.rs @@ -0,0 +1,420 @@ +use crate::cxx_extern::readlinkat_for_cxx; +use crate::{ + cstr, cstr_buf, errno, fd_path, fd_set_attr, FileAttr, FsPath, LibcReturn, Utf8CStr, + Utf8CStrBuf, +}; +use libc::{dirent, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY}; +use std::ffi::CStr; +use std::fs::File; +use std::ops::Deref; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use std::{io, mem, slice}; + +pub struct DirEntry<'a> { + dir: &'a Directory, + entry: &'a dirent, + d_name_len: usize, +} + +impl DirEntry<'_> { + pub fn name(&self) -> &CStr { + unsafe { + CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( + self.d_name.as_ptr().cast(), + self.d_name_len, + )) + } + } + + pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + self.dir.path(buf)?; + buf.push_str("/"); + buf.push_lossy(self.name().to_bytes()); + Ok(()) + } + + 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_symlink(&self) -> bool { + self.d_type == libc::DT_LNK + } + + pub fn is_block_device(&self) -> bool { + self.d_type == libc::DT_BLK + } + + pub fn is_char_device(&self) -> bool { + self.d_type == libc::DT_CHR + } + + pub fn is_fifo(&self) -> bool { + self.d_type == libc::DT_FIFO + } + + pub fn is_socket(&self) -> bool { + self.d_type == libc::DT_SOCK + } + + pub fn unlink(&self) -> io::Result<()> { + let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 }; + unsafe { + libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?; + } + Ok(()) + } + + pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { + buf.clear(); + unsafe { + let r = readlinkat_for_cxx( + self.dir.as_raw_fd(), + self.d_name.as_ptr(), + buf.as_mut_ptr().cast(), + buf.capacity(), + ) + .check_os_err()? as usize; + buf.set_len(r); + } + Ok(()) + } + + unsafe fn open_fd(&self, flags: i32) -> io::Result { + unsafe { self.dir.open_raw_fd(self.name(), flags, 0) } + } + + pub fn open_as_dir(&self) -> io::Result { + if !self.is_dir() { + return Err(io::Error::from(io::ErrorKind::NotADirectory)); + } + unsafe { Directory::try_from(OwnedFd::from_raw_fd(self.open_fd(O_RDONLY)?)) } + } + + pub fn open_as_file(&self, flags: i32) -> io::Result { + if self.is_dir() { + return Err(io::Error::from(io::ErrorKind::IsADirectory)); + } + unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) } + } + + pub fn get_attr(&self) -> io::Result { + let mut path = cstr_buf::default(); + self.path(&mut path)?; + FsPath::from(&path).get_attr() + } + + pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { + let mut path = cstr_buf::default(); + self.path(&mut path)?; + FsPath::from(&path).set_attr(attr) + } + + pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> { + let mut path = cstr_buf::default(); + self.path(&mut path)?; + FsPath::from(&path).get_secontext(con) + } + + pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> { + let mut path = cstr_buf::default(); + self.path(&mut path)?; + FsPath::from(&path).set_secontext(con) + } +} + +impl Deref for DirEntry<'_> { + type Target = dirent; + + fn deref(&self) -> &dirent { + self.entry + } +} + +pub struct Directory { + dirp: *mut libc::DIR, +} + +pub enum WalkResult { + Continue, + Abort, + Skip, +} + +impl Directory { + pub fn open(path: &Utf8CStr) -> io::Result { + let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?; + Ok(Directory { dirp }) + } + + 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()); + if d_name == cstr!(".") || d_name == cstr!("..") { + self.read() + } else { + let e = DirEntry { + dir: self, + entry, + d_name_len: d_name.to_bytes_with_nul().len(), + }; + Ok(Some(e)) + } + } + } + + pub fn rewind(&mut self) { + unsafe { libc::rewinddir(self.dirp) } + } + + unsafe fn open_raw_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result { + unsafe { + libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err() + } + } + + pub fn open_fd(&self, name: &Utf8CStr, flags: i32, mode: i32) -> io::Result { + unsafe { + self.open_raw_fd(name.as_cstr(), flags, mode) + .map(|fd| OwnedFd::from_raw_fd(fd)) + } + } + + pub fn contains_path(&self, path: &CStr) -> bool { + // WARNING: Using faccessat is incorrect, because the raw linux kernel syscall + // does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2. + // Use fstatat to check the existence of a file instead. + unsafe { + let mut st: libc::stat = mem::zeroed(); + libc::fstatat( + self.as_raw_fd(), + path.as_ptr(), + &mut st, + libc::AT_SYMLINK_NOFOLLOW, + ) == 0 + } + } + + pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> 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(()) + } + + pub fn copy_into(&mut self, dir: &Directory) -> io::Result<()> { + while let Some(ref e) = self.read()? { + let attr = e.get_attr()?; + let new_entry = DirEntry { + dir, + entry: e.entry, + d_name_len: e.d_name_len, + }; + if e.is_dir() { + unsafe { + libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?; + } + let mut src = e.open_as_dir()?; + let dest = new_entry.open_as_dir()?; + src.copy_into(&dest)?; + fd_set_attr(dest.as_raw_fd(), &attr)?; + } else if e.is_file() { + let mut src = e.open_as_file(O_RDONLY)?; + let mut dest = unsafe { + File::from_raw_fd(dir.open_raw_fd( + e.name(), + O_WRONLY | O_CREAT | O_TRUNC, + 0o777, + )?) + }; + std::io::copy(&mut src, &mut dest)?; + fd_set_attr(dest.as_raw_fd(), &attr)?; + } else if e.is_symlink() { + let mut path = cstr_buf::default(); + e.read_link(&mut path)?; + unsafe { + libc::symlinkat(path.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr()) + .as_os_err()?; + } + new_entry.set_attr(&attr)?; + } + } + Ok(()) + } + + pub fn move_into(&mut self, dir: &Directory) -> io::Result<()> { + let dir_fd = self.as_raw_fd(); + while let Some(ref e) = self.read()? { + if e.is_dir() && dir.contains_path(e.name()) { + // Destination folder exists, needs recursive move + let mut src = e.open_as_dir()?; + let new_entry = DirEntry { + dir, + entry: e.entry, + d_name_len: e.d_name_len, + }; + let dest = new_entry.open_as_dir()?; + src.move_into(&dest)?; + return e.unlink(); + } + + unsafe { + libc::renameat( + dir_fd, + e.d_name.as_ptr(), + dir.as_raw_fd(), + e.d_name.as_ptr(), + ) + .as_os_err()?; + } + } + Ok(()) + } + + pub fn link_into(&mut self, dir: &Directory) -> io::Result<()> { + let dir_fd = self.as_raw_fd(); + while let Some(ref e) = self.read()? { + if e.is_dir() { + unsafe { + libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?; + } + 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 dest = new_entry.open_as_dir()?; + src.link_into(&dest)?; + fd_set_attr(dest.as_raw_fd(), &attr)?; + } else { + unsafe { + libc::linkat( + dir_fd, + e.d_name.as_ptr(), + dir.as_raw_fd(), + e.d_name.as_ptr(), + 0, + ) + .as_os_err()?; + } + } + } + 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 => 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()) }.check_os_err()?; + Ok(Directory { dirp }) + } +} + +impl AsRawFd for Directory { + fn as_raw_fd(&self) -> RawFd { + unsafe { libc::dirfd(self.dirp) } + } +} + +impl AsFd for Directory { + fn as_fd(&self) -> BorrowedFd { + unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } + } +} + +impl Drop for Directory { + fn drop(&mut self) { + unsafe { + libc::closedir(self.dirp); + } + } +} diff --git a/native/src/base/files.rs b/native/src/base/files.rs index 743adbf76..693e733cc 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -1,8 +1,10 @@ -use crate::cxx_extern::readlinkat_for_cxx; -use crate::{cstr, cstr_buf, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrBuf}; +use crate::{ + cstr_buf, errno, error, Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, Utf8CStr, + Utf8CStrBuf, +}; use bytemuck::{bytes_of, bytes_of_mut, Pod}; use libc::{ - c_uint, dirent, makedev, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, + c_uint, makedev, mode_t, stat, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, }; use mem::MaybeUninit; @@ -11,8 +13,7 @@ use std::cmp::min; use std::ffi::CStr; use std::fs::File; use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write}; -use std::ops::Deref; -use std::os::fd::{AsFd, BorrowedFd, IntoRawFd}; +use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::path::Path; @@ -186,418 +187,13 @@ impl FileAttr { } } -const XATTR_NAME_SELINUX: &[u8] = b"security.selinux\0"; - -pub struct DirEntry<'a> { - dir: &'a Directory, - entry: &'a dirent, - d_name_len: usize, -} - -impl DirEntry<'_> { - pub fn name(&self) -> &CStr { - unsafe { - CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( - self.d_name.as_ptr().cast(), - self.d_name_len, - )) - } - } - - pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { - self.dir.path(buf)?; - buf.push_str("/"); - buf.push_lossy(self.name().to_bytes()); - Ok(()) - } - - 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_symlink(&self) -> bool { - self.d_type == libc::DT_LNK - } - - pub fn is_block_device(&self) -> bool { - self.d_type == libc::DT_BLK - } - - pub fn is_char_device(&self) -> bool { - self.d_type == libc::DT_CHR - } - - pub fn is_fifo(&self) -> bool { - self.d_type == libc::DT_FIFO - } - - pub fn is_socket(&self) -> bool { - self.d_type == libc::DT_SOCK - } - - pub fn unlink(&self) -> io::Result<()> { - let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 }; - unsafe { - libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?; - } - Ok(()) - } - - pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> { - buf.clear(); - unsafe { - let r = readlinkat_for_cxx( - self.dir.as_raw_fd(), - self.d_name.as_ptr(), - buf.as_mut_ptr().cast(), - buf.capacity(), - ) - .check_os_err()? as usize; - buf.set_len(r); - } - Ok(()) - } - - unsafe fn open_fd(&self, flags: i32) -> io::Result { - unsafe { self.dir.open_raw_fd(self.name(), flags, 0) } - } - - pub fn open_as_dir(&self) -> io::Result { - if !self.is_dir() { - return Err(io::Error::from(io::ErrorKind::NotADirectory)); - } - unsafe { Directory::try_from(OwnedFd::from_raw_fd(self.open_fd(O_RDONLY)?)) } - } - - pub fn open_as_file(&self, flags: i32) -> io::Result { - if self.is_dir() { - return Err(io::Error::from(io::ErrorKind::IsADirectory)); - } - unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) } - } - - pub fn get_attr(&self) -> io::Result { - let mut path = cstr_buf::default(); - self.path(&mut path)?; - FsPath::from(&path).get_attr() - } - - pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { - let mut path = cstr_buf::default(); - self.path(&mut path)?; - FsPath::from(&path).set_attr(attr) - } - - pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> { - let mut path = cstr_buf::default(); - self.path(&mut path)?; - FsPath::from(&path).get_secontext(con) - } - - pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> { - let mut path = cstr_buf::default(); - self.path(&mut path)?; - FsPath::from(&path).set_secontext(con) - } -} - -impl Deref for DirEntry<'_> { - type Target = dirent; - - fn deref(&self) -> &dirent { - self.entry - } -} - -pub struct Directory { - dirp: *mut libc::DIR, -} - -pub enum WalkResult { - Continue, - Abort, - Skip, -} - -impl Directory { - pub fn open(path: &Utf8CStr) -> io::Result { - let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?; - Ok(Directory { dirp }) - } - - 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()); - if d_name == cstr!(".") || d_name == cstr!("..") { - self.read() - } else { - let e = DirEntry { - dir: self, - entry, - d_name_len: d_name.to_bytes_with_nul().len(), - }; - Ok(Some(e)) - } - } - } - - pub fn rewind(&mut self) { - unsafe { libc::rewinddir(self.dirp) } - } - - unsafe fn open_raw_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result { - unsafe { - libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err() - } - } - - pub fn open_fd(&self, name: &Utf8CStr, flags: i32, mode: i32) -> io::Result { - unsafe { - self.open_raw_fd(name.as_cstr(), flags, mode) - .map(|fd| OwnedFd::from_raw_fd(fd)) - } - } - - pub fn contains_path(&self, path: &CStr) -> bool { - // WARNING: Using faccessat is incorrect, because the raw linux kernel syscall - // does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2. - // Use fstatat to check the existence of a file instead. - unsafe { - let mut st: libc::stat = mem::zeroed(); - libc::fstatat( - self.as_raw_fd(), - path.as_ptr(), - &mut st, - libc::AT_SYMLINK_NOFOLLOW, - ) == 0 - } - } - - pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> 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(()) - } - - pub fn copy_into(&mut self, dir: &Directory) -> io::Result<()> { - while let Some(ref e) = self.read()? { - let attr = e.get_attr()?; - let new_entry = DirEntry { - dir, - entry: e.entry, - d_name_len: e.d_name_len, - }; - if e.is_dir() { - unsafe { - libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?; - } - let mut src = e.open_as_dir()?; - let dest = new_entry.open_as_dir()?; - src.copy_into(&dest)?; - fd_set_attr(dest.as_raw_fd(), &attr)?; - } else if e.is_file() { - let mut src = e.open_as_file(O_RDONLY)?; - let mut dest = unsafe { - File::from_raw_fd(dir.open_raw_fd( - e.name(), - O_WRONLY | O_CREAT | O_TRUNC, - 0o777, - )?) - }; - std::io::copy(&mut src, &mut dest)?; - fd_set_attr(dest.as_raw_fd(), &attr)?; - } else if e.is_symlink() { - let mut path = cstr_buf::default(); - e.read_link(&mut path)?; - unsafe { - libc::symlinkat(path.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr()) - .as_os_err()?; - } - new_entry.set_attr(&attr)?; - } - } - Ok(()) - } - - pub fn move_into(&mut self, dir: &Directory) -> io::Result<()> { - let dir_fd = self.as_raw_fd(); - while let Some(ref e) = self.read()? { - if e.is_dir() && dir.contains_path(e.name()) { - // Destination folder exists, needs recursive move - let mut src = e.open_as_dir()?; - let new_entry = DirEntry { - dir, - entry: e.entry, - d_name_len: e.d_name_len, - }; - let dest = new_entry.open_as_dir()?; - src.move_into(&dest)?; - return e.unlink(); - } - - unsafe { - libc::renameat( - dir_fd, - e.d_name.as_ptr(), - dir.as_raw_fd(), - e.d_name.as_ptr(), - ) - .as_os_err()?; - } - } - Ok(()) - } - - pub fn link_into(&mut self, dir: &Directory) -> io::Result<()> { - let dir_fd = self.as_raw_fd(); - while let Some(ref e) = self.read()? { - if e.is_dir() { - unsafe { - libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?; - } - 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 dest = new_entry.open_as_dir()?; - src.link_into(&dest)?; - fd_set_attr(dest.as_raw_fd(), &attr)?; - } else { - unsafe { - libc::linkat( - dir_fd, - e.d_name.as_ptr(), - dir.as_raw_fd(), - e.d_name.as_ptr(), - 0, - ) - .as_os_err()?; - } - } - } - 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 => 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()) }.check_os_err()?; - Ok(Directory { dirp }) - } -} - -impl AsRawFd for Directory { - fn as_raw_fd(&self) -> RawFd { - unsafe { libc::dirfd(self.dirp) } - } -} - -impl AsFd for Directory { - fn as_fd(&self) -> BorrowedFd { - unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } - } -} - -impl Drop for Directory { - fn drop(&mut self) { - unsafe { - libc::closedir(self.dirp); - } - } -} +const XATTR_NAME_SELINUX: &CStr = c"security.selinux"; impl FsPath { + pub fn follow_link(&self) -> &FsPathFollow { + unsafe { mem::transmute(self) } + } + pub fn open(&self, flags: i32) -> io::Result { Ok(File::from(open_fd!(self, flags)?)) } @@ -607,7 +203,10 @@ impl FsPath { } pub fn exists(&self) -> bool { - unsafe { libc::access(self.as_ptr(), F_OK) == 0 } + unsafe { + let mut st: stat = mem::zeroed(); + libc::lstat(self.as_ptr(), &mut st) == 0 + } } pub fn rename_to>(&self, name: T) -> io::Result<()> { @@ -732,7 +331,7 @@ impl FsPath { unsafe { let sz = libc::lgetxattr( self.as_ptr(), - XATTR_NAME_SELINUX.as_ptr().cast(), + XATTR_NAME_SELINUX.as_ptr(), con.as_mut_ptr().cast(), con.capacity(), ); @@ -752,7 +351,7 @@ impl FsPath { unsafe { libc::lsetxattr( self.as_ptr(), - XATTR_NAME_SELINUX.as_ptr().cast(), + XATTR_NAME_SELINUX.as_ptr(), con.as_ptr().cast(), con.len() + 1, 0, @@ -832,6 +431,69 @@ impl FsPath { } } +impl FsPathFollow { + pub fn exists(&self) -> bool { + unsafe { libc::access(self.as_ptr(), F_OK) == 0 } + } + + pub fn get_attr(&self) -> io::Result { + let mut attr = FileAttr::new(); + unsafe { + libc::stat(self.as_ptr(), &mut attr.st).as_os_err()?; + + #[cfg(feature = "selinux")] + self.get_secontext(&mut attr.con)?; + } + Ok(attr) + } + + pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { + unsafe { + libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?; + libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?; + + #[cfg(feature = "selinux")] + if !attr.con.is_empty() { + self.set_secontext(&attr.con)?; + } + } + Ok(()) + } + + pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> { + unsafe { + let sz = libc::getxattr( + self.as_ptr(), + XATTR_NAME_SELINUX.as_ptr(), + con.as_mut_ptr().cast(), + con.capacity(), + ); + if sz < 1 { + con.clear(); + if *errno() != libc::ENODATA { + return Err(io::Error::last_os_error()); + } + } else { + con.set_len((sz - 1) as usize); + } + } + Ok(()) + } + + pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> { + unsafe { + libc::setxattr( + self.as_ptr(), + XATTR_NAME_SELINUX.as_ptr(), + con.as_ptr().cast(), + con.len() + 1, + 0, + ) + .as_os_err() + } + } +} + pub fn fd_get_attr(fd: RawFd) -> io::Result { let mut attr = FileAttr::new(); unsafe { @@ -841,7 +503,7 @@ pub fn fd_get_attr(fd: RawFd) -> io::Result { { let sz = libc::fgetxattr( fd, - XATTR_NAME_SELINUX.as_ptr().cast(), + XATTR_NAME_SELINUX.as_ptr(), attr.con.as_mut_ptr().cast(), attr.con.capacity(), ); @@ -866,7 +528,7 @@ pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> io::Result<()> { if !attr.con.is_empty() { libc::fsetxattr( fd, - XATTR_NAME_SELINUX.as_ptr().cast(), + XATTR_NAME_SELINUX.as_ptr(), attr.con.as_ptr().cast(), attr.con.len() + 1, 0, diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index 585d7e464..e6e3bedbd 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -8,6 +8,7 @@ use num_traits::FromPrimitive; pub use cstr::*; use cxx_extern::*; +pub use dir::*; pub use ffi::fork_dont_care; pub use files::*; pub use logging::*; @@ -16,6 +17,7 @@ pub use result::*; mod cstr; mod cxx_extern; +mod dir; mod files; mod logging; mod misc; diff --git a/native/src/init/twostage.rs b/native/src/init/twostage.rs index 766f737d3..5d165aa0e 100644 --- a/native/src/init/twostage.rs +++ b/native/src/init/twostage.rs @@ -2,7 +2,7 @@ use crate::ffi::MagiskInit; use base::{ clone_attr, cstr, debug, error, info, libc::{ - fstatat, mount, stat, statfs, umount2, AT_SYMLINK_NOFOLLOW, MNT_DETACH, MS_BIND, O_CLOEXEC, + mount, statfs, umount2, MNT_DETACH, MS_BIND, O_CLOEXEC, O_CREAT, O_RDONLY, O_WRONLY, TMPFS_MAGIC, }, path, raw_cstr, LibcReturn, MappedFile, MutBytesExt, ResultExt, @@ -14,16 +14,7 @@ impl MagiskInit { info!("First Stage Init"); self.prepare_data(); - if unsafe { - let mut st: stat = std::mem::zeroed(); - fstatat(-1, raw_cstr!("/sdcard"), &mut st, AT_SYMLINK_NOFOLLOW) != 0 - && fstatat( - -1, - raw_cstr!("/first_stage_ramdisk/sdcard"), - &mut st, - AT_SYMLINK_NOFOLLOW, - ) != 0 - } { + if !path!("/sdcard").exists() && !path!("/first_stage_ramdisk/sdcard").exists() { if self.config.force_normal_boot { path!("/first_stage_ramdisk/storage/self") .mkdirs(0o755)