Introduce new module mount implementation
Rewrite the whole module mounting logic from scratch. Even the algorithm is different compared to the old one. This new design focuses on a few key points: - Modular: Custom nodes can be injected into the mount tree. It's the main reason for starting the rewrite (needed for Android 11) - Efficient: Compared to the existing implementation, this is the most efficient (both in terms of computation and memory usage) design I currently can come up with. - Accurate: The old mounting logic relies on handling specifically every edge case I can think of. During this rewrite I actually found some cases that the old design does not handle properly. This new design is architected in a way (node types and its rankings) that it should handle edge cases all by itself when constructing mount trees.
This commit is contained in:
parent
ab853e1fcf
commit
a73e7e9f99
@ -20,6 +20,7 @@ LOCAL_SRC_FILES := \
|
|||||||
core/db.cpp \
|
core/db.cpp \
|
||||||
core/scripting.cpp \
|
core/scripting.cpp \
|
||||||
core/restorecon.cpp \
|
core/restorecon.cpp \
|
||||||
|
core/module.cpp \
|
||||||
magiskhide/magiskhide.cpp \
|
magiskhide/magiskhide.cpp \
|
||||||
magiskhide/proc_monitor.cpp \
|
magiskhide/proc_monitor.cpp \
|
||||||
magiskhide/hide_utils.cpp \
|
magiskhide/hide_utils.cpp \
|
||||||
|
@ -16,286 +16,18 @@
|
|||||||
#include <daemon.hpp>
|
#include <daemon.hpp>
|
||||||
#include <resetprop.hpp>
|
#include <resetprop.hpp>
|
||||||
#include <selinux.hpp>
|
#include <selinux.hpp>
|
||||||
#include <flags.h>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
static vector<string> module_list;
|
extern vector<string> module_list;
|
||||||
static bool no_secure_dir = false;
|
static bool no_secure_dir = false;
|
||||||
static bool pfs_done = false;
|
static bool pfs_done = false;
|
||||||
|
|
||||||
static int bind_mount(const char *from, const char *to, bool log = true);
|
|
||||||
extern void auto_start_magiskhide();
|
extern void auto_start_magiskhide();
|
||||||
|
|
||||||
/***************
|
/*********
|
||||||
* Magic Mount *
|
* Setup *
|
||||||
***************/
|
*********/
|
||||||
|
|
||||||
#ifdef MAGISK_DEBUG
|
|
||||||
#define VLOGI(tag, from, to) LOGI("%s: %s <- %s\n", tag, to, from)
|
|
||||||
#else
|
|
||||||
#define VLOGI(tag, from, to) LOGI("%s: %s\n", tag, to)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Precedence: MODULE > SKEL > INTER > DUMMY
|
|
||||||
#define IS_DUMMY 0x01 /* mount from mirror */
|
|
||||||
#define IS_INTER 0x02 /* intermediate node */
|
|
||||||
#define IS_SKEL 0x04 /* replace with tmpfs */
|
|
||||||
#define IS_MODULE 0x08 /* mount from module */
|
|
||||||
|
|
||||||
#define IS_DIR(n) ((n)->type == DT_DIR)
|
|
||||||
#define IS_LNK(n) ((n)->type == DT_LNK)
|
|
||||||
#define IS_REG(n) ((n)->type == DT_REG)
|
|
||||||
|
|
||||||
class node_entry {
|
|
||||||
public:
|
|
||||||
explicit node_entry(const char *name, uint8_t type = DT_DIR, uint8_t status = IS_INTER)
|
|
||||||
: module(nullptr), name(name), type(type), status(status), parent(nullptr) {}
|
|
||||||
~node_entry();
|
|
||||||
void create_module_tree(const char *module);
|
|
||||||
void magic_mount();
|
|
||||||
node_entry *extract(const char *name);
|
|
||||||
|
|
||||||
static bool vendor_root;
|
|
||||||
static bool product_root;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const char *module; /* Only used when IS_MODULE */
|
|
||||||
const string name;
|
|
||||||
uint8_t type;
|
|
||||||
uint8_t status;
|
|
||||||
node_entry *parent;
|
|
||||||
vector<node_entry *> children;
|
|
||||||
|
|
||||||
node_entry(node_entry *parent, const char *module, const char *name, uint8_t type)
|
|
||||||
: module(module), name(name), type(type), status(0), parent(parent) {}
|
|
||||||
bool is_special();
|
|
||||||
bool is_root();
|
|
||||||
string get_path();
|
|
||||||
void insert(node_entry *&);
|
|
||||||
void clone_skeleton();
|
|
||||||
};
|
|
||||||
|
|
||||||
bool node_entry::vendor_root = false;
|
|
||||||
bool node_entry::product_root = false;
|
|
||||||
|
|
||||||
node_entry::~node_entry() {
|
|
||||||
for (auto &node : children)
|
|
||||||
delete node;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SPECIAL_NODE (parent->parent ? false : \
|
|
||||||
((vendor_root && name == "vendor") || (product_root && name == "product")))
|
|
||||||
|
|
||||||
bool node_entry::is_special() {
|
|
||||||
return parent ? SPECIAL_NODE : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool node_entry::is_root() {
|
|
||||||
return parent ? SPECIAL_NODE : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
string node_entry::get_path() {
|
|
||||||
string path;
|
|
||||||
if (parent)
|
|
||||||
path = parent->get_path();
|
|
||||||
path += "/";
|
|
||||||
path += name;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_entry::insert(node_entry *&node) {
|
|
||||||
node->parent = this;
|
|
||||||
for (auto &child : children) {
|
|
||||||
if (child->name == node->name) {
|
|
||||||
if (node->status > child->status) {
|
|
||||||
// The new node has higher precedence
|
|
||||||
delete child;
|
|
||||||
child = node;
|
|
||||||
} else {
|
|
||||||
delete node;
|
|
||||||
node = child;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
children.push_back(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_entry::create_module_tree(const char *module) {
|
|
||||||
auto full_path = get_path();
|
|
||||||
auto cwd = MODULEROOT + "/"s + module + full_path;
|
|
||||||
|
|
||||||
auto dir = xopen_dir(cwd.data());
|
|
||||||
if (!dir)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check directory replace
|
|
||||||
if (faccessat(dirfd(dir.get()), ".replace", F_OK, 0) == 0) {
|
|
||||||
if (is_root()) {
|
|
||||||
// Root nodes should not be replaced
|
|
||||||
rm_rf(cwd.data());
|
|
||||||
} else if (status < IS_MODULE) {
|
|
||||||
// Upgrade current node to current module
|
|
||||||
this->module = module;
|
|
||||||
status = IS_MODULE;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
|
||||||
if (entry->d_name == "."sv || entry->d_name == ".."sv)
|
|
||||||
continue;
|
|
||||||
// Create new node
|
|
||||||
auto node = new node_entry(this, module, entry->d_name, entry->d_type);
|
|
||||||
|
|
||||||
auto dest = full_path + "/" + entry->d_name;
|
|
||||||
auto src = cwd + "/" + entry->d_name;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Clone current directory in one of the following conditions:
|
|
||||||
* - Target does not exist
|
|
||||||
* - Module file is a symlink
|
|
||||||
* - Target file is a symlink (exclude special nodes)
|
|
||||||
*/
|
|
||||||
bool clone = false;
|
|
||||||
if (IS_LNK(node) || access(dest.data(), F_OK) != 0) {
|
|
||||||
clone = true;
|
|
||||||
} else if (!node->is_special()) {
|
|
||||||
struct stat s;
|
|
||||||
xlstat(dest.data(), &s);
|
|
||||||
if (S_ISLNK(s.st_mode))
|
|
||||||
clone = true;
|
|
||||||
}
|
|
||||||
if (clone && is_root()) {
|
|
||||||
// Root nodes should not be cloned
|
|
||||||
rm_rf(src.data());
|
|
||||||
delete node;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clone) {
|
|
||||||
// Mark self as a skeleton
|
|
||||||
status |= IS_SKEL; /* This will not overwrite if parent is module */
|
|
||||||
node->status = IS_MODULE;
|
|
||||||
} else if (node->is_special()) {
|
|
||||||
// Special nodes will be pulled out as root nodes later
|
|
||||||
node->status = IS_INTER;
|
|
||||||
} else {
|
|
||||||
// Clone attributes from real path
|
|
||||||
clone_attr(dest.data(), src.data());
|
|
||||||
if (IS_DIR(node)) {
|
|
||||||
// First mark as an intermediate node
|
|
||||||
node->status = IS_INTER;
|
|
||||||
} else if (IS_REG(node)) {
|
|
||||||
// This is a file, mark as leaf
|
|
||||||
node->status = IS_MODULE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
insert(node);
|
|
||||||
if (IS_DIR(node)) {
|
|
||||||
// Recursive traverse through everything
|
|
||||||
node->create_module_tree(module);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_entry::clone_skeleton() {
|
|
||||||
// Clone the structure
|
|
||||||
auto full_path = get_path();
|
|
||||||
auto mirror_path = MAGISKTMP + "/" MIRRDIR + full_path;
|
|
||||||
|
|
||||||
if (auto dir = xopen_dir(mirror_path.data()); dir) {
|
|
||||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
|
||||||
if (entry->d_name == "."sv || entry->d_name == ".."sv)
|
|
||||||
continue;
|
|
||||||
// Create dummy node
|
|
||||||
auto dummy = new node_entry(entry->d_name, entry->d_type, IS_DUMMY);
|
|
||||||
insert(dummy);
|
|
||||||
}
|
|
||||||
} else { return; }
|
|
||||||
|
|
||||||
if (status & IS_SKEL) {
|
|
||||||
file_attr attr;
|
|
||||||
getattr(full_path.data(), &attr);
|
|
||||||
LOGI("mnt_tmpfs : %s\n", full_path.data());
|
|
||||||
xmount("tmpfs", full_path.data(), "tmpfs", 0, nullptr);
|
|
||||||
setattr(full_path.data(), &attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &child : children) {
|
|
||||||
auto dest = full_path + "/" + child->name;
|
|
||||||
string src;
|
|
||||||
|
|
||||||
// Create the dummy file/directory
|
|
||||||
if (IS_DIR(child))
|
|
||||||
xmkdir(dest.data(), 0755);
|
|
||||||
else if (IS_REG(child))
|
|
||||||
close(creat(dest.data(), 0644));
|
|
||||||
// Links will be handled later
|
|
||||||
|
|
||||||
if (child->status & IS_MODULE) {
|
|
||||||
// Mount from module file to dummy file
|
|
||||||
src = MAGISKTMP + "/" MODULEMNT "/" + child->module + full_path + "/" + child->name;
|
|
||||||
} else if (child->status & (IS_SKEL | IS_INTER)) {
|
|
||||||
// It's an intermediate folder, recursive clone
|
|
||||||
child->clone_skeleton();
|
|
||||||
continue;
|
|
||||||
} else if (child->status & IS_DUMMY) {
|
|
||||||
// Mount from mirror to dummy file
|
|
||||||
src = MAGISKTMP + "/" MIRRDIR + full_path + "/" + child->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_LNK(child)) {
|
|
||||||
// Copy symlinks directly
|
|
||||||
cp_afc(src.data(), dest.data());
|
|
||||||
VLOGI("copy_link ", src.data(), dest.data());
|
|
||||||
} else {
|
|
||||||
bind_mount(src.data(), dest.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void node_entry::magic_mount() {
|
|
||||||
if (status & IS_MODULE) {
|
|
||||||
// Mount module item
|
|
||||||
auto dest = get_path();
|
|
||||||
auto src = MAGISKTMP + "/" MODULEMNT "/" + module + dest;
|
|
||||||
bind_mount(src.data(), dest.data());
|
|
||||||
} else if (status & IS_SKEL) {
|
|
||||||
// The node is labeled to be cloned with skeleton, lets do it
|
|
||||||
clone_skeleton();
|
|
||||||
} else if (status & IS_INTER) {
|
|
||||||
// It's an intermediate node, travel deeper
|
|
||||||
for (auto &child : children)
|
|
||||||
child->magic_mount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node_entry *node_entry::extract(const char *name) {
|
|
||||||
node_entry *node = nullptr;
|
|
||||||
// Extract the node out of the tree
|
|
||||||
for (auto it = children.begin(); it != children.end(); ++it) {
|
|
||||||
if ((*it)->name == name) {
|
|
||||||
node = *it;
|
|
||||||
node->parent = nullptr;
|
|
||||||
children.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************
|
|
||||||
* Miscellaneous *
|
|
||||||
*****************/
|
|
||||||
|
|
||||||
static int bind_mount(const char *from, const char *to, bool log) {
|
|
||||||
int ret = xmount(from, to, nullptr, MS_BIND, nullptr);
|
|
||||||
if (log) VLOGI("bind_mount", from, to);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#define DIR_IS(part) (me->mnt_dir == "/" #part ""sv)
|
#define DIR_IS(part) (me->mnt_dir == "/" #part ""sv)
|
||||||
#define SETMIR(b, part) sprintf(b, "%s/" MIRRDIR "/" #part, MAGISKTMP.data())
|
#define SETMIR(b, part) sprintf(b, "%s/" MIRRDIR "/" #part, MAGISKTMP.data())
|
||||||
@ -308,7 +40,7 @@ static int bind_mount(const char *from, const char *to, bool log) {
|
|||||||
mknod(buf2, (st.st_mode & S_IFMT) | 0600, st.st_rdev); \
|
mknod(buf2, (st.st_mode & S_IFMT) | 0600, st.st_rdev); \
|
||||||
xmkdir(buf1, 0755); \
|
xmkdir(buf1, 0755); \
|
||||||
xmount(buf2, buf1, me->mnt_type, flag, nullptr); \
|
xmount(buf2, buf1, me->mnt_type, flag, nullptr); \
|
||||||
VLOGI("mount", buf2, buf1); \
|
LOGI("mount: %s\n", buf1); \
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool magisk_env() {
|
static bool magisk_env() {
|
||||||
@ -367,7 +99,7 @@ static bool magisk_env() {
|
|||||||
mount_mirror(system_root, MS_RDONLY);
|
mount_mirror(system_root, MS_RDONLY);
|
||||||
SETMIR(buf1, system);
|
SETMIR(buf1, system);
|
||||||
xsymlink("./system_root/system", buf1);
|
xsymlink("./system_root/system", buf1);
|
||||||
VLOGI("link", "./system_root/system", buf1);
|
LOGI("link: %s\n", buf1);
|
||||||
system_as_root = true;
|
system_as_root = true;
|
||||||
} else if (!system_as_root && DIR_IS(system)) {
|
} else if (!system_as_root && DIR_IS(system)) {
|
||||||
mount_mirror(system, MS_RDONLY);
|
mount_mirror(system, MS_RDONLY);
|
||||||
@ -387,17 +119,17 @@ static bool magisk_env() {
|
|||||||
if (access(buf1, F_OK) != 0 && access(buf2, F_OK) == 0) {
|
if (access(buf1, F_OK) != 0 && access(buf2, F_OK) == 0) {
|
||||||
// Pre-init mirrors
|
// Pre-init mirrors
|
||||||
xsymlink("./system_root/system", buf1);
|
xsymlink("./system_root/system", buf1);
|
||||||
VLOGI("link", "./system_root/system", buf1);
|
LOGI("link: %s\n", buf1);
|
||||||
}
|
}
|
||||||
SETMIR(buf1, vendor);
|
SETMIR(buf1, vendor);
|
||||||
if (access(buf1, F_OK) != 0) {
|
if (access(buf1, F_OK) != 0) {
|
||||||
xsymlink("./system/vendor", buf1);
|
xsymlink("./system/vendor", buf1);
|
||||||
VLOGI("link", "./system/vendor", buf1);
|
LOGI("link: %s\n", buf1);
|
||||||
}
|
}
|
||||||
SETMIR(buf1, product);
|
SETMIR(buf1, product);
|
||||||
if (access("/system/product", F_OK) == 0 && access(buf1, F_OK) != 0) {
|
if (access("/system/product", F_OK) == 0 && access(buf1, F_OK) != 0) {
|
||||||
xsymlink("./system/product", buf1);
|
xsymlink("./system/product", buf1);
|
||||||
VLOGI("link", "./system/product", buf1);
|
LOGI("link: %s\n", buf1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable/remove magiskhide, resetprop
|
// Disable/remove magiskhide, resetprop
|
||||||
@ -419,35 +151,6 @@ static bool magisk_env() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prepare_modules() {
|
|
||||||
// Upgrade modules
|
|
||||||
if (auto dir = open_dir(MODULEUPGRADE); dir) {
|
|
||||||
int ufd = dirfd(dir.get());
|
|
||||||
int mfd = xopen(MODULEROOT, O_RDONLY | O_CLOEXEC);
|
|
||||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
|
||||||
if (entry->d_type == DT_DIR) {
|
|
||||||
if (entry->d_name == "."sv || entry->d_name == ".."sv)
|
|
||||||
continue;
|
|
||||||
// Cleanup old module if exists
|
|
||||||
if (faccessat(mfd, entry->d_name, F_OK, 0) == 0) {
|
|
||||||
frm_rf(xopenat(mfd, entry->d_name, O_RDONLY | O_CLOEXEC));
|
|
||||||
unlinkat(mfd, entry->d_name, AT_REMOVEDIR);
|
|
||||||
}
|
|
||||||
LOGI("Upgrade / New module: %s\n", entry->d_name);
|
|
||||||
renameat(ufd, entry->d_name, mfd, entry->d_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(mfd);
|
|
||||||
rm_rf(MODULEUPGRADE);
|
|
||||||
}
|
|
||||||
auto src = MAGISKTMP + "/" MIRRDIR "/" MODULEROOT;
|
|
||||||
auto dest = MAGISKTMP + "/" MODULEMNT;
|
|
||||||
bind_mount(src.data(), dest.data(), false);
|
|
||||||
|
|
||||||
restorecon();
|
|
||||||
chmod(SECURE_DIR, 0700);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void reboot() {
|
static void reboot() {
|
||||||
if (RECOVERY_MODE)
|
if (RECOVERY_MODE)
|
||||||
exec_command_sync("/system/bin/reboot", "recovery");
|
exec_command_sync("/system/bin/reboot", "recovery");
|
||||||
@ -472,94 +175,6 @@ void remove_modules() {
|
|||||||
reboot();
|
reboot();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void collect_modules() {
|
|
||||||
auto dir = xopen_dir(MODULEROOT);
|
|
||||||
int dfd = dirfd(dir.get());
|
|
||||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
|
||||||
if (entry->d_type == DT_DIR) {
|
|
||||||
if (entry->d_name == "."sv || entry->d_name == ".."sv || entry->d_name == ".core"sv)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int modfd = xopenat(dfd, entry->d_name, O_RDONLY);
|
|
||||||
run_finally f([=]{ close(modfd); });
|
|
||||||
|
|
||||||
if (faccessat(modfd, "remove", F_OK, 0) == 0) {
|
|
||||||
LOGI("%s: remove\n", entry->d_name);
|
|
||||||
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
|
|
||||||
if (access(uninstaller.data(), F_OK) == 0)
|
|
||||||
exec_script(uninstaller.data());
|
|
||||||
frm_rf(xdup(modfd));
|
|
||||||
unlinkat(dfd, entry->d_name, AT_REMOVEDIR);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
unlinkat(modfd, "update", 0);
|
|
||||||
|
|
||||||
if (faccessat(modfd, "disable", F_OK, 0) != 0)
|
|
||||||
module_list.emplace_back(entry->d_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool load_modules(node_entry *root) {
|
|
||||||
char buf1[4096];
|
|
||||||
char buf2[4096];
|
|
||||||
|
|
||||||
LOGI("* Loading modules\n");
|
|
||||||
bool has_modules = false;
|
|
||||||
for (const auto &m : module_list) {
|
|
||||||
auto module = m.data();
|
|
||||||
char *b1 = buf1 + sprintf(buf1, MODULEROOT "/%s/", module);
|
|
||||||
|
|
||||||
// Read props
|
|
||||||
strcpy(b1, "system.prop");
|
|
||||||
if (access(buf1, F_OK) == 0) {
|
|
||||||
LOGI("%s: loading [system.prop]\n", module);
|
|
||||||
load_prop_file(buf1, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy sepolicy rules
|
|
||||||
strcpy(b1, "sepolicy.rule");
|
|
||||||
char *b2 = buf2 + sprintf(buf2, "%s/" MIRRDIR "/persist", MAGISKTMP.data());
|
|
||||||
if (access(buf2, F_OK) == 0 && access(buf1, F_OK) == 0) {
|
|
||||||
b2 += sprintf(b2, "/magisk/%s", module);
|
|
||||||
xmkdirs(buf2, 0755);
|
|
||||||
strcpy(b2, "/sepolicy.rule");
|
|
||||||
cp_afc(buf1, buf2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether skip mounting
|
|
||||||
strcpy(b1, "skip_mount");
|
|
||||||
if (access(buf1, F_OK) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Double check whether the system folder exists
|
|
||||||
strcpy(b1, "system");
|
|
||||||
if (access(buf1, F_OK) != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Construct structure
|
|
||||||
has_modules = true;
|
|
||||||
LOGI("%s: constructing magic mount structure\n", module);
|
|
||||||
// If /system/vendor exists in module, create a link outside
|
|
||||||
strcpy(b1, "system/vendor");
|
|
||||||
if (node_entry::vendor_root && access(buf1, F_OK) == 0) {
|
|
||||||
sprintf(buf2, MODULEROOT "/%s/vendor", module);
|
|
||||||
unlink(buf2);
|
|
||||||
xsymlink("./system/vendor", buf2);
|
|
||||||
}
|
|
||||||
// If /system/product exists in module, create a link outside
|
|
||||||
strcpy(b1, "system/product");
|
|
||||||
if (node_entry::product_root && access(buf1, F_OK) == 0) {
|
|
||||||
sprintf(buf2, MODULEROOT "/%s/product", module);
|
|
||||||
unlink(buf2);
|
|
||||||
xsymlink("./system/product", buf2);
|
|
||||||
}
|
|
||||||
root->create_module_tree(module);
|
|
||||||
}
|
|
||||||
return has_modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool check_data() {
|
static bool check_data() {
|
||||||
bool mnt = false;
|
bool mnt = false;
|
||||||
bool data = false;
|
bool data = false;
|
||||||
@ -682,45 +297,11 @@ void post_fs_data(int client) {
|
|||||||
LOGI("* Running post-fs-data.d scripts\n");
|
LOGI("* Running post-fs-data.d scripts\n");
|
||||||
exec_common_script("post-fs-data");
|
exec_common_script("post-fs-data");
|
||||||
|
|
||||||
prepare_modules();
|
|
||||||
|
|
||||||
// Core only mode
|
// Core only mode
|
||||||
if (access(DISABLEFILE, F_OK) == 0)
|
if (access(DISABLEFILE, F_OK) == 0)
|
||||||
core_only();
|
core_only();
|
||||||
|
|
||||||
collect_modules();
|
handle_modules();
|
||||||
|
|
||||||
// Execute module scripts
|
|
||||||
LOGI("* Running module post-fs-data scripts\n");
|
|
||||||
exec_module_script("post-fs-data", module_list);
|
|
||||||
|
|
||||||
// Recollect modules
|
|
||||||
module_list.clear();
|
|
||||||
collect_modules();
|
|
||||||
|
|
||||||
node_entry::vendor_root = access("/vendor", F_OK) == 0;
|
|
||||||
node_entry::product_root = access("/product", F_OK) == 0;
|
|
||||||
|
|
||||||
// Create the system root entry
|
|
||||||
auto sys_root = new node_entry("system");
|
|
||||||
|
|
||||||
if (load_modules(sys_root)) {
|
|
||||||
// Pull out special nodes if exist
|
|
||||||
node_entry *special;
|
|
||||||
if (node_entry::vendor_root && (special = sys_root->extract("vendor"))) {
|
|
||||||
special->magic_mount();
|
|
||||||
delete special;
|
|
||||||
}
|
|
||||||
if (node_entry::product_root && (special = sys_root->extract("product"))) {
|
|
||||||
special->magic_mount();
|
|
||||||
delete special;
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_root->magic_mount();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup memory
|
|
||||||
delete sys_root;
|
|
||||||
|
|
||||||
core_only();
|
core_only();
|
||||||
}
|
}
|
||||||
|
619
native/jni/core/module.cpp
Normal file
619
native/jni/core/module.cpp
Normal file
@ -0,0 +1,619 @@
|
|||||||
|
#include <sys/mount.h>
|
||||||
|
#include <map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <magisk.hpp>
|
||||||
|
#include <selinux.hpp>
|
||||||
|
#include <daemon.hpp>
|
||||||
|
#include <resetprop.hpp>
|
||||||
|
#include <flags.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#define SKIP_DOTS {\
|
||||||
|
if (entry->d_name == "."sv || entry->d_name == ".."sv) \
|
||||||
|
continue;\
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MAGISK_DEBUG
|
||||||
|
#define VLOGI(tag, from, to) LOGI("%-8s: %s <- %s\n", tag, to, from)
|
||||||
|
#else
|
||||||
|
#define VLOGI(tag, from, to) LOGI("%-8s: %s\n", tag, to)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TYPE_INTER (1 << 0) /* intermediate node */
|
||||||
|
#define TYPE_MIRROR (1 << 1) /* mount from mirror */
|
||||||
|
#define TYPE_SKEL (1 << 2) /* replace with tmpfs */
|
||||||
|
#define TYPE_MODULE (1 << 3) /* mount from module */
|
||||||
|
#define TYPE_ROOT (1 << 4) /* partition root */
|
||||||
|
#define TYPE_DIR (TYPE_INTER|TYPE_MIRROR|TYPE_SKEL|TYPE_ROOT)
|
||||||
|
|
||||||
|
vector<string> module_list;
|
||||||
|
|
||||||
|
class node_entry;
|
||||||
|
class dir_node;
|
||||||
|
class inter_node;
|
||||||
|
class mirror_node;
|
||||||
|
class skel_node;
|
||||||
|
class module_node;
|
||||||
|
class root_node;
|
||||||
|
|
||||||
|
static void merge_node(node_entry *a, node_entry *b);
|
||||||
|
template<class T> static bool isa(node_entry *node);
|
||||||
|
static int bind_mount(const char *from, const char *to) {
|
||||||
|
int ret = xmount(from, to, nullptr, MS_BIND, nullptr);
|
||||||
|
if (ret == 0)
|
||||||
|
VLOGI("bind_mnt", from, to);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> uint8_t type_id() { return 0; }
|
||||||
|
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<mirror_node>() { return TYPE_MIRROR; }
|
||||||
|
template<> uint8_t type_id<skel_node>() { return TYPE_SKEL; }
|
||||||
|
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;
|
||||||
|
|
||||||
|
bool is_dir() { return file_type() == DT_DIR; }
|
||||||
|
bool is_lnk() { return file_type() == DT_LNK; }
|
||||||
|
bool is_reg() { return file_type() == DT_REG; }
|
||||||
|
|
||||||
|
uint8_t type() { return node_type; }
|
||||||
|
const string &name() { return _name; }
|
||||||
|
|
||||||
|
dir_node *parent() { return _parent; }
|
||||||
|
|
||||||
|
virtual void mount() = 0;
|
||||||
|
|
||||||
|
static string module_mnt;
|
||||||
|
static string mirror_dir;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class T>
|
||||||
|
node_entry(const char *name, uint8_t file_type, T*)
|
||||||
|
: _name(name), _file_type(file_type), node_type(type_id<T>()) {}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
node_entry(T*) : node_type(type_id<T>()) {}
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
const string &node_path();
|
||||||
|
string mirror_path() { return mirror_dir + node_path(); }
|
||||||
|
|
||||||
|
void create_and_mount(const string &src);
|
||||||
|
|
||||||
|
/* Use top bit of _file_type for node exist status */
|
||||||
|
bool exist() { return static_cast<bool>(_file_type & (1 << 7)); }
|
||||||
|
void set_exist() { _file_type |= (1 << 7); }
|
||||||
|
uint8_t file_type() { return static_cast<uint8_t>(_file_type & ~(1 << 7)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend void merge_node(node_entry *a, node_entry *b);
|
||||||
|
friend class dir_node;
|
||||||
|
|
||||||
|
bool need_clone();
|
||||||
|
|
||||||
|
// Node properties
|
||||||
|
string _name;
|
||||||
|
uint8_t _file_type;
|
||||||
|
uint8_t node_type;
|
||||||
|
|
||||||
|
dir_node *_parent = nullptr;
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
string _node_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
class dir_node : public node_entry {
|
||||||
|
public:
|
||||||
|
friend void merge_node(node_entry *a, node_entry *b);
|
||||||
|
|
||||||
|
typedef map<string_view, node_entry *> map_type;
|
||||||
|
typedef map_type::iterator map_iter;
|
||||||
|
|
||||||
|
~dir_node() override;
|
||||||
|
|
||||||
|
// Return false to indicate need to upgrade to module
|
||||||
|
bool collect_files(const char *module, int dfd);
|
||||||
|
|
||||||
|
// Return false to indicate need to upgrade to skeleton
|
||||||
|
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 *child(string_view name) { return iter_to_node<T>(children.find(name)); }
|
||||||
|
|
||||||
|
// Lazy val
|
||||||
|
root_node *root() {
|
||||||
|
if (!_root)
|
||||||
|
_root = _parent->root();
|
||||||
|
return _root;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return child with name or nullptr
|
||||||
|
node_entry *extract(string_view name);
|
||||||
|
|
||||||
|
// Return false if rejected
|
||||||
|
bool insert(node_entry *node) {
|
||||||
|
return node
|
||||||
|
? iter_to_node(insert(children.find(node->_name), node->node_type,
|
||||||
|
[=](auto _) { return node; })) != nullptr
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return inserted node or null if rejected
|
||||||
|
template<class T, class ...Args>
|
||||||
|
T *emplace(string_view name, Args &...args) {
|
||||||
|
return iter_to_node<T>(insert(children.find(name), type_id<T>(),
|
||||||
|
[&](auto _) { return new T(std::forward<Args>(args)...); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return inserted node, existing node with same rank, or null
|
||||||
|
template<class T, class ...Args>
|
||||||
|
T *emplace_or_get(string_view name, Args &...args) {
|
||||||
|
return iter_to_node<T>(insert(children.find(name), type_id<T>(),
|
||||||
|
[&](auto _) { return new T(std::forward<Args>(args)...); }, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return upgraded node or null if rejected
|
||||||
|
template<class T, class ...Args>
|
||||||
|
T *upgrade(string_view name, Args &...args) {
|
||||||
|
return iter_to_node<T>(upgrade<T>(children.find(name), args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class T>
|
||||||
|
dir_node(const char *name, uint8_t file_type, T *self) : node_entry(name, file_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) {
|
||||||
|
merge_node(this, node);
|
||||||
|
if constexpr (std::is_same_v<T, root_node>)
|
||||||
|
_root = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
dir_node(const char *name, T *self) : dir_node(name, DT_DIR, self) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Root node lookup cache
|
||||||
|
root_node *_root;
|
||||||
|
|
||||||
|
// dir nodes host children
|
||||||
|
map_type children;
|
||||||
|
|
||||||
|
template<class T = node_entry>
|
||||||
|
T *iter_to_node(const map_iter &it) {
|
||||||
|
return reinterpret_cast<T*>(it == children.end() ? nullptr : it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fn signature: (node_ent *&) -> node_ent *
|
||||||
|
* fn gets reference to the existing node, may be null.
|
||||||
|
* If fn consumes input, need to set reference to null.
|
||||||
|
* Returns new node or null to reject the insertion. */
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
map_iter insert(map_iter it, uint8_t type, Func fn, bool allow_same = false);
|
||||||
|
|
||||||
|
template<class To, class From = node_entry, class ...Args>
|
||||||
|
map_iter upgrade(map_iter it, Args &...args) {
|
||||||
|
return insert(it, type_id<To>(), [&](node_entry *&ex) -> node_entry * {
|
||||||
|
if (!ex)
|
||||||
|
return nullptr;
|
||||||
|
if constexpr (!std::is_same_v<From, node_entry>) {
|
||||||
|
// Type check if specific type is selected
|
||||||
|
if (!isa<From>(ex))
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto node = new To(reinterpret_cast<From *>(ex), std::forward<Args>(args)...);
|
||||||
|
ex = nullptr;
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class root_node : public dir_node {
|
||||||
|
public:
|
||||||
|
root_node(const char *name) : dir_node(name, this), prefix("") {}
|
||||||
|
root_node(node_entry *node) : dir_node(node, this), prefix("/system") {}
|
||||||
|
const char * const prefix;
|
||||||
|
};
|
||||||
|
|
||||||
|
class inter_node : public dir_node {
|
||||||
|
public:
|
||||||
|
inter_node(const char *name, const char *module) : dir_node(name, this), module(module) {}
|
||||||
|
private:
|
||||||
|
const char *module;
|
||||||
|
friend class module_node;
|
||||||
|
};
|
||||||
|
|
||||||
|
void node_entry::create_and_mount(const string &src) {
|
||||||
|
const string &dest = node_path();
|
||||||
|
if (is_lnk()) {
|
||||||
|
VLOGI("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(src.data(), dest.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class module_node : public node_entry {
|
||||||
|
public:
|
||||||
|
module_node(const char *module, dirent *entry)
|
||||||
|
: node_entry(entry->d_name, entry->d_type, this), module(module) {}
|
||||||
|
|
||||||
|
module_node(node_entry *node, const char *module) : node_entry(this), module(module) {
|
||||||
|
merge_node(this, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_node(inter_node *node) : module_node(node, node->module) {}
|
||||||
|
|
||||||
|
void mount() override {
|
||||||
|
string src = module_mnt + module + parent()->root()->prefix + node_path();
|
||||||
|
if (exist())
|
||||||
|
clone_attr(mirror_path().data(), src.data());
|
||||||
|
if (isa<skel_node>(parent()))
|
||||||
|
create_and_mount(src);
|
||||||
|
else if (is_dir() || is_reg())
|
||||||
|
bind_mount(src.data(), node_path().data());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char *module;
|
||||||
|
};
|
||||||
|
|
||||||
|
class mirror_node : public dir_node {
|
||||||
|
public:
|
||||||
|
mirror_node(dirent *entry) : dir_node(entry->d_name, entry->d_type, this) {}
|
||||||
|
|
||||||
|
void mount() override {
|
||||||
|
create_and_mount(mirror_path());
|
||||||
|
dir_node::mount();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class skel_node : public dir_node {
|
||||||
|
public:
|
||||||
|
skel_node(node_entry *node) : dir_node(node, this) {}
|
||||||
|
|
||||||
|
void mount() override {
|
||||||
|
string src = mirror_path();
|
||||||
|
const string &dest = node_path();
|
||||||
|
file_attr a;
|
||||||
|
getattr(src.data(), &a);
|
||||||
|
xmount("tmpfs", dest.data(), "tmpfs", 0, nullptr);
|
||||||
|
VLOGI("mnt_tmp", "tmpfs", dest.data());
|
||||||
|
setattr(dest.data(), &a);
|
||||||
|
dir_node::mount();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Poor man's dynamic cast without RTTI
|
||||||
|
template<class T>
|
||||||
|
static bool isa(node_entry *node) {
|
||||||
|
return node && (node->type() & type_id<T>());
|
||||||
|
}
|
||||||
|
template<class T>
|
||||||
|
static T *dyn_cast(node_entry *node) {
|
||||||
|
return isa<T>(node) ? reinterpret_cast<T*>(node) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge b -> a, b will be deleted
|
||||||
|
static void merge_node(node_entry *a, node_entry *b) {
|
||||||
|
a->_name.swap(b->_name);
|
||||||
|
a->_file_type = b->_file_type;
|
||||||
|
a->_parent = b->_parent;
|
||||||
|
|
||||||
|
// Merge children if both is dir
|
||||||
|
if (auto aa = dyn_cast<dir_node>(a); aa) {
|
||||||
|
if (auto bb = dyn_cast<dir_node>(b); bb) {
|
||||||
|
aa->children.merge(bb->children);
|
||||||
|
for (auto &pair : aa->children)
|
||||||
|
pair.second->_parent = aa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete b;
|
||||||
|
}
|
||||||
|
|
||||||
|
string node_entry::module_mnt;
|
||||||
|
string node_entry::mirror_dir;
|
||||||
|
|
||||||
|
const string &node_entry::node_path() {
|
||||||
|
if (_parent && _node_path.empty())
|
||||||
|
_node_path = _parent->node_path() + '/' + _name;
|
||||||
|
return _node_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir_node::~dir_node() {
|
||||||
|
for (auto &it : children)
|
||||||
|
delete it.second;
|
||||||
|
children.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
dir_node::map_iter dir_node::insert(dir_node::map_iter it, uint8_t type, Func fn, bool allow_same) {
|
||||||
|
node_entry *node = nullptr;
|
||||||
|
if (it != children.end()) {
|
||||||
|
if (it->second->node_type < type) {
|
||||||
|
// Upgrade existing node only if higher precedence
|
||||||
|
node = fn(it->second);
|
||||||
|
if (!node)
|
||||||
|
return children.end();
|
||||||
|
if (it->second)
|
||||||
|
merge_node(node, 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 {
|
||||||
|
if (allow_same && it->second->node_type == type)
|
||||||
|
return it;
|
||||||
|
return children.end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node = fn(node);
|
||||||
|
if (!node)
|
||||||
|
return it;
|
||||||
|
node->_parent = this;
|
||||||
|
it = children.emplace(node->_name, node).first;
|
||||||
|
}
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
node_entry* dir_node::extract(string_view name) {
|
||||||
|
auto it = children.find(name);
|
||||||
|
if (it != children.end()) {
|
||||||
|
auto ret = it->second;
|
||||||
|
children.erase(it);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool node_entry::need_clone() {
|
||||||
|
/* We need to upgrade parent to skeleton if:
|
||||||
|
* - Target does not exist
|
||||||
|
* - Source or target is a symlink */
|
||||||
|
bool clone = false;
|
||||||
|
struct stat st;
|
||||||
|
if (lstat(node_path().data(), &st) != 0) {
|
||||||
|
clone = true;
|
||||||
|
} else {
|
||||||
|
set_exist(); // cache exist result
|
||||||
|
if (is_lnk() || S_ISLNK(st.st_mode))
|
||||||
|
clone = true;
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dir_node::prepare() {
|
||||||
|
bool upgrade_skel = false;
|
||||||
|
for (auto it = children.begin(); it != children.end();) {
|
||||||
|
auto child = it->second;
|
||||||
|
if (child->need_clone()) {
|
||||||
|
if (node_type > type_id<skel_node>()) {
|
||||||
|
// Upgrade will fail, remove unsupported child node
|
||||||
|
delete child;
|
||||||
|
it = children.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Tell parent to upgrade self to skel
|
||||||
|
upgrade_skel = true;
|
||||||
|
// If child is inter_node, upgrade to module
|
||||||
|
if (auto nit = upgrade<module_node, inter_node>(it); nit != children.end()) {
|
||||||
|
it = nit;
|
||||||
|
goto increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (auto dn = dyn_cast<dir_node>(child); dn && dn->is_dir() && !dn->prepare()) {
|
||||||
|
// Upgrade child to skeleton (shall always success)
|
||||||
|
it = upgrade<skel_node>(it);
|
||||||
|
auto skel = iter_to_node<skel_node>(it);
|
||||||
|
string mirror = skel->mirror_path();
|
||||||
|
auto dir = xopen_dir(mirror.data());
|
||||||
|
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||||
|
SKIP_DOTS
|
||||||
|
// Insert mirror nodes
|
||||||
|
skel->emplace<mirror_node>(entry->d_name, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
increment:
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return !upgrade_skel;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dir_node::collect_files(const char *module, int dfd) {
|
||||||
|
auto dir = xopen_dir(xopenat(dfd, _name.data(), O_RDONLY | O_CLOEXEC));
|
||||||
|
if (!dir)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||||
|
SKIP_DOTS
|
||||||
|
if (entry->d_name == ".replace"sv) {
|
||||||
|
// Stop traversing and tell parent to upgrade self to module
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->d_type == DT_DIR) {
|
||||||
|
// Need check cause emplace could fail due to previous module dir replace
|
||||||
|
if (auto dn = emplace_or_get<inter_node>(entry->d_name, entry->d_name, module); dn &&
|
||||||
|
!dn->collect_files(module, dirfd(dir.get()))) {
|
||||||
|
// Upgrade node to module due to '.replace'
|
||||||
|
upgrade<module_node>(dn->name(), module);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emplace<module_node>(entry->d_name, module, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mount_modules() {
|
||||||
|
node_entry::mirror_dir = MAGISKTMP + "/" MIRRDIR;
|
||||||
|
node_entry::module_mnt = MAGISKTMP + "/" MODULEMNT "/";
|
||||||
|
|
||||||
|
auto root = make_unique<root_node>("");
|
||||||
|
auto system = new root_node("system");
|
||||||
|
root->insert(system);
|
||||||
|
|
||||||
|
char buf1[4096];
|
||||||
|
char buf2[4096];
|
||||||
|
LOGI("* Loading modules\n");
|
||||||
|
for (const auto &m : module_list) {
|
||||||
|
auto module = m.data();
|
||||||
|
char *b1 = buf1 + sprintf(buf1, MODULEROOT "/%s/", module);
|
||||||
|
|
||||||
|
// Read props
|
||||||
|
strcpy(b1, "system.prop");
|
||||||
|
if (access(buf1, F_OK) == 0) {
|
||||||
|
LOGI("%s: loading [system.prop]\n", module);
|
||||||
|
load_prop_file(buf1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy sepolicy rules
|
||||||
|
strcpy(b1, "sepolicy.rule");
|
||||||
|
char *b2 = buf2 + sprintf(buf2, "%s/" MIRRDIR "/persist", MAGISKTMP.data());
|
||||||
|
if (access(buf1, F_OK) == 0 && access(buf2, F_OK) == 0) {
|
||||||
|
b2 += sprintf(b2, "/magisk/%s", module);
|
||||||
|
xmkdirs(buf2, 0755);
|
||||||
|
strcpy(b2, "/sepolicy.rule");
|
||||||
|
cp_afc(buf1, buf2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether skip mounting
|
||||||
|
strcpy(b1, "skip_mount");
|
||||||
|
if (access(buf1, F_OK) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Double check whether the system folder exists
|
||||||
|
strcpy(b1, "system");
|
||||||
|
if (access(buf1, F_OK) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LOGI("%s: loading mount files\n", module);
|
||||||
|
b1[-1] = '\0';
|
||||||
|
int fd = xopen(buf1, O_RDONLY | O_CLOEXEC);
|
||||||
|
system->collect_files(module, fd);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
if (system->is_empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 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); old) {
|
||||||
|
auto new_node = new root_node(old);
|
||||||
|
root->insert(new_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root->prepare();
|
||||||
|
root->mount();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepare_modules() {
|
||||||
|
// Upgrade modules
|
||||||
|
if (auto dir = open_dir(MODULEUPGRADE); dir) {
|
||||||
|
int ufd = dirfd(dir.get());
|
||||||
|
int mfd = xopen(MODULEROOT, O_RDONLY | O_CLOEXEC);
|
||||||
|
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||||
|
if (entry->d_type == DT_DIR) {
|
||||||
|
if (entry->d_name == "."sv || entry->d_name == ".."sv)
|
||||||
|
continue;
|
||||||
|
// Cleanup old module if exists
|
||||||
|
if (faccessat(mfd, entry->d_name, F_OK, 0) == 0) {
|
||||||
|
frm_rf(xopenat(mfd, entry->d_name, O_RDONLY | O_CLOEXEC));
|
||||||
|
unlinkat(mfd, entry->d_name, AT_REMOVEDIR);
|
||||||
|
}
|
||||||
|
LOGI("Upgrade / New module: %s\n", entry->d_name);
|
||||||
|
renameat(ufd, entry->d_name, mfd, entry->d_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(mfd);
|
||||||
|
rm_rf(MODULEUPGRADE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup module mount (workaround nosuid selabel issue)
|
||||||
|
auto src = MAGISKTMP + "/" MIRRDIR MODULEROOT;
|
||||||
|
auto dest = MAGISKTMP + "/" MODULEMNT;
|
||||||
|
bind_mount(src.data(), dest.data());
|
||||||
|
|
||||||
|
restorecon();
|
||||||
|
chmod(SECURE_DIR, 0700);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collect_modules() {
|
||||||
|
auto dir = xopen_dir(MODULEROOT);
|
||||||
|
int dfd = dirfd(dir.get());
|
||||||
|
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||||
|
if (entry->d_type == DT_DIR) {
|
||||||
|
if (entry->d_name == "."sv || entry->d_name == ".."sv || entry->d_name == ".core"sv)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int modfd = xopenat(dfd, entry->d_name, O_RDONLY);
|
||||||
|
run_finally f([=]{ close(modfd); });
|
||||||
|
|
||||||
|
if (faccessat(modfd, "remove", F_OK, 0) == 0) {
|
||||||
|
LOGI("%s: remove\n", entry->d_name);
|
||||||
|
auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh";
|
||||||
|
if (access(uninstaller.data(), F_OK) == 0)
|
||||||
|
exec_script(uninstaller.data());
|
||||||
|
frm_rf(xdup(modfd));
|
||||||
|
unlinkat(dfd, entry->d_name, AT_REMOVEDIR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlinkat(modfd, "update", 0);
|
||||||
|
|
||||||
|
if (faccessat(modfd, "disable", F_OK, 0) != 0)
|
||||||
|
module_list.emplace_back(entry->d_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_modules() {
|
||||||
|
prepare_modules();
|
||||||
|
collect_modules();
|
||||||
|
|
||||||
|
// Execute module scripts
|
||||||
|
LOGI("* Running module post-fs-data scripts\n");
|
||||||
|
exec_module_script("post-fs-data", module_list);
|
||||||
|
|
||||||
|
// Recollect modules (module scripts could remove itself)
|
||||||
|
module_list.clear();
|
||||||
|
collect_modules();
|
||||||
|
|
||||||
|
mount_modules();
|
||||||
|
}
|
@ -41,6 +41,7 @@ void unlock_blocks();
|
|||||||
void post_fs_data(int client);
|
void post_fs_data(int client);
|
||||||
void late_start(int client);
|
void late_start(int client);
|
||||||
void boot_complete(int client);
|
void boot_complete(int client);
|
||||||
|
void handle_modules();
|
||||||
void remove_modules();
|
void remove_modules();
|
||||||
|
|
||||||
/*************
|
/*************
|
||||||
|
Loading…
Reference in New Issue
Block a user