Rewrite skel_node mounting and construction logic

Close #2725
This commit is contained in:
topjohnwu 2020-04-24 02:07:46 -07:00
parent 2468f5a6c4
commit 15f155100c

View File

@ -17,13 +17,13 @@ using namespace std;
#define VLOGI(tag, from, to) LOGI("%-8s: %s\n", tag, to) #define VLOGI(tag, from, to) LOGI("%-8s: %s\n", tag, to)
#endif #endif
#define TYPE_INTER (1 << 0) /* intermediate node */ #define TYPE_MIRROR (1 << 0) /* mount from mirror */
#define TYPE_MIRROR (1 << 1) /* mount from mirror */ #define TYPE_INTER (1 << 1) /* intermediate node */
#define TYPE_SKEL (1 << 2) /* replace with tmpfs */ #define TYPE_SKEL (1 << 2) /* replace with tmpfs */
#define TYPE_MODULE (1 << 3) /* mount from module */ #define TYPE_MODULE (1 << 3) /* mount from module */
#define TYPE_ROOT (1 << 4) /* partition root */ #define TYPE_ROOT (1 << 4) /* partition root */
#define TYPE_CUSTOM (1 << 5) /* custom node type overrides all */ #define TYPE_CUSTOM (1 << 5) /* custom node type overrides all */
#define TYPE_DIR (TYPE_INTER|TYPE_MIRROR|TYPE_SKEL|TYPE_ROOT) #define TYPE_DIR (TYPE_INTER|TYPE_SKEL|TYPE_ROOT)
vector<string> module_list; vector<string> module_list;
@ -86,14 +86,14 @@ protected:
/* Use top bit of _file_type for node exist status */ /* Use top bit of _file_type for node exist status */
bool exist() { return static_cast<bool>(_file_type & (1 << 7)); } bool exist() { return static_cast<bool>(_file_type & (1 << 7)); }
void set_exist() { _file_type |= (1 << 7); } void set_exist(bool b) { if (b) _file_type |= (1 << 7); else _file_type &= ~(1 << 7); }
uint8_t file_type() { return static_cast<uint8_t>(_file_type & ~(1 << 7)); } uint8_t file_type() { return static_cast<uint8_t>(_file_type & ~(1 << 7)); }
private: private:
friend void merge_node(node_entry *a, node_entry *b); friend void merge_node(node_entry *a, node_entry *b);
friend class dir_node; friend class dir_node;
bool need_clone(); bool need_skel_upgrade(node_entry *child);
// Node properties // Node properties
string _name; string _name;
@ -113,7 +113,11 @@ public:
typedef map<string_view, node_entry *> map_type; typedef map<string_view, node_entry *> map_type;
typedef map_type::iterator map_iter; typedef map_type::iterator map_iter;
~dir_node() override; ~dir_node() override {
for (auto &it : children)
delete it.second;
children.clear();
}
// Return false to indicate need to upgrade to module // Return false to indicate need to upgrade to module
bool collect_files(const char *module, int dfd); bool collect_files(const char *module, int dfd);
@ -191,13 +195,6 @@ protected:
template<class T> template<class T>
dir_node(const char *name, T *self) : dir_node(name, DT_DIR, self) {} 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> template<class T = node_entry>
T *iter_to_node(const map_iter &it) { T *iter_to_node(const map_iter &it) {
return reinterpret_cast<T*>(it == children.end() ? nullptr : it->second); return reinterpret_cast<T*>(it == children.end() ? nullptr : it->second);
@ -226,6 +223,12 @@ private:
return node; return node;
}); });
} }
// dir nodes host children
map_type children;
// Root node lookup cache
root_node *_root;
}; };
class root_node : public dir_node { class root_node : public dir_node {
@ -243,22 +246,6 @@ private:
friend class module_node; 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 { class module_node : public node_entry {
public: public:
module_node(const char *module, dirent *entry) module_node(const char *module, dirent *entry)
@ -270,45 +257,23 @@ public:
module_node(inter_node *node) : module_node(node, node->module) {} module_node(inter_node *node) : module_node(node, node->module) {}
void mount() override { 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: private:
const char *module; const char *module;
}; };
class mirror_node : public dir_node { class mirror_node : public node_entry {
public: public:
mirror_node(dirent *entry) : dir_node(entry->d_name, entry->d_type, this) {} mirror_node(dirent *entry) : node_entry(entry->d_name, entry->d_type, this) {}
void mount() override { void mount() override {
create_and_mount(mirror_path()); create_and_mount(mirror_path());
dir_node::mount();
} }
}; };
class skel_node : public dir_node { class skel_node : public dir_node {
public: public:
skel_node(node_entry *node) : dir_node(node, this) {} skel_node(node_entry *node);
void mount() override;
void mount() override {
string src = mirror_path();
const string &dest = node_path();
file_attr a;
getattr(src.data(), &a);
mkdir(dest.data(), 0);
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 // Poor man's dynamic cast without RTTI
@ -347,11 +312,9 @@ const string &node_entry::node_path() {
return _node_path; return _node_path;
} }
dir_node::~dir_node() { /*************************
for (auto &it : children) * Node Tree Construction
delete it.second; *************************/
children.clear();
}
template<typename Func> template<typename Func>
dir_node::map_iter dir_node::insert(dir_node::map_iter it, uint8_t type, Func fn, bool allow_same) { dir_node::map_iter dir_node::insert(dir_node::map_iter it, uint8_t type, Func fn, bool allow_same) {
@ -395,66 +358,71 @@ node_entry* dir_node::extract(string_view name) {
return nullptr; return nullptr;
} }
bool node_entry::need_clone() { skel_node::skel_node(node_entry *node) : dir_node(node, this) {
/* We need to upgrade parent to skeleton if: string mirror = mirror_path();
if (auto dir = open_dir(mirror.data()); dir) {
set_exist(true);
for (dirent *entry; (entry = xreaddir(dir.get()));) {
// Insert mirror nodes
emplace<mirror_node>(entry->d_name, entry);
}
} else {
// It is actually possible that mirror does not exist (nested mount points)
// Set self to non exist so this node will be ignored at mount
set_exist(false);
return;
}
for (auto it = children.begin(); it != children.end(); ++it) {
// Need to upgrade all inter_node children to skel_node
if (isa<inter_node>(it->second))
it = upgrade<skel_node>(it);
}
}
bool node_entry::need_skel_upgrade(node_entry *child) {
/* We need to upgrade to skeleton if:
* - Target does not exist * - Target does not exist
* - Source or target is a symlink */ * - Source or target is a symlink */
bool clone = false; bool upgrade = false;
struct stat st; struct stat st;
if (lstat(node_path().data(), &st) != 0) { if (lstat(child->node_path().data(), &st) != 0) {
clone = true; upgrade = true;
} else { } else {
set_exist(); // cache exist result child->set_exist(true);
if (is_lnk() || S_ISLNK(st.st_mode)) if (child->is_lnk() || S_ISLNK(st.st_mode))
clone = true; upgrade = true;
} }
return clone; return upgrade;
} }
bool dir_node::prepare() { bool dir_node::prepare() {
bool upgrade_skel = false; bool to_skel = false;
for (auto it = children.begin(); it != children.end();) { for (auto it = children.begin(); it != children.end();) {
auto child = it->second; if (need_skel_upgrade(it->second)) {
if (child->need_clone()) {
if (node_type > type_id<skel_node>()) { if (node_type > type_id<skel_node>()) {
// Upgrade will fail // Upgrade will fail, remove the unsupported child node
goto delete_node; delete it->second;
it = children.erase(it);
continue;
} }
// Tell parent to upgrade self to skel // Tell parent to upgrade self to skel
upgrade_skel = true; to_skel = true;
// If child is inter_node, upgrade to module // If child is inter_node, upgrade to module
if (auto nit = upgrade<module_node, inter_node>(it); nit != children.end()) { if (auto nit = upgrade<module_node, inter_node>(it); nit != children.end()) {
it = nit; it = nit;
goto next_node; goto next_node;
} }
} }
if (auto dn = dyn_cast<dir_node>(child); dn && dn->is_dir() && !dn->prepare()) { if (auto dn = dyn_cast<dir_node>(it->second); dn && dn->is_dir() && !dn->prepare()) {
string mirror = dn->mirror_path(); // Upgrade child to skeleton
if (access(mirror.data(), F_OK) != 0) {
// It is actually possible that mirror does not exist
goto delete_node;
}
// Upgrade child to skeleton (shall always success)
it = upgrade<skel_node>(it); it = upgrade<skel_node>(it);
auto skel = iter_to_node<skel_node>(it);
auto dir = xopen_dir(mirror.data());
for (dirent *entry; (entry = xreaddir(dir.get()));) {
// Insert mirror nodes
skel->emplace<mirror_node>(entry->d_name, entry);
}
} }
next_node: next_node:
++it; ++it;
continue; continue;
delete_node:
// Remove the unsupported child node
delete it->second;
it = children.erase(it);
continue;
} }
return !upgrade_skel; return !to_skel;
} }
bool dir_node::collect_files(const char *module, int dfd) { bool dir_node::collect_files(const char *module, int dfd) {
@ -482,6 +450,57 @@ bool dir_node::collect_files(const char *module, int dfd) {
return true; return true;
} }
/************************
* Mount Implementations
************************/
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());
}
}
void module_node::mount() {
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());
}
void skel_node::mount() {
if (!exist())
return;
string src = mirror_path();
const string &dest = node_path();
file_attr a;
getattr(src.data(), &a);
mkdir(dest.data(), 0);
if (!isa<skel_node>(parent())) {
// We don't need another layer of tmpfs if parent is skel
xmount("tmpfs", dest.data(), "tmpfs", 0, nullptr);
VLOGI("mnt_tmp", "tmpfs", dest.data());
}
setattr(dest.data(), &a);
dir_node::mount();
}
/****************
* Magisk Stuffs
****************/
class magisk_node : public node_entry { class magisk_node : public node_entry {
public: public:
magisk_node(const char *name) : node_entry(name, DT_REG, this) {} magisk_node(const char *name) : node_entry(name, DT_REG, this) {}