diff --git a/native/src/Cargo.lock b/native/src/Cargo.lock index e0f5ad7b5..5511d6b6c 100644 --- a/native/src/Cargo.lock +++ b/native/src/Cargo.lock @@ -75,6 +75,7 @@ dependencies = [ "cxx", "cxx-gen", "libc", + "num-traits", "thiserror", ] diff --git a/native/src/base/Cargo.toml b/native/src/base/Cargo.toml index 67c124b7b..8deb9bb2e 100644 --- a/native/src/base/Cargo.toml +++ b/native/src/base/Cargo.toml @@ -16,3 +16,4 @@ cfg-if = { workspace = true } thiserror = { workspace = true } argh = { workspace = true } bytemuck = { workspace = true } +num-traits = { workspace = true } diff --git a/native/src/base/compat/compat.cpp b/native/src/base/compat/compat.cpp index 0a689e0ad..ce5187506 100644 --- a/native/src/base/compat/compat.cpp +++ b/native/src/base/compat/compat.cpp @@ -7,6 +7,7 @@ #include #include #include +#include extern "C" { @@ -90,6 +91,16 @@ int mkfifo(const char *path, mode_t mode) { return mknod(path, (mode & ~S_IFMT) | S_IFIFO, 0); } +[[gnu::weak]] +int fsetxattr(int fd, const char *name, const void *value, size_t size, int flags) { + return syscall(__NR_fsetxattr, fd, name, value, size, flags); +} + +[[gnu::weak]] +int lsetxattr(const char *path, const char *name, const void *value, size_t size, int flags) { + return syscall(__NR_lsetxattr, path, name, value, size, flags); +} + #define SPLIT_64(v) (unsigned)((v) & 0xFFFFFFFF), (unsigned)((v) >> 32) #if defined(__arm__) diff --git a/native/src/base/cxx_extern.rs b/native/src/base/cxx_extern.rs index be3d2b0b7..8678309a8 100644 --- a/native/src/base/cxx_extern.rs +++ b/native/src/base/cxx_extern.rs @@ -3,12 +3,15 @@ use std::io; use std::os::fd::{BorrowedFd, OwnedFd, RawFd}; +use cfg_if::cfg_if; use cxx::private::c_char; use libc::mode_t; use crate::logging::CxxResultExt; pub(crate) use crate::xwrap::*; -use crate::{fd_path, map_fd, map_file, Directory, FsPath, Utf8CStr, Utf8CStrSlice}; +use crate::{ + clone_attr, fclone_attr, fd_path, map_fd, map_file, Directory, FsPath, Utf8CStr, Utf8CStrSlice, +}; pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize { let mut buf = Utf8CStrSlice::from(buf); @@ -69,3 +72,102 @@ pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8 .unwrap_or(&mut []) } } + +pub(crate) unsafe fn readlinkat_for_cxx( + dirfd: RawFd, + path: *const c_char, + buf: *mut u8, + bufsz: usize, +) -> isize { + // readlinkat() may fail on x86 platform, returning random value + // instead of number of bytes placed in buf (length of link) + cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + libc::memset(buf.cast(), 0, bufsz); + let mut r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1); + if r > 0 { + r = libc::strlen(buf.cast()) as isize; + } + } else { + let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1); + if r >= 0 { + *buf.offset(r) = b'\0'; + } + } + } + r +} + +#[export_name = "cp_afc"] +unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) -> bool { + if let Ok(src) = Utf8CStr::from_ptr(src) { + if let Ok(dest) = Utf8CStr::from_ptr(dest) { + let src = FsPath::from(src); + let dest = FsPath::from(dest); + return src + .copy_to(dest) + .log_cxx_with_msg(|w| { + w.write_fmt(format_args!("cp_afc {} -> {} failed", src, dest)) + }) + .is_ok(); + } + } + false +} + +#[export_name = "mv_path"] +unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool { + if let Ok(src) = Utf8CStr::from_ptr(src) { + if let Ok(dest) = Utf8CStr::from_ptr(dest) { + let src = FsPath::from(src); + let dest = FsPath::from(dest); + return src + .move_to(dest) + .log_cxx_with_msg(|w| { + w.write_fmt(format_args!("mv_path {} -> {} failed", src, dest)) + }) + .is_ok(); + } + } + false +} + +#[export_name = "link_path"] +unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool { + if let Ok(src) = Utf8CStr::from_ptr(src) { + if let Ok(dest) = Utf8CStr::from_ptr(dest) { + let src = FsPath::from(src); + let dest = FsPath::from(dest); + return src + .link_to(dest) + .log_cxx_with_msg(|w| { + w.write_fmt(format_args!("link_path {} -> {} failed", src, dest)) + }) + .is_ok(); + } + } + false +} + +#[export_name = "clone_attr"] +unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char) -> bool { + if let Ok(src) = Utf8CStr::from_ptr(src) { + if let Ok(dest) = Utf8CStr::from_ptr(dest) { + let src = FsPath::from(src); + let dest = FsPath::from(dest); + return clone_attr(src, dest) + .log_cxx_with_msg(|w| { + w.write_fmt(format_args!("clone_attr {} -> {} failed", src, dest)) + }) + .is_ok(); + } + } + false +} + +#[export_name = "fclone_attr"] +unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool { + fclone_attr(a, b) + .log_cxx_with_msg(|w| w.write_str("fclone_attr failed")) + .is_ok() +} diff --git a/native/src/base/files.cpp b/native/src/base/files.cpp index 6829de266..3cb989875 100644 --- a/native/src/base/files.cpp +++ b/native/src/base/files.cpp @@ -20,191 +20,6 @@ int fd_pathat(int dirfd, const char *name, char *path, size_t size) { return 0; } -void mv_path(const char *src, const char *dest) { - file_attr attr; - getattr(src, &attr); - if (S_ISDIR(attr.st.st_mode)) { - if (access(dest, F_OK) != 0) { - xmkdirs(dest, 0); - setattr(dest, &attr); - } - mv_dir(xopen(src, O_RDONLY | O_CLOEXEC), xopen(dest, O_RDONLY | O_CLOEXEC)); - } else{ - xrename(src, dest); - } - rmdir(src); -} - -void mv_dir(int src, int dest) { - auto dir = xopen_dir(src); - run_finally f([=]{ close(dest); }); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - switch (entry->d_type) { - case DT_DIR: - if (xfaccessat(dest, entry->d_name, F_OK, 0) == 0) { - // Destination folder exists, needs recursive move - int newsrc = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC); - int newdest = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC); - mv_dir(newsrc, newdest); - unlinkat(src, entry->d_name, AT_REMOVEDIR); - break; - } - // Else fall through - case DT_LNK: - case DT_REG: - renameat(src, entry->d_name, dest, entry->d_name); - break; - } - } -} - -void cp_afc(const char *src, const char *dest) { - file_attr a; - getattr(src, &a); - - if (S_ISDIR(a.st.st_mode)) { - xmkdirs(dest, 0); - clone_dir(xopen(src, O_RDONLY | O_CLOEXEC), xopen(dest, O_RDONLY | O_CLOEXEC)); - } else{ - unlink(dest); - if (S_ISREG(a.st.st_mode)) { - int sfd = xopen(src, O_RDONLY | O_CLOEXEC); - int dfd = xopen(dest, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0); - xsendfile(dfd, sfd, nullptr, a.st.st_size); - close(sfd); - close(dfd); - } else if (S_ISLNK(a.st.st_mode)) { - char buf[4096]; - xreadlink(src, buf, sizeof(buf)); - xsymlink(buf, dest); - } - } - setattr(dest, &a); -} - -void clone_dir(int src, int dest) { - auto dir = xopen_dir(src); - run_finally f([&]{ close(dest); }); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - file_attr a; - getattrat(src, entry->d_name, &a); - switch (entry->d_type) { - case DT_DIR: { - xmkdirat(dest, entry->d_name, 0); - setattrat(dest, entry->d_name, &a); - int sfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC); - int dst = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC); - clone_dir(sfd, dst); - break; - } - case DT_REG: { - int sfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC); - int dfd = xopenat(dest, entry->d_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0); - xsendfile(dfd, sfd, nullptr, a.st.st_size); - fsetattr(dfd, &a); - close(dfd); - close(sfd); - break; - } - case DT_LNK: { - char buf[4096]; - xreadlinkat(src, entry->d_name, buf, sizeof(buf)); - xsymlinkat(buf, dest, entry->d_name); - setattrat(dest, entry->d_name, &a); - break; - } - } - } -} - -void link_path(const char *src, const char *dest) { - link_dir(xopen(src, O_RDONLY | O_CLOEXEC), xopen(dest, O_RDONLY | O_CLOEXEC)); -} - -void link_dir(int src, int dest) { - auto dir = xopen_dir(src); - run_finally f([&]{ close(dest); }); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - if (entry->d_type == DT_DIR) { - file_attr a; - getattrat(src, entry->d_name, &a); - xmkdirat(dest, entry->d_name, 0); - setattrat(dest, entry->d_name, &a); - int sfd = xopenat(src, entry->d_name, O_RDONLY | O_CLOEXEC); - int dfd = xopenat(dest, entry->d_name, O_RDONLY | O_CLOEXEC); - link_dir(sfd, dfd); - } else { - xlinkat(src, entry->d_name, dest, entry->d_name, 0); - } - } -} - -int getattr(const char *path, file_attr *a) { - if (xlstat(path, &a->st) == -1) - return -1; - char *con; - if (lgetfilecon(path, &con) == -1) - return -1; - strcpy(a->con, con); - freecon(con); - return 0; -} - -int getattrat(int dirfd, const char *name, file_attr *a) { - char path[4096]; - fd_pathat(dirfd, name, path, sizeof(path)); - return getattr(path, a); -} - -int fgetattr(int fd, file_attr *a) { - if (xfstat(fd, &a->st) < 0) - return -1; - char *con; - if (fgetfilecon(fd, &con) < 0) - return -1; - strcpy(a->con, con); - freecon(con); - return 0; -} - -int setattr(const char *path, file_attr *a) { - if (chmod(path, a->st.st_mode & 0777) < 0) - return -1; - if (chown(path, a->st.st_uid, a->st.st_gid) < 0) - return -1; - if (a->con[0] && lsetfilecon(path, a->con) < 0) - return -1; - return 0; -} - -int setattrat(int dirfd, const char *name, file_attr *a) { - char path[4096]; - fd_pathat(dirfd, name, path, sizeof(path)); - return setattr(path, a); -} - -int fsetattr(int fd, file_attr *a) { - if (fchmod(fd, a->st.st_mode & 0777) < 0) - return -1; - if (fchown(fd, a->st.st_uid, a->st.st_gid) < 0) - return -1; - if (a->con[0] && fsetfilecon(fd, a->con) < 0) - return -1; - return 0; -} - -void clone_attr(const char *src, const char *dest) { - file_attr a; - getattr(src, &a); - setattr(dest, &a); -} - -void fclone_attr(int src, int dest) { - file_attr a; - fgetattr(src, &a); - fsetattr(dest, &a); -} - void full_read(int fd, string &str) { char buf[4096]; for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;) diff --git a/native/src/base/files.hpp b/native/src/base/files.hpp index 0d8d11caf..0c8f7277b 100644 --- a/native/src/base/files.hpp +++ b/native/src/base/files.hpp @@ -20,11 +20,6 @@ static inline T align_padding(T v, int a) { return align_to(v, a) - v; } -struct file_attr { - struct stat st; - char con[128]; -}; - struct mount_info { unsigned int id; unsigned int parent; @@ -58,27 +53,19 @@ 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); +void cp_afc(const char *src, const char *dest); +void mv_path(const char *src, const char *dest); +void link_path(const char *src, const char *dest); +void clone_attr(const char *src, const char *dest); +void fclone_attr(int src, int dest); } // extern "C" int fd_pathat(int dirfd, const char *name, char *path, size_t size); -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); -void link_path(const char *src, const char *dest); -void link_dir(int src, int dest); static inline ssize_t realpath( const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz) { return canonical_path(path, buf, bufsiz); } -int getattr(const char *path, file_attr *a); -int getattrat(int dirfd, const char *name, file_attr *a); -int fgetattr(int fd, file_attr *a); -int setattr(const char *path, file_attr *a); -int setattrat(int dirfd, const char *name, file_attr *a); -int fsetattr(int fd, file_attr *a); -void fclone_attr(int src, int dest); -void clone_attr(const char *src, const char *dest); void full_read(int fd, std::string &str); void full_read(const char *filename, std::string &str); std::string full_read(int fd); @@ -90,7 +77,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 clone_dir(int src, int dest); std::vector parse_mount_info(const char *pid); std::string resolve_preinit_dir(const char *base_dir); diff --git a/native/src/base/files.rs b/native/src/base/files.rs index d82e5e9fb..37b51b1e6 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -8,11 +8,17 @@ use std::os::android::fs::MetadataExt; use std::os::fd::{AsFd, BorrowedFd, IntoRawFd}; use std::os::unix::fs::FileTypeExt; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::{io, mem, ptr, slice}; use bytemuck::{bytes_of_mut, Pod}; -use libc::{c_uint, dirent, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_PATH, O_RDONLY, O_RDWR}; +use libc::{ + c_uint, dirent, mode_t, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, + O_TRUNC, O_WRONLY, S_IFDIR, S_IFLNK, S_IFREG, +}; +use num_traits::AsPrimitive; +use crate::cxx_extern::readlinkat_for_cxx; use crate::{ copy_cstr, cstr, errno, error, FsPath, FsPathBuf, LibcReturn, Utf8CStr, Utf8CStrArr, Utf8CStrBuf, @@ -132,6 +138,18 @@ impl WriteExt for T { } } +pub struct FileAttr { + pub st: libc::stat, + pub con: Utf8CStrArr<128>, +} + +const XATTR_NAME_SELINUX: &[u8] = b"security.selinux\0"; +static SELINUX_ENABLED: AtomicBool = AtomicBool::new(false); + +pub fn enable_selinux() { + SELINUX_ENABLED.store(true, Ordering::Relaxed); +} + pub struct DirEntry<'a> { dir: &'a Directory, entry: &'a dirent, @@ -175,34 +193,49 @@ impl DirEntry<'_> { 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 { + self.dir.open_fd(self.d_name(), flags, 0) + } + 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, - ) - .check_os_err()?; - Directory::try_from(OwnedFd::from_raw_fd(fd)) - } + 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 { - let fd = libc::openat( - self.dir.as_raw_fd(), - self.d_name.as_ptr(), - flags | O_CLOEXEC, - ) - .check_os_err()?; - Ok(File::from_raw_fd(fd)) - } + unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) } + } + + pub fn get_attr(&self) -> io::Result { + let mut path = Utf8CStrArr::default(); + self.path(&mut path)?; + FsPath::from(path).get_attr() + } + + pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { + let mut path = Utf8CStrArr::default(); + self.path(&mut path)?; + FsPath::from(path).set_attr(attr) } } @@ -261,6 +294,25 @@ impl Directory { unsafe { libc::rewinddir(self.dirp) } } + unsafe fn open_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result { + libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err() + } + + 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) } @@ -286,6 +338,108 @@ impl Directory { })?; 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_fd( + e.d_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_lnk() { + let mut path = Utf8CStrArr::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.d_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 { @@ -410,6 +564,19 @@ impl FsPath { Ok(()) } + pub fn mkdir(&self, mode: mode_t) -> io::Result<()> { + unsafe { + if libc::mkdir(self.as_ptr(), mode) < 0 { + if *errno() == EEXIST { + libc::chmod(self.as_ptr(), mode).as_os_err()?; + } else { + return Err(io::Error::last_os_error()); + } + } + } + Ok(()) + } + pub fn mkdirs(&self, mode: mode_t) -> io::Result<()> { let mut buf = [0_u8; 4096]; let len = copy_cstr(&mut buf, self); @@ -456,6 +623,151 @@ impl FsPath { } Ok(()) } + + pub fn get_attr(&self) -> io::Result { + let mut attr: FileAttr; + unsafe { + attr = FileAttr { + st: mem::zeroed(), + con: Utf8CStrArr::new(), + }; + libc::lstat(self.as_ptr(), &mut attr.st).as_os_err()?; + if SELINUX_ENABLED.load(Ordering::Relaxed) { + let sz = libc::lgetxattr( + self.as_ptr(), + XATTR_NAME_SELINUX.as_ptr().cast(), + attr.con.as_mut_ptr().cast(), + attr.con.capacity(), + ) + .check_os_err()?; + attr.con.set_len((sz - 1) as usize); + } + } + Ok(attr) + } + + pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> { + unsafe { + if (attr.st.st_mode & libc::S_IFMT as c_uint) != S_IFLNK.as_() { + libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?; + } + libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?; + if !attr.con.is_empty() { + libc::lsetxattr( + self.as_ptr(), + XATTR_NAME_SELINUX.as_ptr().cast(), + attr.con.as_ptr().cast(), + attr.con.len() + 1, + 0, + ) + .as_os_err()?; + } + } + Ok(()) + } + + pub fn copy_to(&self, path: &FsPath) -> io::Result<()> { + let attr = self.get_attr()?; + if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFDIR.as_() { + path.mkdir(0o777)?; + let mut src = Directory::open(self)?; + let dest = Directory::open(path)?; + src.copy_into(&dest)?; + } else { + // It's OK if remove failed + path.remove().ok(); + if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFREG.as_() { + let mut src = self.open(O_RDONLY | O_CLOEXEC)?; + let mut dest = path.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o777)?; + std::io::copy(&mut src, &mut dest)?; + } else if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFLNK.as_() { + let mut buf = Utf8CStrArr::default(); + self.read_link(&mut buf)?; + unsafe { + libc::symlink(buf.as_ptr(), path.as_ptr()).as_os_err()?; + } + } + } + path.set_attr(&attr)?; + Ok(()) + } + + pub fn move_to(&self, path: &FsPath) -> io::Result<()> { + if path.exists() { + let attr = path.get_attr()?; + if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFDIR.as_() { + let mut src = Directory::open(self)?; + let dest = Directory::open(path)?; + return src.move_into(&dest); + } else { + path.remove()?; + } + } + self.rename_to(path) + } + + pub fn link_to(&self, path: &FsPath) -> io::Result<()> { + let attr = self.get_attr()?; + if (attr.st.st_mode & libc::S_IFMT as c_uint) == S_IFDIR.as_() { + path.mkdir(0o777)?; + path.set_attr(&attr)?; + let mut src = Directory::open(self)?; + let dest = Directory::open(path)?; + src.link_into(&dest) + } else { + unsafe { libc::link(self.as_ptr(), path.as_ptr()).as_os_err() } + } + } +} + +pub fn fd_get_attr(fd: RawFd) -> io::Result { + let mut attr: FileAttr; + unsafe { + attr = FileAttr { + st: mem::zeroed(), + con: Utf8CStrArr::new(), + }; + libc::fstat(fd, &mut attr.st).as_os_err()?; + if SELINUX_ENABLED.load(Ordering::Relaxed) { + let sz = libc::fgetxattr( + fd, + XATTR_NAME_SELINUX.as_ptr().cast(), + attr.con.as_mut_ptr().cast(), + attr.con.capacity(), + ) + .check_os_err()?; + attr.con.set_len((sz - 1) as usize); + } + } + Ok(attr) +} + +pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> io::Result<()> { + unsafe { + libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).as_os_err()?; + libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).as_os_err()?; + if !attr.con.is_empty() { + libc::fsetxattr( + fd, + XATTR_NAME_SELINUX.as_ptr().cast(), + attr.con.as_ptr().cast(), + attr.con.len() + 1, + 0, + ) + .as_os_err()?; + } + } + Ok(()) +} + +pub fn clone_attr(a: &FsPath, b: &FsPath) -> io::Result<()> { + let attr = a.get_attr()?; + b.set_attr(&attr) +} + +pub fn fclone_attr(a: RawFd, b: RawFd) -> io::Result<()> { + let attr = fd_get_attr(a)?; + fd_set_attr(b, &attr) } pub struct MappedFile(&'static mut [u8]); @@ -494,13 +806,13 @@ impl Drop for MappedFile { } } +extern "C" { + // Don't use the declaration from the libc crate as request should be u32 not i32 + fn ioctl(fd: RawFd, request: u32, ...) -> i32; +} + // We mark the returned slice static because it is valid until explicitly unmapped pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8]> { - extern "C" { - // Don't use the declaration from the libc crate as request should be u32 not i32 - fn ioctl(fd: RawFd, request: u32, ...) -> i32; - } - #[cfg(target_pointer_width = "64")] const BLKGETSIZE64: u32 = 0x80081272; diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index c4f952108..a8d5df721 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -51,5 +51,6 @@ pub mod ffi { fn map_file_for_cxx(path: &[u8], rw: bool) -> &'static mut [u8]; #[cxx_name = "map_fd"] fn map_fd_for_cxx(fd: i32, sz: usize, rw: bool) -> &'static mut [u8]; + fn enable_selinux(); } } diff --git a/native/src/base/selinux.cpp b/native/src/base/selinux.cpp index 53e3a4a01..0905bbba9 100644 --- a/native/src/base/selinux.cpp +++ b/native/src/base/selinux.cpp @@ -103,6 +103,7 @@ void setfilecon_at(int dirfd, const char *name, const char *con) { } void enable_selinux() { + rust::enable_selinux(); setcon = __setcon; getfilecon = __getfilecon; lgetfilecon = __lgetfilecon; diff --git a/native/src/base/xwrap.rs b/native/src/base/xwrap.rs index e353d33fb..3fe949211 100644 --- a/native/src/base/xwrap.rs +++ b/native/src/base/xwrap.rs @@ -4,12 +4,12 @@ use std::ffi::CStr; use std::os::unix::io::RawFd; use std::ptr; -use cfg_if::cfg_if; 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::cxx_extern::readlinkat_for_cxx; use crate::{cstr, errno, raw_cstr, CxxResultExt, FsPath, Utf8CStr, Utf8CStrSlice}; fn ptr_to_str<'a, T>(ptr: *const T) -> &'a str { @@ -96,23 +96,9 @@ unsafe extern "C" fn xreadlinkat( buf: *mut u8, bufsz: usize, ) -> isize { - // readlinkat() may fail on x86 platform, returning random value - // instead of number of bytes placed in buf (length of link) - cfg_if! { - if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { - libc::memset(buf.cast(), 0, bufsz); - let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1); - if r < 0 { - perror!("readlinkat {}", ptr_to_str(path)) - } - } else { - let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1); - if r < 0 { - perror!("readlinkat {}", ptr_to_str(path)) - } else { - *buf.offset(r) = b'\0'; - } - } + let r = readlinkat_for_cxx(dirfd, path, buf, bufsz); + if r < 0 { + perror!("readlinkat {}", ptr_to_str(path)) } r } diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 5d5f2224b..279230585 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -23,9 +23,6 @@ pub mod ffi { type PropCb; unsafe fn get_prop_rs(name: *const c_char, persist: bool) -> String; unsafe fn prop_cb_exec(cb: Pin<&mut PropCb>, name: *const c_char, value: *const c_char); - - include!("../base/files.hpp"); - unsafe fn clone_attr(src: *const c_char, dst: *const c_char); } extern "Rust" { diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index b06d2c1a8..a37a82a3d 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -164,11 +164,11 @@ void module_node::mount() { void tmpfs_node::mount() { string src = mirror_path(); const string &dest = node_path(); - file_attr a{}; + const char *src_path; if (access(src.data(), F_OK) == 0) - getattr(src.data(), &a); + src_path = src.data(); else - getattr(parent()->node_path().data(), &a); + src_path = parent()->node_path().data(); if (!isa(parent())) { auto worker_dir = MAGISKTMP + "/" WORKERDIR + dest; mkdirs(worker_dir.data(), 0); @@ -177,7 +177,7 @@ void tmpfs_node::mount() { // We don't need another layer of tmpfs if parent is tmpfs mkdir(dest.data(), 0); } - setattr(dest.data(), &a); + clone_attr(src_path, dest.data()); dir_node::mount(); } diff --git a/native/src/core/resetprop/persist.rs b/native/src/core/resetprop/persist.rs index 0b90fa865..59c7f4dc9 100644 --- a/native/src/core/resetprop/persist.rs +++ b/native/src/core/resetprop/persist.rs @@ -12,11 +12,11 @@ use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{ - cstr, debug, libc::mkstemp, raw_cstr, Directory, FsPath, FsPathBuf, LibcReturn, LoggedError, + clone_attr, cstr, debug, libc::mkstemp, Directory, FsPath, FsPathBuf, LibcReturn, LoggedError, LoggedResult, MappedFile, Utf8CStr, Utf8CStrArr, WalkResult, }; -use crate::ffi::{clone_attr, prop_cb_exec, PropCb}; +use crate::ffi::{prop_cb_exec, PropCb}; use crate::resetprop::proto::persistent_properties::{ mod_PersistentProperties::PersistentPropertyRecord, PersistentProperties, }; @@ -140,7 +140,7 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> { debug!("resetprop: encode with protobuf [{}]", tmp); props.write_message(&mut Writer::new(BufWriter::new(f)))?; } - unsafe { clone_attr(raw_cstr!(PERSIST_PROP!()), tmp.as_ptr()) }; + clone_attr(FsPath::from(cstr!(PERSIST_PROP!())), &tmp)?; tmp.rename_to(cstr!(PERSIST_PROP!()))?; Ok(()) }