mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-26 06:20:45 +00:00
Refactor module mount load in rust
This commit is contained in:
parent
1f162b819d
commit
0905adcd64
@ -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())
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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
548
native/src/core/module.rs
Normal 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,
|
||||
})
|
||||
}
|
@ -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();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user