diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp index 935599c19..6014831a0 100644 --- a/native/src/core/bootstages.cpp +++ b/native/src/core/bootstages.cpp @@ -290,7 +290,7 @@ static void post_fs_data() { early_abort: // We still do magic mount because root itself might need it - magic_mount(); + load_modules(); boot_state |= FLAG_POST_FS_DATA_DONE; } diff --git a/native/src/core/core.hpp b/native/src/core/core.hpp index efa7c9ee1..a02d618de 100644 --- a/native/src/core/core.hpp +++ b/native/src/core/core.hpp @@ -14,7 +14,7 @@ std::string read_certificate(int fd, int version = -1); // Module stuffs void handle_modules(); -void magic_mount(); +void load_modules(); void disable_modules(); void remove_modules(); void exec_module_scripts(const char *stage); diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index 7949e58ad..97373797d 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -9,28 +9,12 @@ #include #include "core.hpp" +#include "node.hpp" using namespace std; #define VLOGD(tag, from, to) LOGD("%-8s: %s <- %s\n", tag, to, from) -#define TYPE_MIRROR (1 << 0) /* mount from mirror */ -#define TYPE_INTER (1 << 1) /* intermediate node */ -#define TYPE_TMPFS (1 << 2) /* replace with tmpfs */ -#define TYPE_MODULE (1 << 3) /* mount from module */ -#define TYPE_ROOT (1 << 4) /* partition root */ -#define TYPE_CUSTOM (1 << 5) /* custom node type overrides all */ -#define TYPE_DIR (TYPE_INTER|TYPE_TMPFS|TYPE_ROOT) - -class node_entry; -class dir_node; -class inter_node; -class mirror_node; -class tmpfs_node; -class module_node; -class root_node; - -template static bool isa(node_entry *node); 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) @@ -38,349 +22,22 @@ static int bind_mount(const char *reason, const char *from, const char *to) { return ret; } -template uint8_t type_id() { return TYPE_CUSTOM; } -template<> uint8_t type_id() { return TYPE_DIR; } -template<> uint8_t type_id() { return TYPE_INTER; } -template<> uint8_t type_id() { return TYPE_MIRROR; } -template<> uint8_t type_id() { return TYPE_TMPFS; } -template<> uint8_t type_id() { return TYPE_MODULE; } -template<> uint8_t type_id() { return TYPE_ROOT; } - -class node_entry { -public: - virtual ~node_entry() = default; - - // Node info - 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; } - - // Don't call the following two functions before prepare - const string &node_path(); - string mirror_path() { return mirror_dir + node_path(); } - - // Tree methods - dir_node *parent() { return _parent; } - void merge(node_entry *other); - - virtual void mount() = 0; - - static string module_mnt; - static string mirror_dir; - -protected: - template - node_entry(const char *name, uint8_t file_type, T*) - : _name(name), _file_type(file_type), node_type(type_id()) {} - - template - explicit node_entry(T*) : _file_type(0), node_type(type_id()) {} - - void create_and_mount(const char *reason, const string &src); - - // Use top bit of _file_type for node exist status - bool exist() { return static_cast(_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(_file_type & ~(1 << 7)); } - -private: - friend class dir_node; - - static bool should_be_tmpfs(node_entry *child); - - // Node properties - string _name; - uint8_t _file_type; - uint8_t node_type; - - dir_node *_parent = nullptr; - - // Cache, it should only be used within prepare - string _node_path; -}; - -class dir_node : public node_entry { -public: - friend void node_entry::merge(node_entry *other); - - using map_type = map; - using iterator = map_type::iterator; - - ~dir_node() override { - for (auto &it : children) - delete it.second; - children.clear(); - } - - void collect_files(const char *module, int dfd); - - // Return true to indicate need to upgrade to skeleton - bool prepare(bool should_skip_mirror=false); - - // Default directory mount logic - void mount() override { - for (auto &pair : children) - pair.second->mount(); - } - - /*************** - * Tree Methods - ***************/ - - bool is_empty() { return children.empty(); } - - template - T *child(string_view name) { return iterator_to_node(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) { - 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 - T *emplace(string_view name, Args &&...args) { - auto fn = [&](auto) { return new T(std::forward(args)...); }; - return iterator_to_node(insert(name, type_id(), fn)); - } - - // Return inserted node, existing node with same rank, or null if rejected - template - T *emplace_or_get(string_view name, Args &&...args) { - auto fn = [&](auto) { return new T(std::forward(args)...); }; - return iterator_to_node(insert(name, type_id(), fn, true)); - } - - // Return upgraded node or null if rejected - template - T *upgrade(string_view name, Args &...args) { - return iterator_to_node(upgrade(children.find(name), args...)); - } - - bool skip_mirror() const { return _skip_mirror; } - void set_skip_mirror(bool b) { _skip_mirror = b; } - -protected: - template - dir_node(const char *name, uint8_t file_type, T *self) : node_entry(name, file_type, self) { - if constexpr (std::is_same_v) - _root = self; - } - - template - dir_node(node_entry *node, T *self) : node_entry(self) { - merge(node); - if constexpr (std::is_same_v) - _root = self; - } - - template - dir_node(const char *name, T *self) : dir_node(name, DT_DIR, self) {} - - template - T *iterator_to_node(iterator it) { - return static_cast(it == children.end() ? nullptr : it->second); - } - - // Emplace insert a new node, or upgrade if the requested type has a higher rank. - // Return iterator to new node or end() if insertion is rejected. - // If get_same is true and a node with the same rank exists, it will return that node instead. - // fn is the node construction callback. Signature: (node_ent *&) -> node_ent * - // 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 - iterator insert(iterator it, uint8_t type, const Func &fn, bool get_same); - - template - iterator insert(string_view name, uint8_t type, const Func &fn, bool get_same = false) { - return insert(children.find(name), type, fn, get_same); - } - - template - iterator upgrade(iterator it, Args &&...args); - - // dir nodes host children - map_type children; - - // Root node lookup cache - root_node *_root = nullptr; - - // Skip binding mirror for this directory - bool _skip_mirror = false; -}; - -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, const char *module) : dir_node(name, this), module(module) {} -private: - const char *module; - friend class module_node; -}; - -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); - } - - explicit module_node(inter_node *node) : module_node(node, node->module) {} - - void mount() override; -private: - const char *module; -}; - -// Don't create the following two nodes before prepare -class mirror_node : public node_entry { -public: - explicit mirror_node(dirent *entry) : node_entry(entry->d_name, entry->d_type, this) {} - void mount() override { - create_and_mount("mirror", mirror_path()); - } -}; - -class tmpfs_node : public dir_node { -public: - explicit tmpfs_node(node_entry *node); - void mount() override; -}; - -// Poor man's dynamic cast without RTTI -template -static bool isa(node_entry *node) { - return node && (node->type() & type_id()); -} -template -static T *dyn_cast(node_entry *node) { - return isa(node) ? static_cast(node) : nullptr; -} - string node_entry::module_mnt; string node_entry::mirror_dir; -// other will be deleted -void node_entry::merge(node_entry *other) { - _name.swap(other->_name); - _file_type = other->_file_type; - _parent = other->_parent; - - // Merge children if both is dir - if (auto a = dyn_cast(this)) { - if (auto b = dyn_cast(other)) { - a->_skip_mirror = b->_skip_mirror; - a->children.merge(b->children); - for (auto &pair : a->children) - pair.second->_parent = a; - } - } - delete other; -} - -const string &node_entry::node_path() { - if (_parent && _node_path.empty()) - _node_path = _parent->node_path() + '/' + _name; - return _node_path; -} - /************************* * Node Tree Construction *************************/ -template -dir_node::iterator dir_node::insert(iterator it, uint8_t type, const Func &fn, bool get_same) { - node_entry *node = nullptr; - if (it != children.end()) { - // Upgrade existing node only if higher rank - if (it->second->node_type < type) { - node = fn(it->second); - if (!node) - return children.end(); - if (it->second) - node->merge(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 (get_same && it->second->node_type != type) - return children.end(); - return it; - } - } else { - node = fn(node); - if (!node) - return children.end(); - node->_parent = this; - it = children.emplace(node->_name, node).first; - } - return it; -} - -template -dir_node::iterator dir_node::upgrade(iterator it, Args &&... args) { - return insert(it, type_id(), [&](node_entry *&ex) -> node_entry * { - if (!ex) - return nullptr; - if constexpr (!std::is_same_v) { - // Type check if type is specified - if (!isa(ex)) - return nullptr; - } - auto node = new To(static_cast(ex), std::forward(args)...); - ex = nullptr; - return node; - }, false); -} - -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; -} - tmpfs_node::tmpfs_node(node_entry *node) : dir_node(node, this) { - string mirror = mirror_path(); - if (!skip_mirror()) { + if (!skip_mirror) { + string mirror = mirror_path(); if (auto dir = open_dir(mirror.data())) { set_exist(true); for (dirent *entry; (entry = xreaddir(dir.get()));) { if (entry->d_type == DT_DIR) { // create a dummy inter_node to upgrade later - emplace(entry->d_name, entry->d_name, "mirror"); + emplace(entry->d_name, entry->d_name, ""); } else { // Insert mirror nodes emplace(entry->d_name, entry); @@ -396,34 +53,16 @@ tmpfs_node::tmpfs_node(node_entry *node) : dir_node(node, this) { } } -// We need to upgrade to tmpfs node if any child: -// - Target does not exist -// - Source or target is a symlink (since we cannot bind mount link) -bool node_entry::should_be_tmpfs(node_entry *child) { - struct stat st{}; - if (lstat(child->node_path().data(), &st) != 0) { - return true; - } else { - child->set_exist(true); - if (child->is_lnk() || S_ISLNK(st.st_mode)) - return true; - } - return false; -} - -bool dir_node::prepare(bool should_skip_mirror) { - if (!skip_mirror() && should_skip_mirror) { - set_skip_mirror(true); - } - bool to_tmpfs = false; +bool dir_node::prepare() { + bool to_tmpfs = skip_mirror; if (!exist()) { // If not exist, we need to create it by mounting tmpfs to_tmpfs = true; set_exist(true); } for (auto it = children.begin(); it != children.end();) { - if (should_be_tmpfs(it->second)) { - if (node_type > type_id()) { + if (require_tmpfs_upgrade(it->second)) { + if (_node_type > type_id()) { // Upgrade will fail, remove the unsupported child node LOGW("Unable to add: %s, skipped\n", it->second->node_path().data()); delete it->second; @@ -432,7 +71,7 @@ bool dir_node::prepare(bool should_skip_mirror) { } // Tell parent to upgrade self to tmpfs to_tmpfs = true; - // If child is inter_node and it does not (need to) exist, upgrade to module + // If child is inter_node and it does not exist, upgrade to module if (auto dn = dyn_cast(it->second)) { if (!dn->exist()) { if (auto nit = upgrade(it); nit != children.end()) { @@ -442,25 +81,30 @@ bool dir_node::prepare(bool should_skip_mirror) { } } } - if (auto dn = dyn_cast(it->second); dn && dn->prepare(skip_mirror())) { - // Upgrade child to tmpfs - it = upgrade(it); + if (auto dn = dyn_cast(it->second)) { + if (skip_mirror) { + dn->skip_mirror = true; + } + if (dn->prepare()) { + // Upgrade child to tmpfs + it = upgrade(it); + } } next_node: ++it; } - return to_tmpfs || skip_mirror(); + return to_tmpfs; } -void dir_node::collect_files(const char *module, int dfd) { - auto dir = xopen_dir(xopenat(dfd, _name.data(), O_RDONLY | O_CLOEXEC)); +void dir_node::collect_module_files(const char *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()));) { inter_node *dn; if (entry->d_name == ".replace"sv) { - set_skip_mirror(true); + skip_mirror = true; continue; } @@ -471,7 +115,7 @@ void dir_node::collect_files(const char *module, int dfd) { dn = dyn_cast(it->second); } if (dn) { - dn->collect_files(module, dirfd(dir.get())); + dn->collect_module_files(module, dirfd(dir.get())); } } else { emplace(entry->d_name, module, entry); @@ -518,10 +162,9 @@ void tmpfs_node::mount() { else getattr(parent()->node_path().data(), &a); if (!isa(parent())) { - // We don't need another layer of tmpfs if parent is skel auto worker_dir = MAGISKTMP + "/" WORKERDIR + dest; mkdirs(worker_dir.data(), 0); - create_and_mount(skip_mirror() ? "replace" : "tmpfs", worker_dir); + create_and_mount(skip_mirror ? "replace" : "tmpfs", worker_dir); } else { // We don't need another layer of tmpfs if parent is tmpfs mkdir(dest.data(), 0); @@ -560,7 +203,7 @@ public: }; static void inject_magisk_bins(root_node *system) { - auto bin = system->child("bin"); + auto bin = system->get_child("bin"); if (!bin) { bin = new inter_node("bin", ""); system->insert(bin); @@ -594,7 +237,7 @@ if (access("/system/bin/app_process" #bit, F_OK) == 0) { bind_mount("zygisk", zbin.data(), "/system/bin/app_process" #bit); \ } -void magic_mount() { +void load_modules() { node_entry::mirror_dir = MAGISKTMP + "/" MIRRDIR; node_entry::module_mnt = MAGISKTMP + "/" MODULEMNT "/"; @@ -628,7 +271,7 @@ void magic_mount() { LOGI("%s: loading mount files\n", module); b[-1] = '\0'; int fd = xopen(buf, O_RDONLY | O_CLOEXEC); - system->collect_files(module, fd); + system->collect_module_files(module, fd); close(fd); } if (MAGISKTMP != "/sbin" || !str_contains(getenv("PATH") ?: "", "/sbin")) { @@ -662,6 +305,10 @@ void magic_mount() { } } +/************************ + * Filesystem operations + ************************/ + static void prepare_modules() { // Upgrade modules if (auto dir = open_dir(MODULEUPGRADE); dir) { diff --git a/native/src/core/node.hpp b/native/src/core/node.hpp new file mode 100644 index 000000000..7921d4fb9 --- /dev/null +++ b/native/src/core/node.hpp @@ -0,0 +1,341 @@ +#pragma once + +#include +#include + +using namespace std; + +#define TYPE_MIRROR (1 << 0) /* mount from mirror */ +#define TYPE_INTER (1 << 1) /* intermediate node */ +#define TYPE_TMPFS (1 << 2) /* replace with tmpfs */ +#define TYPE_MODULE (1 << 3) /* mount from module */ +#define TYPE_ROOT (1 << 4) /* partition root */ +#define TYPE_CUSTOM (1 << 5) /* custom node type overrides all */ +#define TYPE_DIR (TYPE_INTER|TYPE_TMPFS|TYPE_ROOT) + +class node_entry; +class dir_node; +class inter_node; +class mirror_node; +class tmpfs_node; +class module_node; +class root_node; + +// Poor man's dynamic cast without RTTI +template static bool isa(node_entry *node); +template static T *dyn_cast(node_entry *node); +template uint8_t type_id() { return TYPE_CUSTOM; } +template<> uint8_t type_id() { return TYPE_DIR; } +template<> uint8_t type_id() { return TYPE_INTER; } +template<> uint8_t type_id() { return TYPE_MIRROR; } +template<> uint8_t type_id() { return TYPE_TMPFS; } +template<> uint8_t type_id() { return TYPE_MODULE; } +template<> uint8_t type_id() { 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; } + 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(); + string mirror_path() { return mirror_dir + node_path(); } + + virtual void mount() = 0; + + static string module_mnt; + static string mirror_dir; + +protected: + template + node_entry(const char *name, uint8_t file_type, T*) + : _name(name), _file_type(file_type), _node_type(type_id()) {} + + template + explicit node_entry(T*) : _file_type(0), _node_type(type_id()) {} + + void consume(node_entry *other); + void create_and_mount(const char *reason, const string &src); + + // Use top bit of _file_type for node exist status + bool exist() const { return static_cast(_file_type & (1 << 7)); } + void set_exist(bool b) { if (b) _file_type |= (1 << 7); else _file_type &= ~(1 << 7); } + uint8_t file_type() const { return static_cast(_file_type & ~(1 << 7)); } + +private: + friend class dir_node; + + template + friend bool isa(node_entry *node); + + // Node properties + string _name; + uint8_t _file_type; + const uint8_t _node_type; + + dir_node *_parent = nullptr; + + // Cache, it should only be used within prepare + string _node_path; +}; + +class dir_node : public node_entry { +public: + friend void node_entry::consume(node_entry *other); + + using map_type = map; + using iterator = map_type::iterator; + + ~dir_node() override { + for (auto &it : children) + delete it.second; + children.clear(); + } + + // Traverse through module directories to generate a tree of module files + void collect_module_files(const char *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 + T *get_child(string_view name) { return iterator_to_node(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 + T *emplace(string_view name, Args &&...args) { + auto fn = [&](auto) { return new T(std::forward(args)...); }; + return iterator_to_node(insert(name, type_id(), fn)); + } + + // Return upgraded node or null if rejected + template + T *upgrade(string_view name, Args &...args) { + return iterator_to_node(upgrade(children.find(name), args...)); + } + +protected: + template + dir_node(const char *name, T *self) : node_entry(name, DT_DIR, self) { + if constexpr (std::is_same_v) + _root = self; + } + + template + dir_node(node_entry *node, T *self) : node_entry(self) { + consume(node); + if constexpr (std::is_same_v) + _root = self; + } + + template + T *iterator_to_node(iterator it) { + return static_cast(it == children.end() ? nullptr : it->second); + } + + template + iterator insert(string_view name, uint8_t type, const Func &fn) { + return insert_at(children.find(name), type, fn); + } + + // 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 + 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 + iterator upgrade(iterator it, Args &&...args) { + return insert_at(it, type_id(), [&](node_entry *&ex) -> node_entry * { + if (!ex) + return nullptr; + if constexpr (!std::is_same_v) { + // Type check if type is specified + if (!isa(ex)) + return nullptr; + } + auto node = new To(static_cast(ex), std::forward(args)...); + ex = nullptr; + return node; + }); + } + + // dir nodes host children + map_type children; + + // Skip binding mirror for this directory + bool skip_mirror = false; + +private: + // Root node lookup cache + root_node *_root = nullptr; + + // We need to upgrade to tmpfs node if any child: + // - Target does not exist + // - Source or target is a symlink (since we cannot bind mount link) + bool require_tmpfs_upgrade(node_entry *child) { + struct stat st{}; + if (lstat(child->node_path().data(), &st) != 0) { + return true; + } else { + child->set_exist(true); + if (child->is_lnk() || S_ISLNK(st.st_mode)) + return true; + } + return false; + } +}; + +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, const char *module) : dir_node(name, this), module(module) {} +private: + const char *module; + friend class module_node; +}; + +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) { + consume(node); + } + + explicit module_node(inter_node *node) : module_node(node, node->module) {} + + void mount() override; +private: + const char *module; +}; + +// Don't create the following two nodes before prepare +class mirror_node : public node_entry { +public: + explicit mirror_node(dirent *entry) : node_entry(entry->d_name, entry->d_type, this) {} + void mount() override { + create_and_mount("mirror", mirror_path()); + } +}; + +class tmpfs_node : public dir_node { +public: + explicit tmpfs_node(node_entry *node); + void mount() override; +}; + +template +static bool isa(node_entry *node) { + return node && (node->_node_type & type_id()); +} +template +static T *dyn_cast(node_entry *node) { + return isa(node) ? static_cast(node) : nullptr; +} + +const string &node_entry::node_path() { + if (_parent && _node_path.empty()) + _node_path = _parent->node_path() + '/' + _name; + return _node_path; +} + +void node_entry::consume(node_entry *other) { + _name.swap(other->_name); + _file_type = other->_file_type; + _parent = other->_parent; + + // Merge children if both is dir + if (auto a = dyn_cast(this)) { + if (auto b = dyn_cast(other)) { + a->skip_mirror = b->skip_mirror; + a->children.merge(b->children); + for (auto &pair : a->children) + pair.second->_parent = a; + } + } + delete other; +}