Commit a73e7e9f authored by topjohnwu's avatar topjohnwu

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.
parent ab853e1f
...@@ -20,6 +20,7 @@ LOCAL_SRC_FILES := \ ...@@ -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 @@ ...@@ -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) { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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) { ...@@ -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();
} }
......
#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(); ...@@ -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();
/************* /*************
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment