Refactor module mount load in rust

This commit is contained in:
LoveSy 2025-03-11 00:33:47 +08:00
parent 1f162b819d
commit 0905adcd64
8 changed files with 624 additions and 636 deletions

View File

@ -81,6 +81,9 @@ pub trait Utf8CStrBuf:
// 2. Ensure len <= capacity - 1
// 3. All bytes from 0 to len is valid UTF-8 and does not contain null
unsafe fn set_len(&mut self, len: usize);
// self[len] = b'\0'; self.set_len(len)
fn resize(&mut self, len: usize);
fn push_str(&mut self, s: &str) -> usize;
fn push_lossy(&mut self, s: &[u8]) -> usize;
// The capacity of the internal buffer. The maximum string length this buffer can contain
@ -185,6 +188,7 @@ impl StringExt for PathBuf {
}
}
#[derive(Eq, Ord, PartialOrd)]
pub struct Utf8CString(String);
impl Default for Utf8CString {
@ -237,6 +241,16 @@ impl Utf8CStrBuf for Utf8CString {
}
}
fn resize(&mut self, len: usize) {
if len >= self.0.capacity() {
return;
}
unsafe {
*self.0.as_mut_ptr().add(len) = b'\0' as _;
self.set_len(len);
}
}
fn push_str(&mut self, s: &str) -> usize {
self.0.push_str(s);
self.0.nul_terminate();
@ -584,6 +598,14 @@ impl<const N: usize> FsPathBuf<N> {
self
}
pub fn resize(mut self, len: usize) -> Self {
unsafe {
self.0.as_bytes_mut()[len] = b'\0';
self.0.set_len(len)
};
self
}
pub fn join_fmt<T: Display>(mut self, name: T) -> Self {
self.0.write_fmt(format_args!("/{}", name)).ok();
self
@ -742,6 +764,14 @@ macro_rules! impl_str_buf_with_slice {
unsafe fn set_len(&mut self, len: usize) {
self.used = len;
}
fn resize(&mut self, len: usize) {
if len < self.capacity() {
unsafe {
self.buf[len] = b'\0';
self.set_len(len);
}
}
}
#[inline(always)]
fn push_str(&mut self, s: &str) -> usize {
utf8_cstr_buf_append(self, s.as_bytes())

View File

@ -3,7 +3,7 @@ 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 libc::{dirent, O_CLOEXEC, O_CREAT, O_DIRECTORY, O_RDONLY, O_TRUNC, O_WRONLY};
use std::ffi::CStr;
use std::fs::File;
use std::ops::Deref;
@ -195,6 +195,13 @@ impl Directory {
}
}
pub fn open_dir(&self, name: &CStr) -> io::Result<Directory> {
let dirp = unsafe {
libc::fdopendir(self.open_raw_fd(name, O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0)?).check_os_err()?
};
Ok(Directory { dirp })
}
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.

View File

@ -3,10 +3,7 @@ use crate::{
Utf8CStrBuf,
};
use bytemuck::{bytes_of, bytes_of_mut, Pod};
use libc::{
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 libc::{c_uint, makedev, mode_t, stat, EEXIST, ENOENT, F_OK, MS_BIND, MS_RDONLY, MS_REC, MS_REMOUNT, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY};
use mem::MaybeUninit;
use num_traits::AsPrimitive;
use std::cmp::min;
@ -185,6 +182,10 @@ impl FileAttr {
pub fn is_socket(&self) -> bool {
self.is(libc::S_IFSOCK)
}
pub fn is_whiteout(&self) -> bool {
self.is_char_device() && self.st.st_rdev == 0
}
}
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
@ -199,7 +200,7 @@ impl FsPath {
}
pub fn create(&self, flags: i32, mode: mode_t) -> io::Result<File> {
Ok(File::from(open_fd!(self, flags, mode)?))
Ok(File::from(open_fd!(self, flags | O_CREAT, mode)?))
}
pub fn exists(&self) -> bool {
@ -429,6 +430,18 @@ impl FsPath {
false
}
}
pub fn bind_mount_to(&self, path: &FsPath, ro: bool) -> io::Result<()> {
unsafe {
libc::mount(path.as_ptr(), self.as_ptr(), ptr::null(), MS_BIND | MS_REC, ptr::null()).as_os_err()?;
if ro {
libc::mount(ptr::null(), self.as_ptr(), ptr::null(), MS_REMOUNT | MS_BIND | MS_RDONLY, ptr::null()).as_os_err()?;
}
}
Ok(())
}
}
impl FsPathFollow {

View File

@ -55,7 +55,7 @@ impl<T> SilentResultExt<T> for Option<T> {
pub trait ResultExt<T> {
fn log(self) -> LoggedResult<T>;
fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
fn log_ok(self);
fn log_ok(self) -> Option<T>;
}
// Internal C++ bridging logging routines
@ -108,14 +108,14 @@ impl<T, R: Loggable<T>> ResultExt<T> for R {
}
#[cfg(not(debug_assertions))]
fn log_ok(self) {
self.log().ok();
fn log_ok(self) -> Option<T> {
self.log().ok()
}
#[track_caller]
#[cfg(debug_assertions)]
fn log_ok(self) {
self.do_log(LogLevel::Error, Some(Location::caller())).ok();
fn log_ok(self) -> Option<T> {
self.do_log(LogLevel::Error, Some(Location::caller())).ok()
}
}

View File

@ -4,6 +4,7 @@
#![feature(fn_traits)]
#![feature(unix_socket_ancillary_data)]
#![feature(unix_socket_peek)]
#![feature(btree_set_entry)]
#![allow(clippy::missing_safety_doc)]
use crate::ffi::SuRequest;
@ -21,6 +22,7 @@ use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::os::fd::FromRawFd;
use zygisk::zygisk_should_load_module;
use module::deploy_modules;
#[path = "../include/consts.rs"]
mod consts;
@ -33,6 +35,7 @@ mod resetprop;
mod socket;
mod su;
mod zygisk;
mod module;
#[allow(clippy::needless_lifetimes)]
#[cxx::bridge]
@ -239,6 +242,7 @@ pub mod ffi {
#[Self = MagiskD]
#[cxx_name = "Get"]
fn get() -> &'static MagiskD;
fn deploy_modules(module_list: &Vec<ModuleInfo>, zygisk_lib: &CxxString, magisk_path: &CxxString) -> bool;
}
unsafe extern "C++" {
#[allow(dead_code)]

View File

@ -9,284 +9,9 @@
#include <core.hpp>
#include <selinux.hpp>
#include "node.hpp"
using namespace std;
#define VLOGD(tag, from, to) LOGD("%-8s: %s <- %s\n", tag, to, from)
static int bind_mount(const char *reason, const char *from, const char *to) {
int ret = xmount(from, to, nullptr, MS_BIND | MS_REC, nullptr);
if (ret == 0)
VLOGD(reason, from, to);
return ret;
}
/*************************
* Node Tree Construction
*************************/
tmpfs_node::tmpfs_node(node_entry *node) : dir_node(node, this) {
if (!replace()) {
if (auto dir = open_dir(node_path().data())) {
set_exist(true);
for (dirent *entry; (entry = xreaddir(dir.get()));) {
// create a dummy inter_node to upgrade later
emplace<inter_node>(entry->d_name, entry);
}
}
}
for (auto it = children.begin(); it != children.end(); ++it) {
// Upgrade resting inter_node children to tmpfs_node
if (isa<inter_node>(it->second))
it = upgrade<tmpfs_node>(it);
}
}
bool dir_node::prepare() {
// If direct replace or not exist, mount ourselves as tmpfs
bool upgrade_to_tmpfs = replace() || !exist();
for (auto it = children.begin(); it != children.end();) {
// We also need to upgrade to tmpfs node if any child:
// - Target does not exist
// - Source or target is a symlink (since we cannot bind mount symlink) or whiteout
bool cannot_mnt;
if (struct stat st{}; lstat(it->second->node_path().data(), &st) != 0) {
// if it's a whiteout, we don't care if the target doesn't exist
cannot_mnt = !it->second->is_wht();
} else {
it->second->set_exist(true);
cannot_mnt = it->second->is_lnk() || S_ISLNK(st.st_mode) || it->second->is_wht();
}
if (cannot_mnt) {
if (_node_type > type_id<tmpfs_node>()) {
// Upgrade will fail, remove the unsupported child node
LOGW("Unable to add: %s, skipped\n", it->second->node_path().data());
delete it->second;
it = children.erase(it);
continue;
}
upgrade_to_tmpfs = true;
}
if (auto dn = dyn_cast<dir_node>(it->second)) {
if (replace()) {
// Propagate skip mirror state to all children
dn->set_replace(true);
}
if (dn->prepare()) {
// Upgrade child to tmpfs
it = upgrade<tmpfs_node>(it);
}
}
++it;
}
return upgrade_to_tmpfs;
}
void dir_node::collect_module_files(std::string_view module, int dfd) {
auto dir = xopen_dir(xopenat(dfd, name().data(), O_RDONLY | O_CLOEXEC));
if (!dir)
return;
for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (entry->d_name == ".replace"sv) {
set_replace(true);
continue;
}
if (entry->d_type == DT_DIR) {
inter_node *node;
if (auto it = children.find(entry->d_name); it == children.end()) {
node = emplace<inter_node>(entry->d_name, entry->d_name);
} else {
node = dyn_cast<inter_node>(it->second);
}
if (node) {
node->collect_module_files(module, dirfd(dir.get()));
}
} else {
if (entry->d_type == DT_CHR) {
struct stat st{};
int ret = fstatat(dirfd(dir.get()), entry->d_name, &st, AT_SYMLINK_NOFOLLOW);
if (ret == 0 && st.st_rdev == 0) {
// if the file is a whiteout, mark it as such
entry->d_type = DT_WHT;
}
}
emplace<module_node>(entry->d_name, module, entry);
}
}
}
/************************
* Mount Implementations
************************/
void node_entry::create_and_mount(const char *reason, const string &src, bool ro) {
const string dest = isa<tmpfs_node>(parent()) ? worker_path() : node_path();
if (is_lnk()) {
VLOGD("cp_link", src.data(), dest.data());
cp_afc(src.data(), dest.data());
} else {
if (is_dir())
xmkdir(dest.data(), 0);
else if (is_reg())
close(xopen(dest.data(), O_RDONLY | O_CREAT | O_CLOEXEC, 0));
else
return;
bind_mount(reason, src.data(), dest.data());
if (ro) {
xmount(nullptr, dest.data(), nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr);
}
}
}
void module_node::mount() {
if (is_wht()) {
VLOGD("delete", "null", node_path().data());
return;
}
std::string path{module.begin(), module.end()};
path += parent()->root()->prefix;
path += node_path();
string mnt_src = module_mnt + path;
{
string src = MODULEROOT "/" + path;
if (exist()) clone_attr(node_path().data(), src.data());
}
if (isa<tmpfs_node>(parent())) {
create_and_mount("module", mnt_src);
} else {
bind_mount("module", mnt_src.data(), node_path().data());
}
}
void tmpfs_node::mount() {
if (!is_dir()) {
create_and_mount("mirror", node_path());
return;
}
if (!isa<tmpfs_node>(parent())) {
auto worker_dir = worker_path();
mkdirs(worker_dir.data(), 0);
clone_attr(exist() ? node_path().data() : parent()->node_path().data(), worker_dir.data());
dir_node::mount();
bind_mount(replace() ? "replace" : "move", worker_dir.data(), node_path().data());
xmount(nullptr, node_path().data(), nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr);
} else {
const string dest = worker_path();
// We don't need another layer of tmpfs if parent is tmpfs
mkdir(dest.data(), 0);
clone_attr(exist() ? node_path().data() : parent()->worker_path().data(), dest.data());
dir_node::mount();
}
}
/****************
* Magisk Stuffs
****************/
class magisk_node : public node_entry {
public:
explicit magisk_node(const char *name) : node_entry(name, DT_REG, this) {}
explicit magisk_node(const char *name, const char *target)
: node_entry(name, DT_LNK, this), target(target) {}
void mount() override {
if (target) {
string dest = isa<tmpfs_node>(parent()) ? worker_path() : node_path();
VLOGD("create", target, dest.data());
xsymlink(target, dest.data());
} else {
string src = get_magisk_tmp() + "/"s + name();
if (access(src.data(), F_OK) == 0)
create_and_mount("magisk", src, true);
}
}
private:
const char *target = nullptr;
};
class zygisk_node : public node_entry {
public:
explicit zygisk_node(const char *name, bool is64bit)
: node_entry(name, DT_REG, this), is64bit(is64bit) {}
void mount() override {
#if defined(__LP64__)
const string src = get_magisk_tmp() + "/magisk"s + (is64bit ? "" : "32");
#else
const string src = get_magisk_tmp() + "/magisk"s;
(void) is64bit;
#endif
if (access(src.data(), F_OK))
return;
create_and_mount("zygisk", src, true);
}
private:
bool is64bit;
};
static void inject_magisk_bins(root_node *system) {
dir_node* bin = system->get_child<inter_node>("bin");
if (!bin) {
struct stat st{};
bin = system;
for (auto &item: split(getenv("PATH"), ":")) {
item.erase(0, item.starts_with("/system/") ? 8 : 1);
auto system_path = "/system/" + item;
if (stat(system_path.data(), &st) == 0 && st.st_mode & S_IXOTH) {
for (const auto &dir: split(item, "/")) {
auto node = bin->get_child<inter_node>(dir);
bin = node ? node : bin->emplace<inter_node>(dir, dir.data());
}
break;
}
}
}
// Insert binaries
bin->insert(new magisk_node("magisk"));
bin->insert(new magisk_node("magiskpolicy"));
// Also insert all applets to make sure no one can override it
for (int i = 0; applet_names[i]; ++i)
bin->insert(new magisk_node(applet_names[i], "./magisk"));
bin->insert(new magisk_node("supolicy", "./magiskpolicy"));
}
static void inject_zygisk_libs(root_node *system) {
if (access("/system/bin/linker", F_OK) == 0) {
auto lib = system->get_child<inter_node>("lib");
if (!lib) {
lib = new inter_node("lib");
system->insert(lib);
}
lib->insert(new zygisk_node(native_bridge.data(), false));
}
if (access("/system/bin/linker64", F_OK) == 0) {
auto lib64 = system->get_child<inter_node>("lib64");
if (!lib64) {
lib64 = new inter_node("lib64");
system->insert(lib64);
}
lib64->insert(new zygisk_node(native_bridge.data(), true));
}
}
static void load_modules(bool zygisk_enabled, const rust::Vec<ModuleInfo> &module_list) {
node_entry::module_mnt = get_magisk_tmp() + "/"s MODULEMNT "/";
auto root = make_unique<root_node>("");
auto system = new root_node("system");
root->insert(system);
char buf[4096];
LOGI("* Loading modules\n");
for (const auto &m : module_list) {
@ -300,26 +25,19 @@ static void load_modules(bool zygisk_enabled, const rust::Vec<ModuleInfo> &modul
// Do NOT go through property service as it could cause boot lock
load_prop_file(buf, true);
}
// Check whether skip mounting
strcpy(b, "skip_mount");
if (access(buf, F_OK) == 0)
continue;
// Double check whether the system folder exists
strcpy(b, "system");
if (access(buf, F_OK) != 0)
continue;
LOGI("%.*s: loading mount files\n", (int) m.name.size(), m.name.data());
b[-1] = '\0';
int fd = xopen(buf, O_RDONLY | O_CLOEXEC);
system->collect_module_files({ m.name.begin(), m.name.end() }, fd);
close(fd);
}
std::string magisk_path;
if (get_magisk_tmp() != "/sbin"sv || !str_contains(getenv("PATH") ?: "", "/sbin")) {
// Need to inject our binaries into /system/bin
inject_magisk_bins(system);
magisk_path = "/system/bin";
for (struct stat st{}; auto &item: split(getenv("PATH"), ":")) {
item.erase(0, item.starts_with("/system/") ? 8 : 1);
auto &&system_path = "/system/"s + item;
if (stat(system_path.data(), &st) == 0 && st.st_mode & S_IXOTH) {
magisk_path = std::move(system_path);
break;
}
}
}
if (zygisk_enabled) {
@ -335,23 +53,9 @@ static void load_modules(bool zygisk_enabled, const rust::Vec<ModuleInfo> &modul
if (get_prop("ro.maple.enable") == "1") {
set_prop("ro.maple.enable", "0");
}
inject_zygisk_libs(system);
}
if (!system->is_empty()) {
// Handle special read-only partitions
for (const char *part : { "/vendor", "/product", "/system_ext" }) {
struct stat st{};
if (lstat(part, &st) == 0 && S_ISDIR(st.st_mode)) {
if (auto old = system->extract(part + 1)) {
auto new_node = new root_node(old);
root->insert(new_node);
}
}
}
root->prepare();
root->mount();
}
deploy_modules(module_list, native_bridge, magisk_path);
}
/************************

548
native/src/core/module.rs Normal file
View File

@ -0,0 +1,548 @@
use crate::consts::{MODULEMNT, WORKERDIR};
use crate::ffi::{ModuleInfo, get_magisk_tmp};
use base::{
Directory, FsPath, FsPathBuf, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, Utf8CString,
WalkResult::Continue, clone_attr, cstr, debug, error, info, libc::O_RDONLY, path, warn,
};
use cxx::CxxString;
use std::collections::{
BTreeMap, BTreeSet, btree_map::Entry as MapEntry, btree_set::Entry as SetEntry,
};
enum Node {
Dir {
children: BTreeMap<Utf8CString, Node>,
exist: bool,
replace: bool,
},
File {
src: Option<Utf8CString>,
is_link: bool,
},
}
impl Node {
fn mount(&mut self, dest: &Utf8CStr) -> LoggedResult<()> {
if let Node::Dir { exist, .. } = self {
*exist = true;
}
self.do_mount(dest, false)
}
fn do_mount(&self, path: &Utf8CStr, parent_tmpfs: bool) -> LoggedResult<()> {
// todo: do mount
match &self {
Node::Dir {
children,
exist,
replace,
} => {
let mut worker = if !*exist || *replace {
let mut worker = get_magisk_tmp().to_owned();
worker.push_str("/");
worker.push_str(WORKERDIR);
let len = worker.len();
worker.push_str(path);
if *replace {
debug!("replace : {}", worker);
} else if parent_tmpfs {
debug!("mkdir : {}", worker);
} else {
debug!("tmpfs : {}", worker);
}
FsPath::from(&worker).mkdirs(0o000)?;
let mut buf = path.to_owned();
while !FsPath::from(&buf).exists() {
let mut tmp = Utf8CString::default();
FsPath::from(&buf).parent(&mut tmp);
buf = tmp.to_owned();
}
clone_attr(FsPath::from(&buf), FsPath::from(&worker)).ok();
worker.resize(len);
Some(worker)
} else {
None
};
let mut buf = path.to_owned();
let len = buf.len();
for (name, child) in children {
buf.push_str("/");
buf.push_str(name);
child.do_mount(&buf, worker.is_some())?;
buf.resize(len);
}
if !*replace
&& let Some(worker) = &mut worker
&& let Ok(mut dest) = Directory::open(&buf)
{
let len = worker.len();
dest.pre_order_walk(|f| {
if let Ok(name) = Utf8CStr::from_cstr(f.name())
&& !children.contains_key(&name.to_owned())
{
f.path(&mut buf)?;
worker.push_str(&buf);
if f.is_dir() {
debug!("mkdir : {}", &worker);
let worker = FsPath::from(worker);
worker.mkdir(0o00)?;
clone_attr(FsPath::from(&buf), worker)?;
} else if f.is_symlink() {
debug!("cp_link : {} <- {}", &worker, buf);
let attr = f.get_attr()?;
let mut link = Utf8CString::default();
f.read_link(&mut link)?;
let worker = FsPath::from(worker);
FsPath::from(&link).symlink_to(worker)?;
worker.set_attr(&attr)?;
} else {
debug!("mirror : {} <- {}", &worker, buf);
let worker = FsPath::from(worker);
worker.create(O_RDONLY, 0o000)?;
worker.bind_mount_to(FsPath::from(&buf), true)?;
}
worker.resize(len);
};
Ok(Continue)
})?;
}
if !parent_tmpfs && let Some(mut worker) = worker {
worker.push_str(path);
debug!("move : {} <- {}", path, worker);
FsPath::from(path).bind_mount_to(FsPath::from(&worker), true)?;
}
}
Node::File { src, is_link } => match (src, is_link) {
(None, _) => {
debug!("delete : {}", path);
}
(Some(src), false) => {
if parent_tmpfs {
let mut worker = get_magisk_tmp().to_owned();
worker.push_str("/");
worker.push_str(WORKERDIR);
worker.push_str(path);
debug!("module : {} <- {}", worker, src);
let worker = FsPath::from(&worker);
worker.create(O_RDONLY, 0o000)?;
worker.bind_mount_to(FsPath::from(src), true)?;
} else {
debug!("module : {} <- {}", path, src);
let src = FsPath::from(src);
let path = FsPath::from(path);
clone_attr(path, src).ok();
path.bind_mount_to(src, true)?;
}
}
(Some(src), true) => {
if parent_tmpfs {
let mut worker = get_magisk_tmp().to_owned();
worker.push_str("/");
worker.push_str(WORKERDIR);
worker.push_str(path);
debug!("symlink : {} <- {}", worker, src);
FsPath::from(src).symlink_to(FsPath::from(&worker))?;
} else {
unreachable!()
}
}
},
}
Ok(())
}
fn extract(&mut self, path: &Utf8CStr) -> Option<Node> {
let path = path.to_owned();
match self {
Node::Dir { children, .. } => children.remove_entry(&path).map(|(_, v)| v),
Node::File { .. } => None,
}
}
}
enum ModuleEntry {
Dir {
name: Utf8CString,
iter: ModuleIterator,
},
File {
name: Utf8CString,
path: Option<Utf8CString>,
is_link: bool,
},
}
enum CustomModule {
Dir {
children: BTreeMap<Utf8CString, CustomModule>,
},
File {
path: Utf8CString,
is_link: bool,
},
}
trait CustomModuleExt {
fn insert_entry(&mut self, path: &Utf8CStr, target: &Utf8CStr, is_link: bool);
}
impl CustomModuleExt for BTreeMap<Utf8CString, CustomModule> {
fn insert_entry(&mut self, path: &Utf8CStr, target: &Utf8CStr, is_link: bool) {
match path.split_once('/') {
Some((dir, rest)) => {
if dir.is_empty() {
return self.insert_entry(&Utf8CString::from(rest.to_owned()), target, is_link);
}
match self
.entry(Utf8CString::from(dir.to_owned()))
.or_insert_with(|| CustomModule::Dir {
children: BTreeMap::new(),
}) {
CustomModule::Dir { children } => {
children.insert_entry(&Utf8CString::from(rest.to_owned()), target, is_link);
}
CustomModule::File { .. } => {
warn!("Duplicate entry: {}", dir);
}
}
}
_ => match self.entry(path.to_owned()) {
MapEntry::Vacant(v) => {
v.insert(CustomModule::File {
path: target.to_owned(),
is_link,
});
}
_ => {
warn!("Duplicate entry: {}", path);
}
},
}
}
}
trait DirExt {
fn open_dir(&mut self, name: &Utf8CStr) -> Self;
}
impl DirExt for Vec<Directory> {
fn open_dir(&mut self, name: &Utf8CStr) -> Self {
self.iter()
.filter_map(|d| d.open_dir(name.as_cstr()).ok())
.collect()
}
}
impl DirExt for Vec<CustomModule> {
fn open_dir(&mut self, name: &Utf8CStr) -> Self {
let name = name.to_owned();
self.iter_mut()
.filter_map(|d| {
if let CustomModule::Dir { children } = d {
children.remove(&name)
} else {
None
}
})
.collect()
}
}
struct ModuleIterator {
modules: Vec<Directory>,
customs: Vec<CustomModule>,
collected: BTreeSet<Utf8CString>,
}
impl Iterator for ModuleIterator {
type Item = ModuleEntry;
fn next(&mut self) -> Option<Self::Item> {
while let Some(e) = self.modules.last_mut().and_then(|d| d.read().log_ok()) {
let res: Option<Self::Item> = try {
let e = e?;
let name = Utf8CStr::from_cstr(e.name()).log_ok()?;
if let SetEntry::Vacant(v) = self.collected.entry(name.to_owned()) {
let mut path = Utf8CString::default();
let attr = e.get_attr().log_ok()?;
if attr.is_symlink() {
e.read_link(&mut path).log_ok()?;
} else {
e.path(&mut path).log_ok()?;
}
let entry = if e.is_dir() {
ModuleEntry::Dir {
name: name.to_owned(),
iter: ModuleIterator {
modules: self.modules.open_dir(v.get()),
customs: self.customs.open_dir(v.get()),
collected: BTreeSet::new(),
},
}
} else {
ModuleEntry::File {
name: name.to_owned(),
path: Some(path).take_if(|_| !attr.is_whiteout()),
is_link: attr.is_symlink(),
}
};
v.insert();
entry
} else if !e.is_dir() {
warn!("Duplicate entry: {}", name);
None?
} else {
None?
}
};
if res.is_some() {
return res;
}
self.modules.pop();
}
while let Some(CustomModule::Dir { children }) = self.customs.last_mut() {
let res: Option<Self::Item> = try {
let e = children.first_entry()?;
let name: &Utf8CStr = e.key();
if let SetEntry::Vacant(v) = self.collected.entry(name.to_owned()) {
let entry = match e.get() {
CustomModule::Dir { .. } => ModuleEntry::Dir {
name: name.to_owned(),
iter: ModuleIterator {
modules: vec![],
customs: self.customs.open_dir(v.get()),
collected: BTreeSet::new(),
},
},
CustomModule::File { .. } => {
if let (name, CustomModule::File { path, is_link }) = e.remove_entry() {
ModuleEntry::File {
name: name.to_owned(),
path: Some(path),
is_link,
}
} else {
unreachable!()
}
}
};
v.insert();
entry
} else if matches!(e.get(), CustomModule::File { .. }) {
error!("Duplicate entry: {}", name);
None?
} else {
None?
}
};
if res.is_some() {
return res;
}
self.customs.pop();
}
None
}
}
struct ModuleList {
modules: Vec<Directory>,
customs: Vec<CustomModule>,
}
impl ModuleList {
fn from_module_infos(module_infos: &[ModuleInfo]) -> Self {
let mut path = FsPathBuf::default().join(get_magisk_tmp()).join(MODULEMNT);
let len = path.len();
let mut modules = Vec::new();
for info in module_infos {
path = path.join(&info.name);
if let Ok(m) = Directory::open(&path) {
if !m.contains_path(c"skip_mount") && m.contains_path(c"system") {
info!("{}: loading mount files", info.name);
modules.push(m);
}
}
path = path.resize(len);
}
Self {
modules,
customs: Vec::new(),
}
}
fn inject_zygisk_bins(&mut self, zygisk_lib: &str) {
self.customs.push(CustomModule::Dir {
children: {
let mut children = BTreeMap::new();
let mut path = FsPathBuf::default();
#[cfg(target_pointer_width = "64")]
{
if path!("/system/bin/linker").exists() {
path = path.join("/system/lib").join(zygisk_lib);
children.insert_entry(
&path,
&FsPathBuf::default().join(get_magisk_tmp()).join("magisk32"),
false,
);
}
if path!("/system/bin/linker64").exists() {
path = path.join("/system/lib64").join(zygisk_lib);
children.insert_entry(
&path,
&FsPathBuf::default().join(get_magisk_tmp()).join("magisk"),
false,
);
}
}
#[cfg(target_pointer_width = "32")]
{
path = path.join("/system/lib").join(zygisk_lib);
if path!("/system/bin/linker").exists() {
children.insert_entry(
&path,
&FsPathBuf::default().join(get_magisk_tmp()).join("magisk"),
false,
);
}
}
children
},
});
}
fn inject_magisk_bins(&mut self, magisk_path: &str) {
let mut path = Utf8CString::default();
path.push_str(magisk_path);
path.push_str("/");
let len = path.len();
self.customs.push(CustomModule::Dir {
children: {
let mut children = BTreeMap::new();
path.push_str("magisk");
children.insert_entry(
&path,
&FsPathBuf::default().join(get_magisk_tmp()).join("magisk"),
false,
);
path.resize(len);
path.push_str("magiskpolicy");
children.insert_entry(
&path,
&FsPathBuf::default()
.join(get_magisk_tmp())
.join("magiskpolicy"),
false,
);
path.resize(len);
path.push_str("su");
children.insert_entry(&path, cstr!("./magisk"), true);
path.resize(len);
path.push_str("resetprop");
children.insert_entry(&path, cstr!("./magisk"), true);
path.resize(len);
path.push_str("supolicy");
children.insert_entry(&path, cstr!("./magiskpolicy"), true);
children
},
})
}
fn iter(&mut self, name: &Utf8CStr) -> ModuleIterator {
ModuleIterator {
modules: self.modules.open_dir(name),
customs: self.customs.open_dir(name),
collected: BTreeSet::new(),
}
}
}
pub fn deploy_modules(
module_list: &[ModuleInfo],
zygisk_lib: &CxxString,
magisk_path: &CxxString,
) -> bool {
let res: LoggedResult<()> = try {
let mut modules = ModuleList::from_module_infos(module_list);
if !magisk_path.is_empty() {
modules.inject_magisk_bins(&magisk_path.to_string_lossy());
}
if zygisk_lib != "0" {
modules.inject_zygisk_bins(&zygisk_lib.to_string_lossy());
}
let mut system_node = tree_module(path!("/system"), &mut modules.iter(cstr!("system")))?;
for dir in [cstr!("product"), cstr!("vendor"), cstr!("system_ext")] {
system_node.extract(dir).and_then(|mut n| {
let mut dest = cstr!("/").to_owned();
dest.push_str(dir);
n.mount(&dest).log_ok()
});
}
system_node.mount(cstr!("/system"))?;
};
res.log().is_ok()
}
fn tree_module(base: &FsPath, dir: &mut ModuleIterator) -> LoggedResult<Node> {
let mut children = BTreeMap::new();
let mut target_path = FsPathBuf::default().join(base);
let mut replace = false;
let mut exist = target_path.exists();
debug!("tree on {}", target_path);
let parent_len = target_path.len();
for e in dir {
match e {
ModuleEntry::Dir { name, mut iter } => {
target_path = target_path.join(&name);
exist = exist && target_path.exists();
children.insert(name, tree_module(&target_path, &mut iter)?);
}
ModuleEntry::File {
name,
path,
is_link,
} => {
if &name == ".replace" {
replace = true;
continue;
}
target_path = target_path.join(&name);
if !matches!(
(
is_link,
&path,
target_path.get_attr().map(|attr| attr.is_symlink())
),
(false, Some(_), Ok(false))
) {
exist = false;
}
children.insert(name, Node::File { src: path, is_link });
}
}
target_path = target_path.resize(parent_len);
}
Ok(Node::Dir {
children,
exist,
replace,
})
}

View File

@ -1,318 +0,0 @@
#pragma once
#include <sys/mount.h>
#include <map>
using namespace std;
#define TYPE_INTER (1 << 0) /* intermediate node */
#define TYPE_TMPFS (1 << 1) /* replace with tmpfs */
#define TYPE_MODULE (1 << 2) /* mount from module */
#define TYPE_ROOT (1 << 3) /* partition root */
#define TYPE_CUSTOM (1 << 4) /* custom node type overrides all */
#define TYPE_DIR (TYPE_INTER|TYPE_TMPFS|TYPE_ROOT)
class node_entry;
class dir_node;
class inter_node;
class tmpfs_node;
class module_node;
class root_node;
// Poor man's dynamic cast without RTTI
template<class T> static bool isa(node_entry *node);
template<class T> static T *dyn_cast(node_entry *node);
template<class T> uint8_t type_id() { return TYPE_CUSTOM; }
template<> uint8_t type_id<dir_node>() { return TYPE_DIR; }
template<> uint8_t type_id<inter_node>() { return TYPE_INTER; }
template<> uint8_t type_id<tmpfs_node>() { return TYPE_TMPFS; }
template<> uint8_t type_id<module_node>() { return TYPE_MODULE; }
template<> uint8_t type_id<root_node>() { return TYPE_ROOT; }
class node_entry {
public:
virtual ~node_entry() = default;
// Node info
bool is_dir() const { return file_type() == DT_DIR; }
bool is_lnk() const { return file_type() == DT_LNK; }
bool is_reg() const { return file_type() == DT_REG; }
bool is_wht() const { return file_type() == DT_WHT; }
const string &name() const { return _name; }
dir_node *parent() const { return _parent; }
// Don't call the following two functions before prepare
const string &node_path();
const string worker_path();
virtual void mount() = 0;
inline static string module_mnt;
protected:
template<class T>
node_entry(const char *name, uint8_t file_type, T*)
: _name(name), _file_type(file_type & 15), _node_type(type_id<T>()) {}
template<class T>
explicit node_entry(T*) : _file_type(0), _node_type(type_id<T>()) {}
virtual void consume(node_entry *other) {
_name.swap(other->_name);
_file_type = other->_file_type;
_parent = other->_parent;
delete other;
}
void create_and_mount(const char *reason, const string &src, bool ro=false);
// Use bit 7 of _file_type for exist status
bool exist() const { return static_cast<bool>(_file_type & (1 << 7)); }
void set_exist(bool b) { if (b) _file_type |= (1 << 7); else _file_type &= ~(1 << 7); }
private:
friend class dir_node;
template<class T>
friend bool isa(node_entry *node);
uint8_t file_type() const { return static_cast<uint8_t>(_file_type & 15); }
// Node properties
string _name;
dir_node *_parent = nullptr;
// Cache, it should only be used within prepare
string _node_path;
uint8_t _file_type;
const uint8_t _node_type;
};
class dir_node : public node_entry {
public:
using map_type = map<string_view, node_entry *>;
using iterator = map_type::iterator;
~dir_node() override {
for (auto &it : children)
delete it.second;
children.clear();
}
/**************
* Entrypoints
**************/
// Traverse through module directories to generate a tree of module files
void collect_module_files(std::string_view module, int dfd);
// Traverse through the real filesystem and prepare the tree for magic mount.
// Return true to indicate that this node needs to be upgraded to tmpfs_node.
bool prepare();
// Default directory mount logic
void mount() override {
for (auto &pair : children)
pair.second->mount();
}
/***************
* Tree Methods
***************/
bool is_empty() { return children.empty(); }
template<class T>
T *get_child(string_view name) { return iterator_to_node<T>(children.find(name)); }
root_node *root() {
if (!_root)
_root = _parent->root();
return _root;
}
// Return child with name or nullptr
node_entry *extract(string_view name) {
auto it = children.find(name);
if (it != children.end()) {
auto ret = it->second;
children.erase(it);
return ret;
}
return nullptr;
}
// Return false if rejected
bool insert(node_entry *node) {
auto fn = [=](auto) { return node; };
return node && iterator_to_node(insert(node->_name, node->_node_type, fn));
}
// Return inserted node or null if rejected
template<class T, class ...Args>
T *emplace(string_view name, Args &&...args) {
auto fn = [&](auto) { return new T(std::forward<Args>(args)...); };
return iterator_to_node<T>(insert(name, type_id<T>(), fn));
}
// Return upgraded node or null if rejected
template<class T, class ...Args>
T *upgrade(string_view name, Args &...args) {
return iterator_to_node<T>(upgrade<T>(children.find(name), args...));
}
protected:
template<class T>
dir_node(const char *name, T *self) : node_entry(name, DT_DIR, self) {
if constexpr (std::is_same_v<T, root_node>)
_root = self;
}
template<class T>
dir_node(dirent *entry, T *self) : node_entry(entry->d_name, entry->d_type, self) {
if constexpr (std::is_same_v<T, root_node>)
_root = self;
}
template<class T>
dir_node(node_entry *node, T *self) : node_entry(self) {
if constexpr (std::is_same_v<T, root_node>)
_root = self;
dir_node::consume(node);
}
void consume(node_entry *other) override {
if (auto o = dyn_cast<dir_node>(other)) {
children.merge(o->children);
for (auto &pair : children)
pair.second->_parent = this;
}
node_entry::consume(other);
}
// Use bit 6 of _file_type
// Skip binding mirror for this directory
bool replace() const { return static_cast<bool>(_file_type & (1 << 6)); }
void set_replace(bool b) { if (b) _file_type |= (1 << 6); else _file_type &= ~(1 << 6); }
template<class T = node_entry>
T *iterator_to_node(iterator it) {
return static_cast<T*>(it == children.end() ? nullptr : it->second);
}
template<typename Builder>
iterator insert(string_view name, uint8_t type, const Builder &builder) {
return insert_at(children.find(name), type, builder);
}
// Emplace insert a new node, or upgrade if the requested type has a higher rank.
// Return iterator to the new/upgraded node, or end() if insertion is rejected.
// fn is the node builder function. Signature: (node_entry *&) -> node_entry *
// fn gets a reference to the existing node pointer and returns a new node object.
// Input is null when there is no existing node. If returns null, the insertion is rejected.
// If fn consumes the input, it should set the reference to null.
template<typename Builder>
iterator insert_at(iterator it, uint8_t type, const Builder &builder) {
node_entry *node = nullptr;
if (it != children.end()) {
// Upgrade existing node only if higher rank
if (it->second->_node_type < type) {
node = builder(it->second);
if (!node)
return children.end();
if (it->second)
node->consume(it->second);
it = children.erase(it);
// Minor optimization to make insert O(1) by using hint
if (it == children.begin())
it = children.emplace(node->_name, node).first;
else
it = children.emplace_hint(--it, node->_name, node);
} else {
return children.end();
}
} else {
node = builder(node);
if (!node)
return children.end();
node->_parent = this;
it = children.emplace(node->_name, node).first;
}
return it;
}
template<class T, class ...Args>
iterator upgrade(iterator it, Args &&...args) {
return insert_at(it, type_id<T>(), [&](node_entry *&ex) -> node_entry * {
if (!ex) return nullptr;
auto node = new T(ex, std::forward<Args>(args)...);
ex = nullptr;
return node;
});
}
// dir nodes host children
map_type children;
private:
// Root node lookup cache
root_node *_root = nullptr;
};
class root_node : public dir_node {
public:
explicit root_node(const char *name) : dir_node(name, this), prefix("") {
set_exist(true);
}
explicit root_node(node_entry *node) : dir_node(node, this), prefix("/system") {
set_exist(true);
}
const char * const prefix;
};
class inter_node : public dir_node {
public:
inter_node(const char *name) : dir_node(name, this) {}
inter_node(dirent *entry) : dir_node(entry, this) {}
};
class module_node : public node_entry {
public:
module_node(std::string_view module, dirent *entry)
: node_entry(entry->d_name, entry->d_type, this), module(module) {}
module_node(node_entry *node, std::string_view module) : node_entry(this), module(module) {
node_entry::consume(node);
}
void mount() override;
private:
std::string_view module;
};
// Don't create tmpfs_node before prepare
class tmpfs_node : public dir_node {
public:
explicit tmpfs_node(node_entry *node);
void mount() override;
};
template<class T>
static bool isa(node_entry *node) {
return node && (node->_node_type & type_id<T>());
}
template<class T>
static T *dyn_cast(node_entry *node) {
return isa<T>(node) ? static_cast<T*>(node) : nullptr;
}
const string &node_entry::node_path() {
if (_parent && _node_path.empty())
_node_path = _parent->node_path() + '/' + _name;
return _node_path;
}
const string node_entry::worker_path() {
return get_magisk_tmp() + "/"s WORKERDIR + node_path();
}