mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-05-18 12:48:29 +00:00
Migrate module mounting to Rust
This commit is contained in:
parent
8b7fb6cdde
commit
2df9fd9a8a
@ -68,7 +68,7 @@ pub mod buf {
|
||||
|
||||
// Trait definitions
|
||||
|
||||
pub trait Utf8CStrBuf: Write + AsRef<Utf8CStr> + Deref<Target = Utf8CStr> {
|
||||
pub trait Utf8CStrBuf: Display + Write + AsRef<Utf8CStr> + Deref<Target = Utf8CStr> {
|
||||
// The length of the string without the terminating null character.
|
||||
// assert_true(len <= capacity - 1)
|
||||
fn len(&self) -> usize;
|
||||
|
@ -182,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";
|
||||
|
@ -3,13 +3,14 @@ use libc::c_ulong;
|
||||
use std::ptr;
|
||||
|
||||
impl Utf8CStr {
|
||||
pub fn bind_mount_to<'a>(&'a self, path: &'a Utf8CStr) -> OsResult<'a, ()> {
|
||||
pub fn bind_mount_to<'a>(&'a self, path: &'a Utf8CStr, rec: bool) -> OsResult<'a, ()> {
|
||||
let flag = if rec { libc::MS_REC } else { 0 };
|
||||
unsafe {
|
||||
libc::mount(
|
||||
self.as_ptr(),
|
||||
path.as_ptr(),
|
||||
ptr::null(),
|
||||
libc::MS_BIND,
|
||||
libc::MS_BIND | flag,
|
||||
ptr::null(),
|
||||
)
|
||||
.check_os_err("bind_mount", Some(self), Some(path))
|
||||
|
@ -14,6 +14,7 @@ use cxx::{ExternType, type_id};
|
||||
use daemon::{MagiskD, daemon_entry};
|
||||
use derive::Decodable;
|
||||
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
|
||||
use module::load_modules;
|
||||
use mount::{find_preinit_device, revert_unmount};
|
||||
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
||||
use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon};
|
||||
@ -30,6 +31,7 @@ mod consts;
|
||||
mod daemon;
|
||||
mod db;
|
||||
mod logging;
|
||||
mod module;
|
||||
mod mount;
|
||||
mod package;
|
||||
mod resetprop;
|
||||
@ -146,6 +148,7 @@ pub mod ffi {
|
||||
value: *const c_char,
|
||||
serial: u32,
|
||||
);
|
||||
unsafe fn load_prop_file(filename: *const c_char, skip_svc: bool);
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
@ -220,6 +223,8 @@ pub mod ffi {
|
||||
|
||||
#[namespace = "rust"]
|
||||
fn daemon_entry();
|
||||
#[namespace = "rust"]
|
||||
fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str);
|
||||
}
|
||||
|
||||
// Default constructors
|
||||
@ -282,3 +287,7 @@ pub fn get_prop(name: &Utf8CStr, persist: bool) -> String {
|
||||
pub fn set_prop(name: &Utf8CStr, value: &Utf8CStr, skip_svc: bool) -> bool {
|
||||
unsafe { ffi::set_prop_rs(name.as_ptr(), value.as_ptr(), skip_svc) == 0 }
|
||||
}
|
||||
|
||||
pub fn load_prop_file(filename: &Utf8CStr, skip_svc: bool) {
|
||||
unsafe { ffi::load_prop_file(filename.as_ptr(), skip_svc) };
|
||||
}
|
||||
|
@ -1,358 +1,12 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/mount.h>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <consts.hpp>
|
||||
#include <core.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) {
|
||||
char *b = buf + ssprintf(buf, sizeof(buf), "%s/" MODULEMNT "/%.*s/",
|
||||
get_magisk_tmp(), (int) m.name.size(), m.name.data());
|
||||
|
||||
// Read props
|
||||
strcpy(b, "system.prop");
|
||||
if (access(buf, F_OK) == 0) {
|
||||
LOGI("%.*s: loading [system.prop]\n", (int) m.name.size(), m.name.data());
|
||||
// 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);
|
||||
}
|
||||
if (get_magisk_tmp() != "/sbin"sv || !str_contains(getenv("PATH") ?: "", "/sbin")) {
|
||||
// Need to inject our binaries into /system/bin
|
||||
inject_magisk_bins(system);
|
||||
}
|
||||
|
||||
if (zygisk_enabled) {
|
||||
string native_bridge_orig = get_prop(NBPROP);
|
||||
if (native_bridge_orig.empty()) {
|
||||
native_bridge_orig = "0";
|
||||
}
|
||||
native_bridge = native_bridge_orig != "0" ? ZYGISKLDR + native_bridge_orig : ZYGISKLDR;
|
||||
set_prop(NBPROP, native_bridge.data());
|
||||
// Weather Huawei's Maple compiler is enabled.
|
||||
// If so, system server will be created by a special Zygote which ignores the native bridge
|
||||
// and make system server out of our control. Avoid it by disabling.
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/************************
|
||||
* Filesystem operations
|
||||
************************/
|
||||
@ -485,7 +139,22 @@ rust::Vec<ModuleInfo> MagiskD::handle_modules() const noexcept {
|
||||
exec_module_scripts("post-fs-data", collect_modules(zygisk, false));
|
||||
// Recollect modules (module scripts could remove itself)
|
||||
auto list = collect_modules(zygisk, true);
|
||||
load_modules(zygisk, list);
|
||||
|
||||
if (zygisk) {
|
||||
string native_bridge_orig = get_prop(NBPROP);
|
||||
if (native_bridge_orig.empty()) {
|
||||
native_bridge_orig = "0";
|
||||
}
|
||||
native_bridge = native_bridge_orig != "0" ? ZYGISKLDR + native_bridge_orig : ZYGISKLDR;
|
||||
set_prop(NBPROP, native_bridge.data());
|
||||
// Whether Huawei's Maple compiler is enabled.
|
||||
// If so, system server will be created by a special Zygote which ignores the native bridge
|
||||
// and make system server out of our control. Avoid it by disabling.
|
||||
if (get_prop("ro.maple.enable") == "1") {
|
||||
set_prop("ro.maple.enable", "0");
|
||||
}
|
||||
}
|
||||
rust::load_modules(rust::Slice<const ModuleInfo>(list), zygisk ? native_bridge : "");
|
||||
return list;
|
||||
}
|
||||
|
||||
|
572
native/src/core/module.rs
Normal file
572
native/src/core/module.rs
Normal file
@ -0,0 +1,572 @@
|
||||
use crate::consts::{MODULEMNT, WORKERDIR};
|
||||
use crate::ffi::{ModuleInfo, get_magisk_tmp};
|
||||
use crate::load_prop_file;
|
||||
use base::{
|
||||
Directory, FsPathBuilder, LoggedResult, OsResultStatic, ResultExt, Utf8CStr, Utf8CStrBuf,
|
||||
Utf8CString, clone_attr, cstr, debug, error, info, libc, warn,
|
||||
};
|
||||
use libc::{MS_RDONLY, O_CLOEXEC, O_CREAT, O_RDONLY};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Component, Path};
|
||||
|
||||
const SECONDARY_READ_ONLY_PARTITIONS: [&Utf8CStr; 3] =
|
||||
[cstr!("/vendor"), cstr!("/product"), cstr!("/system_ext")];
|
||||
|
||||
type FsNodeMap = BTreeMap<String, FsNode>;
|
||||
|
||||
macro_rules! module_log {
|
||||
($($args:tt)+) => {
|
||||
debug!("{:8}: {} <- {}", $($args)+)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn bind_mount(reason: &str, src: &Utf8CStr, dest: &Utf8CStr, rec: bool) -> OsResultStatic<()> {
|
||||
module_log!(reason, dest, src);
|
||||
src.bind_mount_to(dest, rec)?;
|
||||
dest.remount_mount_point_flags(MS_RDONLY)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_dummy(reason: &str, src: &Utf8CStr, dest: &Utf8CStr, is_dir: bool) -> OsResultStatic<()> {
|
||||
if is_dir {
|
||||
dest.mkdir(0o000)?;
|
||||
} else {
|
||||
dest.create(O_CREAT | O_RDONLY | O_CLOEXEC, 0o000)?;
|
||||
}
|
||||
bind_mount(reason, src, dest, false)
|
||||
}
|
||||
|
||||
// File paths that act like a stack, popping out the last element
|
||||
// automatically when out of scope. Using Rust's lifetime mechanism,
|
||||
// we can ensure the buffer will never be incorrectly copied or modified.
|
||||
// After calling append or clone, the mutable reference's lifetime is
|
||||
// "transferred" to the returned object, and the compiler will guarantee
|
||||
// that the original mutable reference can only be reused if and only if
|
||||
// the newly created instance is destroyed.
|
||||
struct PathTracker<'a> {
|
||||
real: &'a mut dyn Utf8CStrBuf,
|
||||
tmp: &'a mut dyn Utf8CStrBuf,
|
||||
real_len: usize,
|
||||
tmp_len: usize,
|
||||
}
|
||||
|
||||
impl PathTracker<'_> {
|
||||
fn from<'a>(real: &'a mut dyn Utf8CStrBuf, tmp: &'a mut dyn Utf8CStrBuf) -> PathTracker<'a> {
|
||||
let real_len = real.len();
|
||||
let tmp_len = tmp.len();
|
||||
PathTracker {
|
||||
real,
|
||||
tmp,
|
||||
real_len,
|
||||
tmp_len,
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, name: &str) -> PathTracker {
|
||||
let real_len = self.real.len();
|
||||
let tmp_len = self.tmp.len();
|
||||
self.real.append_path(name);
|
||||
self.tmp.append_path(name);
|
||||
PathTracker {
|
||||
real: self.real,
|
||||
tmp: self.tmp,
|
||||
real_len,
|
||||
tmp_len,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone(&mut self) -> PathTracker {
|
||||
Self::from(self.real, self.tmp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PathTracker<'_> {
|
||||
// Revert back to the original state after finish using the buffer
|
||||
fn drop(&mut self) {
|
||||
self.real.truncate(self.real_len);
|
||||
self.tmp.truncate(self.tmp_len);
|
||||
}
|
||||
}
|
||||
|
||||
enum FsNode {
|
||||
Directory { children: FsNodeMap },
|
||||
File { src: Utf8CString },
|
||||
Symlink { target: Utf8CString },
|
||||
Whiteout,
|
||||
}
|
||||
|
||||
impl FsNode {
|
||||
fn new_dir() -> FsNode {
|
||||
FsNode::Directory {
|
||||
children: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_from_path(&mut self, path: &mut dyn Utf8CStrBuf) -> LoggedResult<()> {
|
||||
let FsNode::Directory { children } = self else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut dir = Directory::open(path)?;
|
||||
let path_len = path.len();
|
||||
|
||||
while let Some(entry) = dir.read()? {
|
||||
path.truncate(path_len);
|
||||
path.append_path(entry.name());
|
||||
if entry.is_dir() {
|
||||
let node = children
|
||||
.entry(entry.name().to_string())
|
||||
.or_insert_with(FsNode::new_dir);
|
||||
node.build_from_path(path)?;
|
||||
} else if entry.is_symlink() {
|
||||
let mut link = cstr::buf::default();
|
||||
path.read_link(&mut link)?;
|
||||
children
|
||||
.entry(entry.name().to_string())
|
||||
.or_insert_with(|| FsNode::Symlink {
|
||||
target: link.to_owned(),
|
||||
});
|
||||
} else {
|
||||
if entry.is_char_device() {
|
||||
let attr = path.get_attr()?;
|
||||
if attr.is_whiteout() {
|
||||
children
|
||||
.entry(entry.name().to_string())
|
||||
.or_insert_with(|| FsNode::Whiteout);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
children
|
||||
.entry(entry.name().to_string())
|
||||
.or_insert_with(|| FsNode::File {
|
||||
src: path.to_owned(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// The parent node has to be tmpfs if:
|
||||
// - Target does not exist
|
||||
// - Source or target is a symlink (since we cannot bind mount symlink)
|
||||
// - Source is whiteout (used for removal)
|
||||
fn parent_should_be_tmpfs(&self, target_path: &Utf8CStr) -> bool {
|
||||
match self {
|
||||
FsNode::Directory { .. } | FsNode::File { .. } => {
|
||||
if let Ok(attr) = target_path.get_attr() {
|
||||
attr.is_symlink()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
FsNode::Symlink { .. } | FsNode::Whiteout => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn children(&mut self) -> Option<&mut FsNodeMap> {
|
||||
match self {
|
||||
FsNode::Directory { children } => Some(children),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn commit(&mut self, mut path: PathTracker, is_root_dir: bool) -> LoggedResult<()> {
|
||||
match self {
|
||||
FsNode::Directory { children } => {
|
||||
let mut is_tmpfs = false;
|
||||
|
||||
// First determine whether tmpfs is required
|
||||
children.retain(|name, node| {
|
||||
if name == ".replace" {
|
||||
return if is_root_dir {
|
||||
warn!("Unable to replace '{}', ignore request", path.real);
|
||||
false
|
||||
} else {
|
||||
is_tmpfs = true;
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
let path = path.append(name);
|
||||
if node.parent_should_be_tmpfs(path.real) {
|
||||
if is_root_dir {
|
||||
// Ignore the unsupported child node
|
||||
warn!("Unable to add '{}', skipped", path.real);
|
||||
return false;
|
||||
}
|
||||
is_tmpfs = true;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
if is_tmpfs {
|
||||
self.commit_tmpfs(path.clone())?;
|
||||
// Transitioning from non-tmpfs to tmpfs, we need to actually mount the
|
||||
// worker dir to dest after all children are committed.
|
||||
bind_mount("move", path.tmp, path.real, true)?;
|
||||
} else {
|
||||
for (name, node) in children {
|
||||
let path = path.append(name);
|
||||
node.commit(path, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
FsNode::File { src } => {
|
||||
clone_attr(path.real, src)?;
|
||||
bind_mount("mount", src, path.real, false)?;
|
||||
}
|
||||
FsNode::Symlink { .. } | FsNode::Whiteout => {
|
||||
error!("Unable to handle '{}': parent should be tmpfs", path.real);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit_tmpfs(&mut self, mut path: PathTracker) -> LoggedResult<()> {
|
||||
match self {
|
||||
FsNode::Directory { children } => {
|
||||
path.tmp.mkdirs(0o000)?;
|
||||
if path.real.exists() {
|
||||
clone_attr(path.real, path.tmp)?;
|
||||
} else if let Some(p) = path.tmp.parent_dir() {
|
||||
let parent = Utf8CString::from(p);
|
||||
clone_attr(&parent, path.tmp)?;
|
||||
}
|
||||
|
||||
// Check whether a file name '.replace' exist
|
||||
if let Some(FsNode::File { src }) = children.remove(".replace")
|
||||
&& let Some(base_dir) = src.parent_dir()
|
||||
{
|
||||
for (name, node) in children {
|
||||
let path = path.append(name);
|
||||
match node {
|
||||
FsNode::Directory { .. } | FsNode::File { .. } => {
|
||||
let src = Utf8CString::from(base_dir).join_path(name);
|
||||
bind_mount(
|
||||
"mount",
|
||||
&src,
|
||||
path.real,
|
||||
matches!(node, FsNode::Directory { .. }),
|
||||
)?;
|
||||
}
|
||||
_ => node.commit_tmpfs(path)?,
|
||||
}
|
||||
}
|
||||
|
||||
// If performing replace, we skip mirroring
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Traverse the real directory and mount mirrors
|
||||
if let Ok(mut dir) = Directory::open(path.real) {
|
||||
while let Ok(Some(entry)) = dir.read() {
|
||||
if children.contains_key(entry.name().as_str()) {
|
||||
// Should not be mirrored, next
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = path.append(entry.name());
|
||||
|
||||
if entry.is_symlink() {
|
||||
// Add the symlink into children and handle it later
|
||||
let mut link = cstr::buf::default();
|
||||
entry.read_link(&mut link).log_ok();
|
||||
children.insert(
|
||||
entry.name().to_string(),
|
||||
FsNode::Symlink {
|
||||
target: link.to_owned(),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
mount_dummy("mirror", path.real, path.tmp, entry.is_dir())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, commit children
|
||||
for (name, node) in children {
|
||||
let path = path.append(name);
|
||||
node.commit_tmpfs(path)?;
|
||||
}
|
||||
}
|
||||
FsNode::File { src } => {
|
||||
if path.real.exists() {
|
||||
clone_attr(path.real, src)?;
|
||||
}
|
||||
mount_dummy("mount", src, path.tmp, false)?;
|
||||
}
|
||||
FsNode::Symlink { target } => {
|
||||
module_log!("mklink", path.tmp, target);
|
||||
path.tmp.create_symlink_to(target)?;
|
||||
if path.real.exists() {
|
||||
clone_attr(path.real, path.tmp)?;
|
||||
}
|
||||
}
|
||||
FsNode::Whiteout => {
|
||||
module_log!("delete", path.real, "null");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_path_env() -> String {
|
||||
std::env::var_os("PATH")
|
||||
.and_then(|s| s.into_string().ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn inject_magisk_bins(system: &mut FsNode) {
|
||||
fn inject(children: &mut FsNodeMap) {
|
||||
let mut path = cstr::buf::default().join_path(get_magisk_tmp());
|
||||
|
||||
// Inject binaries
|
||||
|
||||
let len = path.len();
|
||||
path.append_path("magisk");
|
||||
children.insert(
|
||||
"magisk".to_string(),
|
||||
FsNode::File {
|
||||
src: path.to_owned(),
|
||||
},
|
||||
);
|
||||
|
||||
path.truncate(len);
|
||||
path.append_path("magiskpolicy");
|
||||
children.insert(
|
||||
"magiskpolicy".to_string(),
|
||||
FsNode::File {
|
||||
src: path.to_owned(),
|
||||
},
|
||||
);
|
||||
|
||||
// Inject applet symlinks
|
||||
|
||||
children.insert(
|
||||
"su".to_string(),
|
||||
FsNode::Symlink {
|
||||
target: Utf8CString::from("./magisk"),
|
||||
},
|
||||
);
|
||||
children.insert(
|
||||
"resetprop".to_string(),
|
||||
FsNode::Symlink {
|
||||
target: Utf8CString::from("./magisk"),
|
||||
},
|
||||
);
|
||||
children.insert(
|
||||
"supolicy".to_string(),
|
||||
FsNode::Symlink {
|
||||
target: Utf8CString::from("./magiskpolicy"),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// First find whether /system/bin exists
|
||||
let bin = system.children().and_then(|c| c.get_mut("bin"));
|
||||
if let Some(FsNode::Directory { children }) = bin {
|
||||
inject(children);
|
||||
return;
|
||||
}
|
||||
|
||||
// If /system/bin node does not exist, use the first suitable directory in PATH
|
||||
let path_env = get_path_env();
|
||||
let bin_paths = path_env.split(':').filter_map(|path| {
|
||||
if SECONDARY_READ_ONLY_PARTITIONS
|
||||
.iter()
|
||||
.any(|p| path.starts_with(p.as_str()))
|
||||
{
|
||||
let path = Utf8CString::from(path);
|
||||
if let Ok(attr) = path.get_attr()
|
||||
&& (attr.st.st_mode & 0x0001) != 0
|
||||
{
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
'path_loop: for path in bin_paths {
|
||||
let components = Path::new(&path)
|
||||
.components()
|
||||
.filter(|c| matches!(c, Component::Normal(_)))
|
||||
.filter_map(|c| c.as_os_str().to_str());
|
||||
|
||||
let mut curr = match system {
|
||||
FsNode::Directory { children } => children,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
for dir in components {
|
||||
let node = curr.entry(dir.to_owned()).or_insert_with(FsNode::new_dir);
|
||||
match node {
|
||||
FsNode::Directory { children } => curr = children,
|
||||
_ => continue 'path_loop,
|
||||
}
|
||||
}
|
||||
|
||||
// Found a suitable path, done
|
||||
inject(curr);
|
||||
return;
|
||||
}
|
||||
|
||||
// If still not found, directly inject into /system/bin
|
||||
let node = system
|
||||
.children()
|
||||
.map(|c| c.entry("bin".to_string()).or_insert_with(FsNode::new_dir));
|
||||
if let Some(FsNode::Directory { children }) = node {
|
||||
inject(children)
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_zygisk_bins(system: &mut FsNode, name: &str) {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
let has_32_bit = cstr!("/system/bin/linker").exists();
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
let has_32_bit = true;
|
||||
|
||||
if has_32_bit {
|
||||
let lib = system
|
||||
.children()
|
||||
.map(|c| c.entry("lib".to_string()).or_insert_with(FsNode::new_dir));
|
||||
if let Some(FsNode::Directory { children }) = lib {
|
||||
let mut bin_path = cstr::buf::default().join_path(get_magisk_tmp());
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
bin_path.append_path("magisk32");
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
bin_path.append_path("magisk");
|
||||
|
||||
children.insert(
|
||||
name.to_string(),
|
||||
FsNode::File {
|
||||
src: bin_path.to_owned(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
if cstr!("/system/bin/linker64").exists() {
|
||||
let lib64 = system
|
||||
.children()
|
||||
.map(|c| c.entry("lib64".to_string()).or_insert_with(FsNode::new_dir));
|
||||
if let Some(FsNode::Directory { children }) = lib64 {
|
||||
let bin_path = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path("magisk");
|
||||
|
||||
children.insert(
|
||||
name.to_string(),
|
||||
FsNode::File {
|
||||
src: bin_path.to_owned(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str) {
|
||||
let mut system = FsNode::new_dir();
|
||||
|
||||
// Step 1: Create virtual filesystem tree
|
||||
//
|
||||
// In this step, there is zero logic applied during tree construction; we simply collect
|
||||
// and record the union of all module filesystem trees under each of their /system directory.
|
||||
|
||||
let mut path = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(MODULEMNT);
|
||||
let len = path.len();
|
||||
for info in module_list {
|
||||
path.truncate(len);
|
||||
path.append_path(&info.name);
|
||||
|
||||
// Read props
|
||||
let module_path_len = path.len();
|
||||
path.append_path("system.prop");
|
||||
if path.exists() {
|
||||
// Do NOT go through property service as it could cause boot lock
|
||||
load_prop_file(&path, true);
|
||||
}
|
||||
|
||||
// Check whether skip mounting
|
||||
path.truncate(module_path_len);
|
||||
path.append_path("skip_mount");
|
||||
if path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Double check whether the system folder exists
|
||||
path.truncate(module_path_len);
|
||||
path.append_path("system");
|
||||
if path.exists() {
|
||||
info!("{}: loading module files", &info.name);
|
||||
system.build_from_path(&mut path).log_ok();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Inject custom files
|
||||
//
|
||||
// Magisk provides some built-in functionality that requires augmenting the filesystem.
|
||||
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
|
||||
// has to also be added into the default LD_LIBRARY_PATH for code injection.
|
||||
// We directly inject file nodes into the virtual filesystem tree we built in the previous
|
||||
// step, treating Magisk just like a special "module".
|
||||
|
||||
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
|
||||
inject_magisk_bins(&mut system);
|
||||
}
|
||||
if !zygisk_name.is_empty() {
|
||||
inject_zygisk_bins(&mut system, zygisk_name);
|
||||
}
|
||||
|
||||
// Step 3: Extract all supported read-only partition roots
|
||||
//
|
||||
// For simplicity and backwards compatibility on older Android versions, when constructing
|
||||
// Magisk modules, we always assume that there is only a single read-only partition mounted
|
||||
// at /system. However, on modern Android there are actually multiple read-only partitions
|
||||
// mounted at their respective paths. We need to extract these subtrees out of the main
|
||||
// tree and treat them as individual trees.
|
||||
|
||||
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
|
||||
if let FsNode::Directory { children } = &mut system {
|
||||
for dir in SECONDARY_READ_ONLY_PARTITIONS {
|
||||
// Only treat these nodes as root iff it is actually a directory in rootdir
|
||||
if let Ok(attr) = dir.get_attr()
|
||||
&& attr.is_dir()
|
||||
{
|
||||
let name = dir.trim_start_matches('/');
|
||||
if let Some(root) = children.remove(name) {
|
||||
roots.insert(name, root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
roots.insert("system", system);
|
||||
|
||||
// Reuse the path buffer
|
||||
path.clear();
|
||||
path.push_str("/");
|
||||
|
||||
// Build the base worker directory path
|
||||
let mut tmp = cstr::buf::default()
|
||||
.join_path(get_magisk_tmp())
|
||||
.join_path(WORKERDIR);
|
||||
|
||||
let mut tracker = PathTracker::from(&mut path, &mut tmp);
|
||||
for (dir, mut root) in roots {
|
||||
// Step 4: Convert virtual filesystem tree into concrete operations
|
||||
//
|
||||
// Compare the virtual filesystem tree we constructed against the real filesystem
|
||||
// structure on-device to generate a series of "operations".
|
||||
// The "core" of the logic is to decide which directories need to be rebuilt in the
|
||||
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
|
||||
|
||||
let path = tracker.append(dir);
|
||||
root.commit(path, true).log_ok();
|
||||
}
|
||||
}
|
@ -68,7 +68,7 @@ pub fn setup_module_mount() {
|
||||
.join_path(MODULEMNT);
|
||||
let _: LoggedResult<()> = try {
|
||||
module_mnt.mkdir(0o755)?;
|
||||
cstr!(MODULEROOT).bind_mount_to(&module_mnt)?;
|
||||
cstr!(MODULEROOT).bind_mount_to(&module_mnt, false)?;
|
||||
module_mnt.remount_mount_point_flags(libc::MS_RDONLY)?;
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -82,7 +82,7 @@ impl MagiskInit {
|
||||
debug!("Mount [{}] -> [{}]", src, dest);
|
||||
clone_attr(&dest, &src)?;
|
||||
dest.get_secontext(&mut con)?;
|
||||
src.bind_mount_to(&dest)?;
|
||||
src.bind_mount_to(&dest, false)?;
|
||||
self.overlay_con
|
||||
.push(OverlayAttr(dest.to_owned(), con.to_owned()));
|
||||
mount_list.push_str(dest.as_str());
|
||||
|
@ -49,13 +49,13 @@ enum SePatchStrategy {
|
||||
fn mock_fifo(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
|
||||
debug!("Hijack [{}]", target);
|
||||
mock.mkfifo(0o666)?;
|
||||
mock.bind_mount_to(target).log()
|
||||
mock.bind_mount_to(target, false).log()
|
||||
}
|
||||
|
||||
fn mock_file(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
|
||||
debug!("Hijack [{}]", target);
|
||||
drop(mock.create(libc::O_RDONLY, 0o666)?);
|
||||
mock.bind_mount_to(target).log()
|
||||
mock.bind_mount_to(target, false).log()
|
||||
}
|
||||
|
||||
impl MagiskInit {
|
||||
|
@ -37,7 +37,7 @@ pub(crate) fn hexpatch_init_for_second_stage(writable: bool) {
|
||||
}
|
||||
let attr = src.follow_link().get_attr()?;
|
||||
dest.set_attr(&attr)?;
|
||||
dest.bind_mount_to(src)?;
|
||||
dest.bind_mount_to(src, false)?;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -89,12 +89,15 @@ impl MagiskInit {
|
||||
cstr!("/init").rename_to(cstr!("/sdcard")).log_ok();
|
||||
|
||||
// First try to mount magiskinit from rootfs to workaround Samsung RKP
|
||||
if cstr!("/sdcard").bind_mount_to(cstr!("/sdcard")).is_ok() {
|
||||
if cstr!("/sdcard")
|
||||
.bind_mount_to(cstr!("/sdcard"), false)
|
||||
.is_ok()
|
||||
{
|
||||
debug!("Bind mount /sdcard -> /sdcard");
|
||||
} else {
|
||||
// Binding mounting from rootfs is not supported before Linux 3.12
|
||||
cstr!("/data/magiskinit")
|
||||
.bind_mount_to(cstr!("/sdcard"))
|
||||
.bind_mount_to(cstr!("/sdcard"), false)
|
||||
.log_ok();
|
||||
debug!("Bind mount /data/magiskinit -> /sdcard");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user