mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-05-18 12:38:32 +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
|
// 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.
|
// The length of the string without the terminating null character.
|
||||||
// assert_true(len <= capacity - 1)
|
// assert_true(len <= capacity - 1)
|
||||||
fn len(&self) -> usize;
|
fn len(&self) -> usize;
|
||||||
|
@ -182,6 +182,10 @@ impl FileAttr {
|
|||||||
pub fn is_socket(&self) -> bool {
|
pub fn is_socket(&self) -> bool {
|
||||||
self.is(libc::S_IFSOCK)
|
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";
|
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
|
||||||
|
@ -3,13 +3,14 @@ use libc::c_ulong;
|
|||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
impl Utf8CStr {
|
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 {
|
unsafe {
|
||||||
libc::mount(
|
libc::mount(
|
||||||
self.as_ptr(),
|
self.as_ptr(),
|
||||||
path.as_ptr(),
|
path.as_ptr(),
|
||||||
ptr::null(),
|
ptr::null(),
|
||||||
libc::MS_BIND,
|
libc::MS_BIND | flag,
|
||||||
ptr::null(),
|
ptr::null(),
|
||||||
)
|
)
|
||||||
.check_os_err("bind_mount", Some(self), Some(path))
|
.check_os_err("bind_mount", Some(self), Some(path))
|
||||||
|
@ -14,6 +14,7 @@ use cxx::{ExternType, type_id};
|
|||||||
use daemon::{MagiskD, daemon_entry};
|
use daemon::{MagiskD, daemon_entry};
|
||||||
use derive::Decodable;
|
use derive::Decodable;
|
||||||
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
|
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 mount::{find_preinit_device, revert_unmount};
|
||||||
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
||||||
use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon};
|
use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon};
|
||||||
@ -30,6 +31,7 @@ mod consts;
|
|||||||
mod daemon;
|
mod daemon;
|
||||||
mod db;
|
mod db;
|
||||||
mod logging;
|
mod logging;
|
||||||
|
mod module;
|
||||||
mod mount;
|
mod mount;
|
||||||
mod package;
|
mod package;
|
||||||
mod resetprop;
|
mod resetprop;
|
||||||
@ -146,6 +148,7 @@ pub mod ffi {
|
|||||||
value: *const c_char,
|
value: *const c_char,
|
||||||
serial: u32,
|
serial: u32,
|
||||||
);
|
);
|
||||||
|
unsafe fn load_prop_file(filename: *const c_char, skip_svc: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C++" {
|
unsafe extern "C++" {
|
||||||
@ -220,6 +223,8 @@ pub mod ffi {
|
|||||||
|
|
||||||
#[namespace = "rust"]
|
#[namespace = "rust"]
|
||||||
fn daemon_entry();
|
fn daemon_entry();
|
||||||
|
#[namespace = "rust"]
|
||||||
|
fn load_modules(module_list: &[ModuleInfo], zygisk_name: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default constructors
|
// 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 {
|
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 }
|
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/mman.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <sys/mount.h>
|
|
||||||
#include <map>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <base.hpp>
|
#include <base.hpp>
|
||||||
#include <consts.hpp>
|
#include <consts.hpp>
|
||||||
#include <core.hpp>
|
#include <core.hpp>
|
||||||
|
|
||||||
#include "node.hpp"
|
|
||||||
|
|
||||||
using namespace std;
|
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
|
* Filesystem operations
|
||||||
************************/
|
************************/
|
||||||
@ -485,7 +139,22 @@ rust::Vec<ModuleInfo> MagiskD::handle_modules() const noexcept {
|
|||||||
exec_module_scripts("post-fs-data", collect_modules(zygisk, false));
|
exec_module_scripts("post-fs-data", collect_modules(zygisk, false));
|
||||||
// Recollect modules (module scripts could remove itself)
|
// Recollect modules (module scripts could remove itself)
|
||||||
auto list = collect_modules(zygisk, true);
|
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;
|
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);
|
.join_path(MODULEMNT);
|
||||||
let _: LoggedResult<()> = try {
|
let _: LoggedResult<()> = try {
|
||||||
module_mnt.mkdir(0o755)?;
|
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)?;
|
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);
|
debug!("Mount [{}] -> [{}]", src, dest);
|
||||||
clone_attr(&dest, &src)?;
|
clone_attr(&dest, &src)?;
|
||||||
dest.get_secontext(&mut con)?;
|
dest.get_secontext(&mut con)?;
|
||||||
src.bind_mount_to(&dest)?;
|
src.bind_mount_to(&dest, false)?;
|
||||||
self.overlay_con
|
self.overlay_con
|
||||||
.push(OverlayAttr(dest.to_owned(), con.to_owned()));
|
.push(OverlayAttr(dest.to_owned(), con.to_owned()));
|
||||||
mount_list.push_str(dest.as_str());
|
mount_list.push_str(dest.as_str());
|
||||||
|
@ -49,13 +49,13 @@ enum SePatchStrategy {
|
|||||||
fn mock_fifo(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
|
fn mock_fifo(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
|
||||||
debug!("Hijack [{}]", target);
|
debug!("Hijack [{}]", target);
|
||||||
mock.mkfifo(0o666)?;
|
mock.mkfifo(0o666)?;
|
||||||
mock.bind_mount_to(target).log()
|
mock.bind_mount_to(target, false).log()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mock_file(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
|
fn mock_file(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
|
||||||
debug!("Hijack [{}]", target);
|
debug!("Hijack [{}]", target);
|
||||||
drop(mock.create(libc::O_RDONLY, 0o666)?);
|
drop(mock.create(libc::O_RDONLY, 0o666)?);
|
||||||
mock.bind_mount_to(target).log()
|
mock.bind_mount_to(target, false).log()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MagiskInit {
|
impl MagiskInit {
|
||||||
|
@ -37,7 +37,7 @@ pub(crate) fn hexpatch_init_for_second_stage(writable: bool) {
|
|||||||
}
|
}
|
||||||
let attr = src.follow_link().get_attr()?;
|
let attr = src.follow_link().get_attr()?;
|
||||||
dest.set_attr(&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();
|
cstr!("/init").rename_to(cstr!("/sdcard")).log_ok();
|
||||||
|
|
||||||
// First try to mount magiskinit from rootfs to workaround Samsung RKP
|
// 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");
|
debug!("Bind mount /sdcard -> /sdcard");
|
||||||
} else {
|
} else {
|
||||||
// Binding mounting from rootfs is not supported before Linux 3.12
|
// Binding mounting from rootfs is not supported before Linux 3.12
|
||||||
cstr!("/data/magiskinit")
|
cstr!("/data/magiskinit")
|
||||||
.bind_mount_to(cstr!("/sdcard"))
|
.bind_mount_to(cstr!("/sdcard"), false)
|
||||||
.log_ok();
|
.log_ok();
|
||||||
debug!("Bind mount /data/magiskinit -> /sdcard");
|
debug!("Bind mount /data/magiskinit -> /sdcard");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user