diff --git a/src/Makefile b/src/Makefile index 919fe5b..880f300 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,19 +1,18 @@ PROJECT = magic_mount -VERSION ?= v0.1.5 +VERSION ?= v0.2.0 # use zig cc -ZIG ?= zig -CC := $(ZIG) cc +CC := zig cc STRIPPER := strip # source files -SRCS := utils.c magic_mount.c main.c +SRCS := utils.c ksu.c module_tree.c magic_mount.c main.c # output directory OUTDIR := bin # common flags -CFLAGS_COMMON := -Wl,--gc-sections -static -Wpedantic -Werror +CFLAGS_COMMON := -std=c23 -D_POSIX_C_SOURCE=200809L -Wl,--gc-sections -static -Wpedantic -Werror CFLAGS_COMMON += -DVERSION=\"$(VERSION)\" LDFLAGS_COMMON := -static diff --git a/src/ksu.c b/src/ksu.c new file mode 100644 index 0000000..47f2289 --- /dev/null +++ b/src/ksu.c @@ -0,0 +1,55 @@ +#include "ksu.h" +#include "utils.h" + +#include +#include +#include +#include + +long syscall(long number, ...); + +static atomic_int g_driver_fd = -1; +static atomic_flag g_driver_fd_initialized = ATOMIC_FLAG_INIT; + +static void ksu_grab_fd_once(void) +{ + int fd = -1; + + syscall(SYS_reboot, KSU_INSTALL_MAGIC1, KSU_INSTALL_MAGIC2, 0, (void *)&fd); + + if (fd < 0) { + LOGW("failed to grab KSU driver fd: %d", fd); + } else { + LOGD("grabbed KSU driver fd: %d", fd); + } + + atomic_store(&g_driver_fd, fd); +} + +static int ksu_grab_fd(void) +{ + if (!atomic_flag_test_and_set(&g_driver_fd_initialized)) { + ksu_grab_fd_once(); + } + return atomic_load(&g_driver_fd); +} + +int ksu_send_unmountable(const char *mntpoint) +{ + struct ksu_add_try_umount_cmd cmd = {0}; + int fd = ksu_grab_fd(); + + if (fd < 0) + return -1; + + cmd.arg = (uint64_t)mntpoint; + cmd.flags = 0x2; + cmd.mode = 1; + + if (ioctl(fd, KSU_IOCTL_ADD_TRY_UMOUNT, &cmd) < 0) { + LOGE("ioctl KSU_IOCTL_ADD_TRY_UMOUNT failed: %s", strerror(errno)); + return -1; + } + + return 0; +} diff --git a/src/ksu.h b/src/ksu.h new file mode 100644 index 0000000..74adea5 --- /dev/null +++ b/src/ksu.h @@ -0,0 +1,21 @@ +#ifndef KSU_H +#define KSU_H + +#include +#include +#include + +#define KSU_INSTALL_MAGIC1 0xDEADBEEF +#define KSU_INSTALL_MAGIC2 0xCAFEBABE + +struct ksu_add_try_umount_cmd { + uint64_t arg; // char *, mountpoint + uint32_t flags; // flags + uint8_t mode; // 0:wipe_list 1:add_to_list 2:delete_entry +}; + +#define KSU_IOCTL_ADD_TRY_UMOUNT _IOC(_IOC_WRITE, 'K', 18, 0) + +int ksu_send_unmountable(const char *mntpoint); + +#endif /* KSU_H */ diff --git a/src/magic_mount.c b/src/magic_mount.c index 0a14766..6c3c831 100644 --- a/src/magic_mount.c +++ b/src/magic_mount.c @@ -1,725 +1,34 @@ #include "magic_mount.h" #include "utils.h" +#include "module_tree.h" +#include "ksu.h" #include #include #include -#include -#include #include #include #include #include #include -#include #include #include -#include -#include -#include -MountStats g_stats = { 0 }; - -const char *g_module_dir = DEFAULT_MODULE_DIR; -const char *g_mount_source = DEFAULT_MOUNT_SOURCE; - -char **g_failed_modules = NULL; -int g_failed_modules_count = 0; - -char **g_extra_parts = NULL; -int g_extra_parts_count = 0; - -static atomic_int g_driver_fd = -1; -static atomic_flag g_driver_fd_initialized = ATOMIC_FLAG_INIT; - -static void grab_fd_once(void) +void magic_mount_init(MagicMount *ctx) { - int fd = -1; - syscall(SYS_reboot, KSU_INSTALL_MAGIC1, KSU_INSTALL_MAGIC2, 0, (void *)&fd); - - if (fd < 0) { - LOGW("failed to grab KSU driver fd: %d", fd); - } else { - LOGD("grabbed KSU driver fd: %d", fd); - } - - atomic_store(&g_driver_fd, fd); + if (!ctx) return; + memset(ctx, 0, sizeof(*ctx)); + ctx->module_dir = DEFAULT_MODULE_DIR; + ctx->mount_source = DEFAULT_MOUNT_SOURCE; } -static int grab_fd(void) +void magic_mount_cleanup(MagicMount *ctx) { - if (!atomic_flag_test_and_set(&g_driver_fd_initialized)) { - grab_fd_once(); - } - - return atomic_load(&g_driver_fd); + if (!ctx) return; + module_tree_cleanup(ctx); } -static void send_unmountable(const char *mntpoint) -{ - struct ksu_add_try_umount_cmd cmd = {0}; - int fd = grab_fd(); - - if (fd < 0) - return; - - cmd.arg = (uint64_t)mntpoint; - cmd.flags = 0x2; - cmd.mode = 1; - - if (ioctl(fd, KSU_IOCTL_ADD_TRY_UMOUNT, &cmd) < 0) { - LOGE("ioctl KSU_IOCTL_ADD_TRY_UMOUNT failed: %s", strerror(errno)); - } -} - -static void register_module_failure(const char *module_name) -{ - if (!module_name) - return; - - for (int i = 0; i < g_failed_modules_count; ++i) { - if (strcmp(g_failed_modules[i], module_name) == 0) - return; - } - - char **arr = - realloc(g_failed_modules, - (size_t)(g_failed_modules_count + 1) * sizeof(char *)); - if (!arr) - return; - - g_failed_modules = arr; - g_failed_modules[g_failed_modules_count] = strdup(module_name); - if (!g_failed_modules[g_failed_modules_count]) - return; - - g_failed_modules_count++; -} - -void add_extra_part_token(const char *start, size_t len) -{ - while (len > 0 && (start[0] == ' ' || start[0] == '\t')) { - start++; - len--; - } - while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t')) - len--; - - if (len == 0) - return; - - char *name = malloc(len + 1); - if (!name) - return; - - memcpy(name, start, len); - name[len] = '\0'; - - char **arr = realloc(g_extra_parts, - (size_t)(g_extra_parts_count + 1) * sizeof(char *)); - if (!arr) { - free(name); - return; - } - - g_extra_parts = arr; - g_extra_parts[g_extra_parts_count++] = name; -} - -typedef enum { - NFT_REGULAR, - NFT_DIRECTORY, - NFT_SYMLINK, - NFT_WHITEOUT -} NodeFileType; - -typedef struct Node { - char *name; - NodeFileType type; - struct Node **children; - size_t child_count; - char *module_path; - char *module_name; - bool replace; - bool skip; - bool done; -} Node; - -static Node *node_new(const char *name, NodeFileType t) -{ - Node *n = calloc(1, sizeof(Node)); - if (!n) - return NULL; - - n->name = strdup(name ? name : ""); - n->type = t; - return n; -} - -static void node_free(Node *n) -{ - if (!n) - return; - - for (size_t i = 0; i < n->child_count; ++i) - node_free(n->children[i]); - - free(n->children); - free(n->name); - free(n->module_path); - free(n->module_name); - free(n); -} - -static NodeFileType node_type_from_stat(const struct stat *st) -{ - if (S_ISCHR(st->st_mode) && st->st_rdev == 0) - return NFT_WHITEOUT; - if (S_ISREG(st->st_mode)) - return NFT_REGULAR; - if (S_ISDIR(st->st_mode)) - return NFT_DIRECTORY; - if (S_ISLNK(st->st_mode)) - return NFT_SYMLINK; - return NFT_WHITEOUT; -} - -static bool dir_is_replace(const char *path) -{ - char buf[8]; - ssize_t len = lgetxattr(path, REPLACE_DIR_XATTR, buf, sizeof(buf) - 1); - - if (len > 0) { - buf[len] = '\0'; - if (strcmp(buf, "y") == 0) - return true; - } - - int dirfd = open(path, O_RDONLY | O_DIRECTORY); - if (dirfd < 0) return false; - - bool exists = (faccessat(dirfd, REPLACE_DIR_FILE_NAME, F_OK, 0) == 0); - close(dirfd); - return exists; -} - -static Node *node_new_module(const char *name, const char *path, - const char *module_name) -{ - struct stat st; - if (lstat(path, &st) < 0) - return NULL; - - if (!(S_ISCHR(st.st_mode) || S_ISREG(st.st_mode) || - S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) - return NULL; - - NodeFileType t = node_type_from_stat(&st); - Node *n = node_new(name, t); - if (!n) - return NULL; - - n->module_path = strdup(path); - if (module_name) - n->module_name = strdup(module_name); - n->replace = (t == NFT_DIRECTORY) && dir_is_replace(path); - - g_stats.nodes_total++; - return n; -} - -static int node_add_child(Node *parent, Node *child) -{ - Node **arr = realloc(parent->children, - (parent->child_count + 1) * sizeof(Node *)); - if (!arr) { - errno = ENOMEM; - return -1; - } - - parent->children = arr; - parent->children[parent->child_count++] = child; - return 0; -} - -static Node *node_find_child(Node *parent, const char *name) -{ - for (size_t i = 0; i < parent->child_count; ++i) { - if (strcmp(parent->children[i]->name, name) == 0) - return parent->children[i]; - } - return NULL; -} - -static Node *node_take_child(Node *parent, const char *name) -{ - for (size_t i = 0; i < parent->child_count; ++i) { - if (strcmp(parent->children[i]->name, name) == 0) { - Node *n = parent->children[i]; - memmove(&parent->children[i], &parent->children[i + 1], - (parent->child_count - i - 1) * sizeof(Node *)); - parent->child_count--; - return n; - } - } - return NULL; -} - -static bool module_disabled(const char *mod_dir) -{ - char buf[PATH_MAX]; - - path_join(mod_dir, DISABLE_FILE_NAME, buf, sizeof(buf)); - if (path_exists(buf)) - return true; - - path_join(mod_dir, REMOVE_FILE_NAME, buf, sizeof(buf)); - if (path_exists(buf)) - return true; - - path_join(mod_dir, SKIP_MOUNT_FILE_NAME, buf, sizeof(buf)); - if (path_exists(buf)) - return true; - - return false; -} - -static int node_collect(Node *self, const char *dir, const char *module_name, - bool *has_any) -{ - DIR *d = opendir(dir); - if (!d) { - LOGE("opendir %s: %s", dir, strerror(errno)); - return -1; - } - - struct dirent *de; - bool any = false; - char path[PATH_MAX]; - - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) - continue; - - if (path_join(dir, de->d_name, path, sizeof(path)) != 0) { - closedir(d); - return -1; - } - - Node *child = node_find_child(self, de->d_name); - if (!child) { - Node *n = node_new_module(de->d_name, path, module_name); - if (n) { - if (node_add_child(self, n) != 0) { - node_free(n); - closedir(d); - return -1; - } - child = n; - } - } - - if (child) { - if (child->type == NFT_DIRECTORY) { - bool sub = false; - if (node_collect(child, path, module_name, &sub) != 0) { - closedir(d); - return -1; - } - if (sub || child->replace) - any = true; - } else { - any = true; - } - } - } - - closedir(d); - *has_any = any; - return 0; -} - -static int handle_symlink_compatibility(Node *system, const char *part_name) -{ - if (!system || !part_name) - return -1; - - Node *sys_child = node_find_child(system, part_name); - if (!sys_child || sys_child->type != NFT_SYMLINK) - return 0; - - if (!sys_child->module_path) - return 0; - - char link_target[PATH_MAX]; - ssize_t len = readlink(sys_child->module_path, link_target, sizeof(link_target) - 1); - if (len < 0) { - LOGW("readlink %s failed: %s", sys_child->module_path, strerror(errno)); - return 0; - } - link_target[len] = '\0'; - - size_t target_len = strlen(link_target); - while (target_len > 0 && link_target[target_len - 1] == '/') { - link_target[--target_len] = '\0'; - } - - if (target_len == 0) { - LOGW("empty symlink target for %s", part_name); - return 0; - } - - // check ../ or ./ - char expected_relative[PATH_MAX]; - char expected_absolute[PATH_MAX]; - snprintf(expected_relative, sizeof(expected_relative), "../%s", part_name); - snprintf(expected_absolute, sizeof(expected_absolute), "/%s", part_name); - - bool is_relative = (strcmp(link_target, expected_relative) == 0); - bool is_absolute = (strcmp(link_target, expected_absolute) == 0); - - if (!is_relative && !is_absolute) { - LOGD("symlink %s -> %s (not pointing to sibling directory, expected %s or %s)", - part_name, link_target, expected_relative, expected_absolute); - return 0; - } - - LOGI("found compatible symlink: system/%s -> %s", part_name, link_target); - - DIR *mod_dir = opendir(g_module_dir); - if (!mod_dir) { - LOGE("opendir %s: %s", g_module_dir, strerror(errno)); - return -1; - } - - struct dirent *mod_de; - bool found_real_dir = false; - char real_part_path[PATH_MAX]; - char module_name_buf[256] = {0}; - - while ((mod_de = readdir(mod_dir))) { - if (!strcmp(mod_de->d_name, ".") || !strcmp(mod_de->d_name, "..")) - continue; - - char mod_path[PATH_MAX]; - char part_path[PATH_MAX]; - - if (path_join(g_module_dir, mod_de->d_name, mod_path, sizeof(mod_path)) != 0) - continue; - - struct stat mod_st; - if (stat(mod_path, &mod_st) < 0 || !S_ISDIR(mod_st.st_mode)) - continue; - - if (module_disabled(mod_path)) - continue; - - // check / - if (path_join(mod_path, part_name, part_path, sizeof(part_path)) != 0) - continue; - - if (path_is_dir(part_path)) { - found_real_dir = true; - strncpy(real_part_path, part_path, sizeof(real_part_path) - 1); - real_part_path[sizeof(real_part_path) - 1] = '\0'; - strncpy(module_name_buf, mod_de->d_name, sizeof(module_name_buf) - 1); - module_name_buf[sizeof(module_name_buf) - 1] = '\0'; - break; - } - } - closedir(mod_dir); - - if (!found_real_dir) { - LOGD("no real directory found for %s, keeping symlink", part_name); - return 0; - } - - LOGI("symlink compatibility: system/%s -> %s, real dir in module '%s'", - part_name, link_target, module_name_buf); - - Node *new_part = node_new(part_name, NFT_DIRECTORY); - if (!new_part) { - LOGE("failed to create node for %s", part_name); - return -1; - } - - bool part_has_any = false; - if (node_collect(new_part, real_part_path, module_name_buf, &part_has_any) != 0) { - LOGE("failed to collect %s from %s", part_name, real_part_path); - node_free(new_part); - return -1; - } - - if (!part_has_any) { - LOGD("no content in %s, keeping symlink", part_name); - node_free(new_part); - return 0; - } - - Node *removed = node_take_child(system, part_name); - if (removed) { - node_free(removed); - LOGD("removed symlink node: system/%s", part_name); - } - - new_part->module_name = strdup(module_name_buf); - if (node_add_child(system, new_part) != 0) { - LOGE("failed to add directory node for %s", part_name); - node_free(new_part); - return -1; - } - - LOGI("replaced symlink with directory node: %s (from module '%s')", - part_name, module_name_buf); - - return 0; -} - -static int handle_all_symlink_compatibility(Node *system) -{ - if (!system) - return -1; - - const char *builtin_parts[] = { - "vendor", - "system_ext", - "product", - }; - - for (size_t i = 0; i < sizeof(builtin_parts) / sizeof(builtin_parts[0]); ++i) { - if (handle_symlink_compatibility(system, builtin_parts[i]) != 0) { - LOGE("failed to handle symlink compatibility for %s", builtin_parts[i]); - } - } - - for (int i = 0; i < g_extra_parts_count; ++i) { - if (handle_symlink_compatibility(system, g_extra_parts[i]) != 0) { - LOGE("failed to handle symlink compatibility for extra part %s", - g_extra_parts[i]); - } - } - - return 0; -} - -static int collect_partition_from_modules(const char *part_name, Node *parent_node) -{ - if (!part_name || !parent_node) - return -1; - - DIR *mod_dir = opendir(g_module_dir); - if (!mod_dir) { - LOGE("opendir %s: %s", g_module_dir, strerror(errno)); - return -1; - } - - struct dirent *mod_de; - bool has_any = false; - char mod_path[PATH_MAX]; - char part_path[PATH_MAX]; - - while ((mod_de = readdir(mod_dir))) { - if (!strcmp(mod_de->d_name, ".") || !strcmp(mod_de->d_name, "..")) - continue; - - if (path_join(g_module_dir, mod_de->d_name, mod_path, sizeof(mod_path)) != 0) - continue; - - struct stat mod_st; - if (stat(mod_path, &mod_st) < 0 || !S_ISDIR(mod_st.st_mode)) - continue; - - if (module_disabled(mod_path)) - continue; - - if (path_join(mod_path, part_name, part_path, sizeof(part_path)) != 0) - continue; - - if (!path_is_dir(part_path)) - continue; - - LOGD("collecting %s from module %s", part_name, mod_de->d_name); - - bool sub = false; - if (node_collect(parent_node, part_path, mod_de->d_name, &sub) != 0) { - closedir(mod_dir); - return -1; - } - - if (sub) - has_any = true; - } - - closedir(mod_dir); - return has_any ? 0 : 1; -} - -static Node *collect_root(void) -{ - const char *mdir = g_module_dir; - Node *root = node_new("", NFT_DIRECTORY); - Node *system = node_new("system", NFT_DIRECTORY); - - if (!root || !system) { - node_free(root); - node_free(system); - return NULL; - } - - DIR *d = opendir(mdir); - if (!d) { - LOGE("opendir %s: %s", mdir, strerror(errno)); - node_free(root); - node_free(system); - return NULL; - } - - struct dirent *de; - bool has_any = false; - char mod[PATH_MAX]; - char mod_sys[PATH_MAX]; - - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) - continue; - - if (path_join(mdir, de->d_name, mod, sizeof(mod)) != 0) { - closedir(d); - node_free(root); - node_free(system); - return NULL; - } - - struct stat st; - if (stat(mod, &st) < 0 || !S_ISDIR(st.st_mode)) - continue; - - if (module_disabled(mod)) - continue; - - if (path_join(mod, "system", mod_sys, sizeof(mod_sys)) != 0) { - closedir(d); - node_free(root); - node_free(system); - return NULL; - } - - if (!path_is_dir(mod_sys)) - continue; - - LOGD("collecting module %s", de->d_name); - g_stats.modules_total++; - - bool sub = false; - if (node_collect(system, mod_sys, de->d_name, &sub) != 0) { - closedir(d); - node_free(root); - node_free(system); - return NULL; - } - if (sub) - has_any = true; - } - - closedir(d); - - if (!has_any) { - node_free(root); - node_free(system); - return NULL; - } - - g_stats.nodes_total += 2; - - if (handle_all_symlink_compatibility(system) != 0) { - LOGW("symlink compatibility handling encountered errors (continuing anyway)"); - } - - struct { - const char *name; - bool need_symlink; - } builtin_parts[] = { - { "vendor", true }, - { "system_ext", true }, - { "product", true }, - }; - - char rp[PATH_MAX]; - char sp[PATH_MAX]; - - for (size_t i = 0; i < sizeof(builtin_parts) / sizeof(builtin_parts[0]); ++i) { - if (path_join("/", builtin_parts[i].name, rp, sizeof(rp)) != 0 || - path_join("/system", builtin_parts[i].name, sp, sizeof(sp)) != 0) { - node_free(root); - node_free(system); - return NULL; - } - - if (!path_is_dir(rp)) - continue; - - if (builtin_parts[i].need_symlink && !path_is_symlink(sp)) - continue; - - Node *child = node_take_child(system, builtin_parts[i].name); - if (child && node_add_child(root, child) != 0) { - node_free(child); - node_free(root); - node_free(system); - return NULL; - } - } - - for (int i = 0; i < g_extra_parts_count; ++i) { - const char *name = g_extra_parts[i]; - - if (path_join("/", name, rp, sizeof(rp)) != 0) { - node_free(root); - node_free(system); - return NULL; - } - - if (!path_is_dir(rp)) - continue; - - Node *child = node_new(name, NFT_DIRECTORY); - if (!child) { - node_free(root); - node_free(system); - return NULL; - } - - int ret = collect_partition_from_modules(name, child); - if (ret == 0) { - LOGI("collected extra partition %s from module root", name); - - if (node_add_child(root, child) != 0) { - node_free(child); - node_free(root); - node_free(system); - return NULL; - } - } else if (ret == 1) { - node_free(child); - LOGD("no content found for extra partition %s", name); - } else { - node_free(child); - node_free(root); - node_free(system); - return NULL; - } - } - - if (node_add_child(root, system) != 0) { - node_free(root); - node_free(system); - return NULL; - } - - return root; -} - -static int clone_symlink(const char *src, const char *dst) +static int mm_clone_symlink(const char *src, const char *dst) { char target[PATH_MAX]; @@ -736,20 +45,87 @@ static int clone_symlink(const char *src, const char *dst) return -1; } - char *con = NULL; - if (get_selinux(src, &con) == 0 && con) { - set_selinux(dst, con); - free(con); - } + (void)copy_selcon(src, dst); LOGD("clone symlink %s -> %s (%s)", src, dst, target); return 0; } -static int mirror(const char *path, const char *work, const char *name); +static int mm_mirror_entry(MagicMount *ctx, + const char *path, const char *work, const char *name); -static int do_magic(const char *base, const char *wbase, Node *node, - bool has_tmpfs) +static int mm_apply_node_recursive(MagicMount *ctx, + const char *base, const char *wbase, + Node *node, bool has_tmpfs); + +static int mm_mirror_entry(MagicMount *ctx, + const char *path, const char *work, const char *name) +{ + char src[PATH_MAX]; + char dst[PATH_MAX]; + + if (path_join(path, name, src, sizeof(src)) != 0 || + path_join(work, name, dst, sizeof(dst)) != 0) + return -1; + + struct stat st; + if (lstat(src, &st) < 0) { + LOGW("lstat %s: %s", src, strerror(errno)); + return 0; + } + + if (S_ISREG(st.st_mode)) { + int fd = open(dst, O_CREAT | O_WRONLY, st.st_mode & 07777); + if (fd < 0) { + LOGE("create %s: %s", dst, strerror(errno)); + return -1; + } + close(fd); + + if (mount(src, dst, NULL, MS_BIND, NULL) < 0) { + LOGE("bind %s->%s: %s", src, dst, strerror(errno)); + return -1; + } + } else if (S_ISDIR(st.st_mode)) { + if (mkdir(dst, st.st_mode & 07777) < 0 && errno != EEXIST) { + LOGE("mkdir %s: %s", dst, strerror(errno)); + return -1; + } + + chmod(dst, st.st_mode & 07777); + chown(dst, st.st_uid, st.st_gid); + + (void)copy_selcon(src, dst); + + DIR *d = opendir(src); + if (!d) { + LOGE("opendir %s: %s", src, strerror(errno)); + return -1; + } + + struct dirent *de; + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || + !strcmp(de->d_name, "..")) { + continue; + } + if (mm_mirror_entry(ctx, src, dst, de->d_name) != 0) { + closedir(d); + return -1; + } + } + closedir(d); + } else if (S_ISLNK(st.st_mode)) { + if (mm_clone_symlink(src, dst) != 0) + return -1; + } + + return 0; +} + +static int mm_apply_node_recursive(MagicMount *ctx, + const char *base, const char *wbase, + Node *node, bool has_tmpfs) { char path[PATH_MAX]; char wpath[PATH_MAX]; @@ -786,13 +162,14 @@ static int do_magic(const char *base, const char *wbase, Node *node, LOGE("bind %s->%s: %s", node->module_path, target, strerror(errno)); return -1; - } else - if (!strstr(target, ".magic_mount/workdir/")) { send_unmountable(target); } // tell ksu about this mount + } else if (!strstr(target, ".magic_mount/workdir/")) { + ksu_send_unmountable(target); + } (void)mount(NULL, target, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); - g_stats.nodes_mounted++; + ctx->stats.nodes_mounted++; return 0; } @@ -803,16 +180,16 @@ static int do_magic(const char *base, const char *wbase, Node *node, return -1; } - if (clone_symlink(node->module_path, wpath) != 0) + if (mm_clone_symlink(node->module_path, wpath) != 0) return -1; - g_stats.nodes_mounted++; + ctx->stats.nodes_mounted++; return 0; } case NFT_WHITEOUT: LOGD("whiteout %s", path); - g_stats.nodes_whiteout++; + ctx->stats.nodes_whiteout++; return 0; case NFT_DIRECTORY: { @@ -837,7 +214,8 @@ static int do_magic(const char *base, const char *wbase, Node *node, LOGD("child %s is SYMLINK", c->name); } else if (c->type == NFT_WHITEOUT) { need = path_exists(rp); - LOGD("child %s is WHITEOUT, path_exists=%d, need=%d", c->name, need, need); + LOGD("child %s is WHITEOUT, path_exists=%d, need=%d", + c->name, need, need); } else { struct stat st; if (lstat(rp, &st) == 0) { @@ -848,7 +226,8 @@ static int do_magic(const char *base, const char *wbase, Node *node, need = true; } else { LOGD("lstat failed for %s: %s (errno=%d), path_exists=%d", - rp, strerror(errno), errno, path_exists(rp) ? 1 : 0); + rp, strerror(errno), errno, + path_exists(rp) ? 1 : 0); need = true; } } @@ -859,9 +238,10 @@ static int do_magic(const char *base, const char *wbase, Node *node, if (need) { if (!node->module_path) { LOGE("cannot create tmpfs on %s (%s) - child type: %d, target exists: %d", - path, c->name, c->type, path_exists(rp) ? 1 : 0); + path, c->name, c->type, + path_exists(rp) ? 1 : 0); c->skip = true; - g_stats.nodes_skipped++; + ctx->stats.nodes_skipped++; continue; } create_tmp = true; @@ -893,11 +273,7 @@ static int do_magic(const char *base, const char *wbase, Node *node, chmod(wpath, st.st_mode & 07777); chown(wpath, st.st_uid, st.st_gid); - char *con = NULL; - if (get_selinux(meta_path, &con) == 0 && con) { - set_selinux(wpath, con); - free(con); - } + (void)copy_selcon(meta_path, wpath); } if (create_tmp) { @@ -921,7 +297,7 @@ static int do_magic(const char *base, const char *wbase, Node *node, continue; } - Node *c = node_find_child(node, de->d_name); + Node *c = node_child_find(node, de->d_name); int r = 0; if (c) { @@ -930,9 +306,9 @@ static int do_magic(const char *base, const char *wbase, Node *node, continue; } c->done = true; - r = do_magic(path, wpath, c, now_tmp); + r = mm_apply_node_recursive(ctx, path, wpath, c, now_tmp); } else if (now_tmp) { - r = mirror(path, wpath, de->d_name); + r = mm_mirror_entry(ctx, path, wpath, de->d_name); } if (r != 0) { @@ -948,14 +324,14 @@ static int do_magic(const char *base, const char *wbase, Node *node, path, c ? c->name : de->d_name, mn); - register_module_failure(mn); + module_mark_failed(ctx, mn); } else { LOGE("child %s/%s failed (no module_name)", path, c ? c->name : de->d_name); } - g_stats.nodes_fail++; + ctx->stats.nodes_fail++; if (now_tmp) { closedir(d); @@ -972,7 +348,7 @@ static int do_magic(const char *base, const char *wbase, Node *node, if (c->skip || c->done) continue; - int r = do_magic(path, wpath, c, now_tmp); + int r = mm_apply_node_recursive(ctx, path, wpath, c, now_tmp); if (r != 0) { const char *mn = c->module_name ? c->module_name : @@ -981,13 +357,13 @@ static int do_magic(const char *base, const char *wbase, Node *node, if (mn) { LOGE("child %s/%s failed (module: %s)", path, c->name, mn); - register_module_failure(mn); + module_mark_failed(ctx, mn); } else { LOGE("child %s/%s failed (no module_name)", path, c->name); } - g_stats.nodes_fail++; + ctx->stats.nodes_fail++; if (now_tmp) return -1; } @@ -1001,19 +377,17 @@ static int do_magic(const char *base, const char *wbase, Node *node, LOGE("move %s->%s failed: %s", wpath, path, strerror(errno)); if (node->module_name) - register_module_failure(node->module_name); + module_mark_failed(ctx, node->module_name); return -1; } LOGI("move mountpoint success: %s -> %s", wpath, path); (void)mount(NULL, path, NULL, MS_REC | MS_PRIVATE, NULL); - // tell ksu about this one too - send_unmountable(path); - + ksu_send_unmountable(path); } - g_stats.nodes_mounted++; + ctx->stats.nodes_mounted++; return 0; } } @@ -1021,77 +395,12 @@ static int do_magic(const char *base, const char *wbase, Node *node, return 0; } -static int mirror(const char *path, const char *work, const char *name) +int magic_mount(MagicMount *ctx, const char *tmp_root) { - char src[PATH_MAX]; - char dst[PATH_MAX]; - - if (path_join(path, name, src, sizeof(src)) != 0 || - path_join(work, name, dst, sizeof(dst)) != 0) + if (!ctx) return -1; - struct stat st; - if (lstat(src, &st) < 0) { - LOGW("lstat %s: %s", src, strerror(errno)); - return 0; - } - - if (S_ISREG(st.st_mode)) { - int fd = open(dst, O_CREAT | O_WRONLY, st.st_mode & 07777); - if (fd < 0) { - LOGE("create %s: %s", dst, strerror(errno)); - return -1; - } - close(fd); - - if (mount(src, dst, NULL, MS_BIND, NULL) < 0) { - LOGE("bind %s->%s: %s", src, dst, strerror(errno)); - return -1; - } - } else if (S_ISDIR(st.st_mode)) { - if (mkdir(dst, st.st_mode & 07777) < 0 && errno != EEXIST) { - LOGE("mkdir %s: %s", dst, strerror(errno)); - return -1; - } - - chmod(dst, st.st_mode & 07777); - chown(dst, st.st_uid, st.st_gid); - - char *con = NULL; - if (get_selinux(src, &con) == 0 && con) { - set_selinux(dst, con); - free(con); - } - - DIR *d = opendir(src); - if (!d) { - LOGE("opendir %s: %s", src, strerror(errno)); - return -1; - } - - struct dirent *de; - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || - !strcmp(de->d_name, "..")) { - continue; - } - if (mirror(src, dst, de->d_name) != 0) { - closedir(d); - return -1; - } - } - closedir(d); - } else if (S_ISLNK(st.st_mode)) { - if (clone_symlink(src, dst) != 0) - return -1; - } - - return 0; -} - -int magic_mount(const char *tmp_root) -{ - Node *root = collect_root(); + Node *root = build_mount_tree(ctx); if (!root) { LOGI("no modules, magic_mount skipped"); return 0; @@ -1109,9 +418,9 @@ int magic_mount(const char *tmp_root) } LOGI("starting magic_mount core logic: tmpfs_source=%s tmp_dir=%s", - g_mount_source, tmp_dir); + ctx->mount_source, tmp_dir); - if (mount(g_mount_source, tmp_dir, "tmpfs", 0, "") < 0) { + if (mount(ctx->mount_source, tmp_dir, "tmpfs", 0, "") < 0) { LOGE("mount tmpfs %s: %s", tmp_dir, strerror(errno)); node_free(root); return -1; @@ -1119,9 +428,9 @@ int magic_mount(const char *tmp_root) (void)mount(NULL, tmp_dir, NULL, MS_REC | MS_PRIVATE, NULL); - int rc = do_magic("/", tmp_dir, root, false); + int rc = mm_apply_node_recursive(ctx, "/", tmp_dir, root, false); if (rc != 0) - g_stats.nodes_fail++; + ctx->stats.nodes_fail++; if (umount2(tmp_dir, MNT_DETACH) < 0) LOGE("umount %s: %s", tmp_dir, strerror(errno)); diff --git a/src/magic_mount.h b/src/magic_mount.h index 328a6c5..39610f3 100644 --- a/src/magic_mount.h +++ b/src/magic_mount.h @@ -1,19 +1,19 @@ #ifndef MAGIC_MOUNT_H #define MAGIC_MOUNT_H -#include -#include +#include #define DISABLE_FILE_NAME "disable" #define REMOVE_FILE_NAME "remove" #define SKIP_MOUNT_FILE_NAME "skip_mount" -#define REPLACE_DIR_XATTR "trusted.overlay.opaque" -#define REPLACE_DIR_FILE_NAME ".replace" +#define REPLACE_DIR_XATTR "trusted.overlay.opaque" +#define REPLACE_DIR_FILE_NAME ".replace" -#define DEFAULT_MOUNT_SOURCE "KSU" -#define DEFAULT_MODULE_DIR "/data/adb/modules" +#define DEFAULT_MOUNT_SOURCE "KSU" +#define DEFAULT_MODULE_DIR "/data/adb/modules" +/* Mount statistics */ typedef struct { int modules_total; int nodes_total; @@ -23,31 +23,27 @@ typedef struct { int nodes_fail; } MountStats; -extern MountStats g_stats; +/* Core ctx */ +typedef struct MagicMount { + const char *module_dir; + const char *mount_source; -extern const char *g_module_dir; -extern const char *g_mount_source; + MountStats stats; -extern char **g_failed_modules; -extern int g_failed_modules_count; + char **failed_modules; + int failed_modules_count; -extern char **g_extra_parts; -extern int g_extra_parts_count; + char **extra_parts; + int extra_parts_count; +} MagicMount; -int magic_mount(const char *tmp_root); +/* Initialization ctx (module_dir/mount_source) */ +void magic_mount_init(MagicMount *ctx); -void add_extra_part_token(const char *start, size_t len); +/* Main func */ +int magic_mount(MagicMount *ctx, const char *tmp_root); -// kernelsu api, atleast ones we care about -#define KSU_INSTALL_MAGIC1 0xDEADBEEF -#define KSU_INSTALL_MAGIC2 0xCAFEBABE - -struct ksu_add_try_umount_cmd { - uint64_t arg; // char ptr, this is the mountpoint - uint32_t flags; // this is the flag we use for it - uint8_t mode; // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry -}; - -#define KSU_IOCTL_ADD_TRY_UMOUNT _IOC(_IOC_WRITE, 'K', 18, 0) +/* (failure module / extra_parts) */ +void magic_mount_cleanup(MagicMount *ctx); #endif /* MAGIC_MOUNT_H */ diff --git a/src/main.c b/src/main.c index e81c0fb..a05840e 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,5 @@ #include "magic_mount.h" +#include "module_tree.h" #include "utils.h" #include @@ -16,296 +17,336 @@ #define DEFAULT_CONFIG_PATH "/data/adb/magic_mount/mm.conf" +/* --- Configuration structure --- */ +typedef struct { + const char *module_dir; + const char *temp_dir; + const char *mount_source; + const char *log_file; + const char *partitions; + bool debug; +} Config; + +/* --- Forward declarations --- */ +static void usage(const char *prog); +static int load_config_file(const char *path, Config *cfg, MagicMount *ctx); +static int parse_partitions(const char *list, MagicMount *ctx); +static int setup_logging(const char *log_path); +static void print_summary(const MagicMount *ctx); +static void cleanup_resources(MagicMount *ctx); + +/* --- Helper function implementations --- */ static void usage(const char *prog) { fprintf(stderr, - "Magic Mount Version: %s\n" + "Magic Mount: %s\n" + "\n" "Usage: %s [options]\n" - " -m DIR module dir (default: %s)\n" - " -t DIR temp dir (default: auto rw tmpfs + \".magic_mount\")\n" - " -s SRC mount src (default: %s)\n" - " -p LIST extra partitions, comma-separated (e.g. odm,mi_ext,my_stock)\n" - " -l FILE log file (default: stderr, '-'=stdout)\n" - " -c FILE config file (default: " DEFAULT_CONFIG_PATH ")\n" - " -v enable debug logging (default: info)\n" - " -h help\n", - VERSION, prog, DEFAULT_MODULE_DIR, - DEFAULT_MOUNT_SOURCE); + "\n" + "Options:\n" + " -m, --module-dir DIR Module directory (default: %s)\n" + " -t, --temp-dir DIR Temporary directory (default: auto-detected)\n" + " -s, --mount-source SRC Mount source (default: %s)\n" + " -p, --partitions LIST Extra partitions (eg. odm,mi_ext,my_stock)\n" + " -l, --log-file FILE Log file (default: stderr, '-' for stdout)\n" + " -c, --config FILE Config file (default: %s)\n" + " -v, --verbose Enable debug logging\n" + " -h, --help Show this help message\n" + "\n", + VERSION, prog, DEFAULT_MODULE_DIR, DEFAULT_MOUNT_SOURCE, + DEFAULT_CONFIG_PATH); } -static int enter_pid1_ns(void) +static int load_config_file(const char *path, Config *cfg, MagicMount *ctx) { - int fd = open("/proc/1/ns/mnt", O_RDONLY); - if (fd < 0) { - LOGE("open /proc/1/ns/mnt: %s", strerror(errno)); - return -1; - } - - if (setns(fd, 0) < 0) { - LOGE("setns: %s", strerror(errno)); - close(fd); - return -1; - } - - close(fd); - LOGI("entered pid1 mnt ns"); - return 0; -} - -static char *str_trim(char *s) -{ - if (!s) - return s; - - char *start = s; - while (*start && isspace((unsigned char)*start)) - start++; - - char *end = start + strlen(start); - while (end > start && isspace((unsigned char)end[-1])) - *--end = '\0'; - - return start; -} - -static bool str_is_true(const char *v) -{ - if (!v) - return false; - if (!strcasecmp(v, "1") || - !strcasecmp(v, "true") || - !strcasecmp(v, "yes") || - !strcasecmp(v, "on")) - return true; - return false; -} - -static void load_config(const char *config_path, - const char **tmp_dir_out, - const char **log_path_out, - bool *debug_out) -{ - FILE *fp = fopen(config_path, "r"); + FILE *fp = fopen(path, "r"); if (!fp) { - LOGD("config file %s not used: %s", - config_path, strerror(errno)); - return; + if (errno != ENOENT) { + LOGW("config file %s: %s", path, strerror(errno)); + } + return -1; } - LOGI("loading config file: %s", config_path); + LOGI("Loading config file: %s", path); char line[1024]; + int line_num = 0; + while (fgets(line, sizeof(line), fp)) { - char *p = line; + line_num++; - while (*p && isspace((unsigned char)*p)) - p++; + str_trim(line); - if (*p == '\0' || *p == '\n' || *p == '#') + if (line[0] == '\0' || line[0] == '#') continue; - char *eq = strchr(p, '='); + char *eq = strchr(line, '='); if (!eq) { + LOGW("config:%d: invalid line (no '=')", line_num); continue; } *eq = '\0'; - char *key = str_trim(p); + char *key = str_trim(line); char *val = str_trim(eq + 1); - size_t vlen = strlen(val); - if (vlen > 0 && (val[vlen - 1] == '\n' || val[vlen - 1] == '\r')) - val[vlen - 1] = '\0'; - val = str_trim(val); - if (*key == '\0' || *val == '\0') continue; + /* Process key-value pairs */ if (!strcasecmp(key, "module_dir")) { - char *dup = strdup(val); - if (dup) - g_module_dir = dup; + cfg->module_dir = strdup(val); + } else if (!strcasecmp(key, "temp_dir")) { - char *dup = strdup(val); - if (dup && tmp_dir_out) - *tmp_dir_out = dup; + cfg->temp_dir = strdup(val); + } else if (!strcasecmp(key, "mount_source")) { - char *dup = strdup(val); - if (dup) - g_mount_source = dup; - } else if (!strcasecmp(key, "partitions")) { - const char *list = val; - const char *q = list; - while (*q) { - const char *start = q; - while (*q && *q != ',') - q++; - size_t len = (size_t)(q - start); - add_extra_part_token(start, len); - if (*q == ',') - q++; - } + cfg->mount_source = strdup(val); + } else if (!strcasecmp(key, "log_file")) { - char *dup = strdup(val); - if (dup && log_path_out) - *log_path_out = dup; + cfg->log_file = strdup(val); + } else if (!strcasecmp(key, "debug")) { - if (debug_out && str_is_true(val)) - *debug_out = true; + cfg->debug = str_is_true(val); + + } else if (!strcasecmp(key, "partitions")) { + cfg->partitions = strdup(val); + } else { - LOGW("unknown config key: %s", key); + LOGW("config:%d: unknown key '%s'", line_num, key); } } fclose(fp); + return 0; } +static int parse_partitions(const char *list, MagicMount *ctx) +{ + if (!list || !*list) + return 0; + + const char *p = list; + while (*p) { + /* Find start of token */ + while (*p && (*p == ',' || isspace((unsigned char)*p))) + p++; + + if (!*p) + break; + + const char *start = p; + + /* Find end of token */ + while (*p && *p != ',' && !isspace((unsigned char)*p)) + p++; + + size_t len = (size_t)(p - start); + if (len > 0) { + extra_partition_register(ctx, start, len); + LOGD("Added extra partition: %.*s", (int)len, start); + } + } + + return 0; +} + +static int setup_logging(const char *log_path) +{ + if (!log_path) + return 0; + + FILE *fp = NULL; + + if (!strcmp(log_path, "-")) { + fp = stdout; + } else { + fp = fopen(log_path, "a"); + if (!fp) { + fprintf(stderr, "Error: Cannot open log file %s: %s\n", + log_path, strerror(errno)); + return -1; + } + /* Ensure log is line-buffered */ + setvbuf(fp, NULL, _IOLBF, 0); + } + + log_set_file(fp); + return 0; +} + +static void print_summary(const MagicMount *ctx) +{ + LOGI("Summary"); + LOGI("Modules processed: %d", ctx->stats.modules_total); + LOGI("Nodes total: %d", ctx->stats.nodes_total); + LOGI("Nodes mounted: %d", ctx->stats.nodes_mounted); + LOGI("Nodes skipped: %d", ctx->stats.nodes_skipped); + LOGI("Whiteouts: %d", ctx->stats.nodes_whiteout); + LOGI("Failures: %d", ctx->stats.nodes_fail); + + if (ctx->failed_modules_count > 0) { + LOGE("Failed modules (%d):", ctx->failed_modules_count); + for (int i = 0; i < ctx->failed_modules_count; i++) { + LOGE(" - %s", ctx->failed_modules[i]); + } + } else { + LOGI("No module failures"); + } +} + +static void cleanup_resources(MagicMount *ctx) +{ + if (!ctx) + return; + + magic_mount_cleanup(ctx); + + if (g_log_file && g_log_file != stdout && g_log_file != stderr) { + fclose(g_log_file); + g_log_file = NULL; + } +} + +/* --- Main function --- */ int main(int argc, char **argv) { - const char *tmp_dir = NULL; - const char *log_path = NULL; - const char *config_path = DEFAULT_CONFIG_PATH; - char auto_tmp[PATH_MAX] = { 0 }; - bool debug_from_cfg = false; + MagicMount ctx; + Config cfg = {0}; + char auto_tmp[PATH_MAX] = {0}; - for (int i = 1; i < argc; ++i) { + const char *config_path = DEFAULT_CONFIG_PATH; + const char *tmp_dir = NULL; + const char *cli_log_path = NULL; + bool cli_has_partitions = false; + int rc; + + magic_mount_init(&ctx); + + for (int i = 1; i < argc; i++) { const char *arg = argv[i]; - if ((strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) && - i + 1 < argc) { + if ((!strcmp(arg, "-c") || !strcmp(arg, "--config")) && i + 1 < argc) { config_path = argv[++i]; } + else if ((!strcmp(arg, "-l") || !strcmp(arg, "--log-file")) && i + 1 < argc) { + cli_log_path = argv[++i]; + } } - load_config(config_path, &tmp_dir, &log_path, &debug_from_cfg); - - if (debug_from_cfg) { - g_log_level = LV_DEBUG; + if (cli_log_path && setup_logging(cli_log_path) < 0) { + fprintf(stderr, "Error: Failed to setup logging to %s\n", cli_log_path); + return 1; } - for (int i = 1; i < argc; ++i) { + load_config_file(config_path, &cfg, &ctx); + + if (!cli_log_path && cfg.log_file && setup_logging(cfg.log_file) < 0) { + fprintf(stderr, "Error: Failed to setup logging to %s\n", cfg.log_file); + return 1; + } + + if (cfg.module_dir) + ctx.module_dir = cfg.module_dir; + if (cfg.mount_source) + ctx.mount_source = cfg.mount_source; + if (cfg.temp_dir) + tmp_dir = cfg.temp_dir; + if (cfg.debug) + log_set_level(LOG_DEBUG); + + for (int i = 1; i < argc; i++) { const char *arg = argv[i]; - if ((strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) && - i + 1 < argc) { - ++i; - continue; - } + if ((!strcmp(arg, "-c") || !strcmp(arg, "--config")) && i + 1 < argc) { i++; continue; } + if ((!strcmp(arg, "-l") || !strcmp(arg, "--log-file")) && i + 1 < argc) { i++; continue; } - if ((strcmp(arg, "-m") == 0 || - strcmp(arg, "--module-dir") == 0) && - i + 1 < argc) { - g_module_dir = argv[++i]; + if ((!strcmp(arg, "-m") || !strcmp(arg, "--module-dir")) && i + 1 < argc) { + ctx.module_dir = argv[++i]; - } else if ((strcmp(arg, "-t") == 0 || - strcmp(arg, "--temp-dir") == 0) && - i + 1 < argc) { + } else if ((!strcmp(arg, "-t") || !strcmp(arg, "--temp-dir")) && i + 1 < argc) { tmp_dir = argv[++i]; - } else if ((strcmp(arg, "-s") == 0 || - strcmp(arg, "--mount-source") == 0) && - i + 1 < argc) { - g_mount_source = argv[++i]; + } else if ((!strcmp(arg, "-s") || !strcmp(arg, "--mount-source")) && i + 1 < argc) { + ctx.mount_source = argv[++i]; - } else if ((strcmp(arg, "-l") == 0 || - strcmp(arg, "--log-file") == 0) && - i + 1 < argc) { - log_path = argv[++i]; + } else if ((!strcmp(arg, "-l") || !strcmp(arg, "--log-file")) && i + 1 < argc) { + cfg.log_file = argv[++i]; } else if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) { - g_log_level = LV_DEBUG; + log_set_level(LOG_DEBUG); - } else if ((strcmp(arg, "-p") == 0 || - strcmp(arg, "--partitions") == 0) && - i + 1 < argc) { - const char *list = argv[++i]; - const char *p = list; - while (*p) { - const char *start = p; - while (*p && *p != ',') - p++; - size_t len = (size_t)(p - start); - add_extra_part_token(start, len); - if (*p == ',') - p++; + } else if ((!strcmp(arg, "-p") || !strcmp(arg, "--partitions")) && i + 1 < argc) { + cli_has_partitions = true; + if (parse_partitions(argv[++i], &ctx) < 0) { + LOGE("failed to parse partitions"); + cleanup_resources(&ctx); + return 1; } } else if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) { usage(argv[0]); + cleanup_resources(&ctx); return 0; } else { - fprintf(stderr, "Unknown arg: %s\n", arg); + fprintf(stderr, "Error: Unknown argument: %s\n\n", arg); usage(argv[0]); + cleanup_resources(&ctx); return 1; } } - if (log_path) { - if (!strcmp(log_path, "-")) { - g_log_file = stdout; - } else { - g_log_file = fopen(log_path, "a"); - if (!g_log_file) { - fprintf(stderr, "open log %s: %s\n", log_path, - strerror(errno)); - return 1; - } + if (!cli_has_partitions && cfg.partitions) { + if (parse_partitions(cfg.partitions, &ctx) < 0) { + LOGW("failed to parse partitions from config"); + cleanup_resources(&ctx); + return 1; } } - if (!tmp_dir) { + /* Determine temp directory */ + if (!tmp_dir) tmp_dir = select_auto_tempdir(auto_tmp); - } - if (geteuid() != 0) { - LOGE("must run as root (euid=%d)", (int)geteuid()); - if (g_log_file && g_log_file != stdout && g_log_file != stderr) - fclose(g_log_file); - free_string_array(&g_extra_parts, &g_extra_parts_count); + if (!tmp_dir || *tmp_dir == '\0') { + LOGE("failed to determine temp directory"); + cleanup_resources(&ctx); return 1; } - if (enter_pid1_ns() != 0) { - if (g_log_file && g_log_file != stdout && g_log_file != stderr) - fclose(g_log_file); - free_string_array(&g_extra_parts, &g_extra_parts_count); + /* Validate environment */ + if (root_check() < 0) { + cleanup_resources(&ctx); return 1; } - int rc = 0; - - LOGI("starting magic_mount process: module_dir=%s temp_dir=%s " - "mount_source=%s log_level=%d", - g_module_dir, tmp_dir, g_mount_source, g_log_level); - - rc = magic_mount(tmp_dir); - - if (rc == 0) { - LOGI("magic_mount completed successfully"); - } else { - LOGE("magic_mount failed with rc=%d " - "(check previous logs for details)", - rc); - } - - LOGI("magic_mount summary: modules=%d nodes_total=%d mounted=%d " - "skipped=%d whiteouts=%d failures=%d", - g_stats.modules_total, g_stats.nodes_total, g_stats.nodes_mounted, - g_stats.nodes_skipped, g_stats.nodes_whiteout, g_stats.nodes_fail); - - if (g_failed_modules_count > 0) { - LOGW("modules with failures (%d):", g_failed_modules_count); - for (int i = 0; i < g_failed_modules_count; ++i) { - LOGW(" failed module: %s", g_failed_modules[i]); + /* Log startup information */ + LOGI("Magic Mount %s Starting", VERSION); + LOGI("Configuration:"); + LOGI(" Module directory: %s", ctx.module_dir); + LOGI(" Temp directory: %s", tmp_dir); + LOGI(" Mount source: %s", ctx.mount_source); + LOGI(" Log level: %s", g_log_level == LOG_DEBUG ? "DEBUG" : "INFO"); + if (ctx.extra_parts_count > 0) { + LOGI(" Extra partitions: %d", ctx.extra_parts_count); + for (int i = 0; i < ctx.extra_parts_count; i++) { + LOGI(" - %s", ctx.extra_parts[i]); } } - if (g_log_file && g_log_file != stdout && g_log_file != stderr) - fclose(g_log_file); + /* Perform magic mount */ + rc = magic_mount(&ctx, tmp_dir); - free_string_array(&g_failed_modules, &g_failed_modules_count); - free_string_array(&g_extra_parts, &g_extra_parts_count); + /* Print results */ + if (rc == 0) { + LOGI("Magic Mount Completed Successfully"); + } else { + LOGE("Magic Mount Failed (rc=%d)", rc); + } + + print_summary(&ctx); + cleanup_resources(&ctx); return rc == 0 ? 0 : 1; } diff --git a/src/module_tree.c b/src/module_tree.c new file mode 100644 index 0000000..09e777e --- /dev/null +++ b/src/module_tree.c @@ -0,0 +1,844 @@ +#include "module_tree.h" +#include "magic_mount.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* --- Node basic mgr --- */ + +static Node *node_new(const char *name, NodeFileType t) +{ + Node *n = calloc(1, sizeof(Node)); + if (!n) return NULL; + + n->name = strdup(name ? name : ""); + n->type = t; + return n; +} + +void node_free(Node *n) +{ + if (!n) return; + + for (size_t i = 0; i < n->child_count; ++i) + node_free(n->children[i]); + + free(n->children); + free(n->name); + free(n->module_path); + free(n->module_name); + free(n); +} + +NodeFileType node_type_from_stat(const struct stat *st) +{ + if (S_ISCHR(st->st_mode) && st->st_rdev == 0) + return NFT_WHITEOUT; + if (S_ISREG(st->st_mode)) return NFT_REGULAR; + if (S_ISDIR(st->st_mode)) return NFT_DIRECTORY; + if (S_ISLNK(st->st_mode)) return NFT_SYMLINK; + return NFT_WHITEOUT; +} + +static bool dir_is_replace(const char *path) +{ + char buf[8]; + ssize_t len = lgetxattr(path, REPLACE_DIR_XATTR, buf, sizeof(buf) - 1); + + if (len > 0) { + buf[len] = '\0'; + if (strcmp(buf, "y") == 0) return true; + } + + int dirfd = open(path, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) return false; + + bool exists = (faccessat(dirfd, REPLACE_DIR_FILE_NAME, F_OK, 0) == 0); + close(dirfd); + return exists; +} + +static Node *node_create_from_fs(MagicMount *ctx, const char *name, + const char *path, const char *module_name) +{ + struct stat st; + if (lstat(path, &st) < 0) { + LOGD("node_create_from_fs: lstat(%s) failed: %s", path, strerror(errno)); + return NULL; + } + + if (!(S_ISCHR(st.st_mode) || S_ISREG(st.st_mode) || + S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { + LOGD("node_create_from_fs: skip unsupported file type for %s (mode=%o)", + path, st.st_mode); + return NULL; + } + + NodeFileType t = node_type_from_stat(&st); + Node *n = node_new(name, t); + if (!n) { + LOGE("node_create_from_fs: failed to allocate node for %s", path); + return NULL; + } + + n->module_path = strdup(path); + if (module_name) + n->module_name = strdup(module_name); + n->replace = (t == NFT_DIRECTORY) && dir_is_replace(path); + + LOGD("node_create_from_fs: created node '%s' (type=%d, replace=%d, module=%s, path=%s)", + name, t, n->replace, module_name ? module_name : "(none)", path); + + ctx->stats.nodes_total++; + return n; +} + +static int node_child_append(Node *parent, Node *child) +{ + if (!parent || !child) { + LOGE("node_child_append: parent or child is NULL"); + errno = EINVAL; + return -1; + } + + LOGD("node_child_append: parent='%s' add child='%s'", + parent->name ? parent->name : "(root)", + child->name ? child->name : "(null)"); + + Node **arr = realloc(parent->children, + (parent->child_count + 1) * sizeof(Node *)); + if (!arr) { + LOGE("node_child_append: realloc failed (parent='%s', child='%s')", + parent->name ? parent->name : "(root)", + child->name ? child->name : "(null)"); + errno = ENOMEM; + return -1; + } + + parent->children = arr; + parent->children[parent->child_count++] = child; + return 0; +} + +Node *node_child_find(Node *parent, const char *name) +{ + for (size_t i = 0; i < parent->child_count; ++i) { + if (strcmp(parent->children[i]->name, name) == 0) + return parent->children[i]; + } + return NULL; +} + +static Node *node_child_detach(Node *parent, const char *name) +{ + for (size_t i = 0; i < parent->child_count; ++i) { + if (strcmp(parent->children[i]->name, name) == 0) { + Node *n = parent->children[i]; + memmove(&parent->children[i], &parent->children[i + 1], + (parent->child_count - i - 1) * sizeof(Node *)); + parent->child_count--; + return n; + } + } + return NULL; +} + +void module_mark_failed(MagicMount *ctx, const char *module_name) +{ + if (!ctx || !module_name) return; + + // Check for duplicates + for (int i = 0; i < ctx->failed_modules_count; ++i) { + if (strcmp(ctx->failed_modules[i], module_name) == 0) + return; + } + + if (!str_array_append(&ctx->failed_modules, + &ctx->failed_modules_count, + module_name)) { + LOGW("failed to record module failure for %s (OOM)", module_name); + } +} + +/* --- Extra partition blacklist --- */ + +static bool extra_part_blacklisted(const char *name) +{ + if (!name || !*name) + return false; + + while (*name == '/') + name++; + + char buf[16]; + size_t i = 0; + while (name[i] != '\0' && name[i] != '/' && i + 1 < sizeof(buf)) { + buf[i] = name[i]; + i++; + } + buf[i] = '\0'; + + static const char *blacklist[] = { + "bin", "etc", "data", "data_mirror", "sdcard", + "tmp", "dev", "sys", "mnt", "proc", "d", "test" + }; + size_t n = sizeof(blacklist) / sizeof(blacklist[0]); + + for (size_t j = 0; j < n; ++j) { + if (strcmp(buf, blacklist[j]) == 0) + return true; + } + return false; +} + +void extra_partition_register(MagicMount *ctx, const char *start, size_t len) +{ + if (!ctx) { + LOGE("extra_partition_register: NULL context"); + return; + } + + if (!start || len == 0) { + LOGW("extra_partition_register: invalid input (start=%p, len=%zu)", + (void*)start, len); + return; + } + + char *buf = malloc(len + 1); + if (!buf) { + LOGE("extra_partition_register: malloc failed for %zu bytes", len + 1); + return; + } + + memcpy(buf, start, len); + buf[len] = '\0'; + + LOGD("extra_partition_register: processing '%s' (len=%zu)", buf, len); + + size_t original_len = strlen(buf); + str_trim(buf); + size_t trimmed_len = strlen(buf); + + if (original_len != trimmed_len) { + LOGD("extra_partition_register: trimmed whitespace (%zu -> %zu bytes)", + original_len, trimmed_len); + } + + if (buf[0] == '\0') { + LOGW("extra_partition_register: rejected empty string after trim"); + free(buf); + return; + } + + if (extra_part_blacklisted(buf)) { + LOGW("extra_partition_register: rejected '%s' (blacklisted)", buf); + free(buf); + return; + } + + if (!str_array_append(&ctx->extra_parts, + &ctx->extra_parts_count, + buf)) { + LOGE("extra_partition_register: failed to add '%s' (OOM or array error, count=%d)", + buf, ctx->extra_parts_count); + free(buf); + return; + } + + LOGI("extra_partition_register: success added '%s' (total: %d partitions)", + buf, ctx->extra_parts_count); + + free(buf); +} + +static bool module_is_disabled(const char *mod_dir) +{ + char buf[PATH_MAX]; + const char *disable_files[] = { + DISABLE_FILE_NAME, + REMOVE_FILE_NAME, + SKIP_MOUNT_FILE_NAME + }; + + for (size_t i = 0; i < sizeof(disable_files) / sizeof(disable_files[0]); ++i) { + if (path_join(mod_dir, disable_files[i], buf, sizeof(buf)) == 0 && + path_exists(buf)) + return true; + } + return false; +} + +/* --- Node collect --- */ + +static int node_scan_dir(MagicMount *ctx, Node *self, const char *dir, + const char *module_name, bool *has_any) +{ + LOGD("node_scan_dir: enter dir=%s module=%s node='%s'", + dir, module_name ? module_name : "(none)", + self && self->name ? self->name : "(null)"); + + DIR *d = opendir(dir); + if (!d) { + LOGE("opendir %s: %s", dir, strerror(errno)); + return -1; + } + + struct dirent *de; + bool any = false; + char path[PATH_MAX]; + + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + if (path_join(dir, de->d_name, path, sizeof(path)) != 0) { + LOGE("node_scan_dir: path_join failed for dir=%s name=%s", + dir, de->d_name); + closedir(d); + return -1; + } + + LOGD("node_scan_dir: processing '%s' (full=%s)", de->d_name, path); + + Node *child = node_child_find(self, de->d_name); + if (!child) { + Node *n = node_create_from_fs(ctx, de->d_name, path, module_name); + if (n && node_child_append(self, n) == 0) { + child = n; + } else if (n) { + LOGE("node_scan_dir: failed to add child '%s' to '%s'", + de->d_name, + self && self->name ? self->name : "(null)"); + node_free(n); + } else { + LOGD("node_scan_dir: node_create_from_fs returned NULL for %s", path); + } + } + + if (child) { + if (child->type == NFT_DIRECTORY) { + bool sub = false; + if (node_scan_dir(ctx, child, path, module_name, &sub) != 0) { + LOGE("node_scan_dir: recurse failed for dir=%s", path); + closedir(d); + return -1; + } + if (sub || child->replace) { + LOGD("node_scan_dir: directory '%s' has content (sub=%d, replace=%d)", + child->name, sub, child->replace); + any = true; + } + } else { + LOGD("node_scan_dir: file node '%s' has content (type=%d)", + child->name, child->type); + any = true; + } + } else { + LOGD("node_scan_dir: no child node created for %s", path); + } + } + + closedir(d); + *has_any = any; + LOGD("node_scan_dir: leave dir=%s has_any=%d", dir, any); + return 0; +} + +/* --- Symlink compatibility --- */ + +static bool is_compatible_symlink(const char *link_target, const char *part_name, + const MagicMount *ctx, const char *module_name) +{ + // Remove trailing slashes + size_t target_len = strlen(link_target); + while (target_len > 0 && link_target[target_len - 1] == '/') { + target_len--; + } + if (target_len == 0) return false; + + // Check relative: ../part_name + char expected_relative[PATH_MAX]; + snprintf(expected_relative, sizeof(expected_relative), "../%s", part_name); + if (strncmp(link_target, expected_relative, target_len) == 0 && + expected_relative[target_len] == '\0') + return true; + + // Check absolute: /module_dir/module_name/part_name + if (ctx && ctx->module_dir && module_name) { + char expected_absolute[PATH_MAX]; + char tmp[PATH_MAX]; + + if (path_join(ctx->module_dir, module_name, tmp, sizeof(tmp)) == 0 && + path_join(tmp, part_name, expected_absolute, sizeof(expected_absolute)) == 0) { + if (strncmp(link_target, expected_absolute, target_len) == 0 && + expected_absolute[target_len] == '\0') + return true; + } + } + + return false; +} + +static int find_real_partition_dir(MagicMount *ctx, const char *part_name, + char *out_path, char *out_module, size_t buf_size) +{ + DIR *mod_dir = opendir(ctx->module_dir); + if (!mod_dir) { + LOGE("opendir %s: %s", ctx->module_dir, strerror(errno)); + return -1; + } + + struct dirent *mod_de; + int result = -1; + + while ((mod_de = readdir(mod_dir))) { + if (!strcmp(mod_de->d_name, ".") || !strcmp(mod_de->d_name, "..")) + continue; + + char mod_path[PATH_MAX], part_path[PATH_MAX]; + + if (path_join(ctx->module_dir, mod_de->d_name, mod_path, sizeof(mod_path)) != 0) + continue; + + struct stat mod_st; + if (stat(mod_path, &mod_st) < 0 || !S_ISDIR(mod_st.st_mode)) + continue; + + if (module_is_disabled(mod_path)) + continue; + + if (path_join(mod_path, part_name, part_path, sizeof(part_path)) != 0) + continue; + + if (path_is_dir(part_path)) { + strncpy(out_path, part_path, buf_size - 1); + out_path[buf_size - 1] = '\0'; + strncpy(out_module, mod_de->d_name, buf_size - 1); + out_module[buf_size - 1] = '\0'; + result = 0; + break; + } + } + + closedir(mod_dir); + return result; +} + +static int symlink_resolve_partition(MagicMount *ctx, Node *system, const char *part_name) +{ + if (!system || !part_name) return -1; + + Node *sys_child = node_child_find(system, part_name); + if (!sys_child || sys_child->type != NFT_SYMLINK || !sys_child->module_path) + return 0; + + char link_target[PATH_MAX]; + ssize_t len = readlink(sys_child->module_path, link_target, sizeof(link_target) - 1); + if (len < 0) { + LOGW("readlink %s failed: %s", sys_child->module_path, strerror(errno)); + return 0; + } + link_target[len] = '\0'; + + if (!is_compatible_symlink(link_target, part_name, ctx, sys_child->module_name)) { + LOGD("symlink %s -> %s (not compatible)", part_name, link_target); + return 0; + } + + LOGI("found compatible symlink: system/%s -> %s", part_name, link_target); + + char real_part_path[PATH_MAX], module_name_buf[256]; + if (find_real_partition_dir(ctx, part_name, real_part_path, + module_name_buf, sizeof(module_name_buf)) != 0) { + LOGD("no real directory found for %s, keeping symlink", part_name); + return 0; + } + + LOGI("symlink compatibility: system/%s -> %s, real dir in module '%s'", + part_name, link_target, module_name_buf); + + Node *new_part = node_new(part_name, NFT_DIRECTORY); + if (!new_part) { + LOGE("failed to create node for %s", part_name); + return -1; + } + + bool part_has_any = false; + if (node_scan_dir(ctx, new_part, real_part_path, module_name_buf, &part_has_any) != 0) { + LOGE("failed to collect %s from %s", part_name, real_part_path); + node_free(new_part); + return -1; + } + + if (!part_has_any) { + LOGD("no content in %s, keeping symlink", part_name); + node_free(new_part); + return 0; + } + + Node *removed = node_child_detach(system, part_name); + if (removed) { + node_free(removed); + LOGD("removed symlink node: system/%s", part_name); + } + + new_part->module_name = strdup(module_name_buf); + + if (node_child_append(system, new_part) != 0) { + LOGE("failed to add directory node for %s", part_name); + node_free(new_part); + return -1; + } + + LOGI("replaced symlink with directory node: %s (from module '%s')", + part_name, module_name_buf); + + return 0; +} + +static int symlink_resolve_all_partition_links(MagicMount *ctx, Node *system) +{ + if (!system) return -1; + + const char *builtin_parts[] = { "vendor", "system_ext", "product" }; + + for (size_t i = 0; i < sizeof(builtin_parts) / sizeof(builtin_parts[0]); ++i) { + if (symlink_resolve_partition(ctx, system, builtin_parts[i]) != 0) { + LOGE("failed to handle symlink compatibility for %s", builtin_parts[i]); + } + } + + for (int i = 0; i < ctx->extra_parts_count; ++i) { + if (symlink_resolve_partition(ctx, system, ctx->extra_parts[i]) != 0) { + LOGE("failed to handle symlink compatibility for extra part %s", + ctx->extra_parts[i]); + } + } + + return 0; +} + +/* --- Extra partition collect --- */ + +static int partition_scan_from_modules(MagicMount *ctx, const char *part_name, Node *parent_node) +{ + if (!part_name || !parent_node) { + LOGE("partition_scan_from_modules: invalid args part=%s node=%p", + part_name ? part_name : "(null)", + (void *)parent_node); + return -1; + } + + LOGD("partition_scan_from_modules: part=%s", part_name); + + DIR *mod_dir = opendir(ctx->module_dir); + if (!mod_dir) { + LOGE("opendir %s: %s", ctx->module_dir, strerror(errno)); + return -1; + } + + struct dirent *mod_de; + bool has_any = false; + + while ((mod_de = readdir(mod_dir))) { + if (!strcmp(mod_de->d_name, ".") || !strcmp(mod_de->d_name, "..")) + continue; + + char mod_path[PATH_MAX], part_path[PATH_MAX]; + + if (path_join(ctx->module_dir, mod_de->d_name, mod_path, sizeof(mod_path)) != 0) { + LOGE("partition_scan_from_modules: path_join failed for module=%s", mod_de->d_name); + continue; + } + + struct stat mod_st; + if (stat(mod_path, &mod_st) < 0 || !S_ISDIR(mod_st.st_mode)) { + LOGD("partition_scan_from_modules: skip non-dir module=%s", mod_path); + continue; + } + + if (module_is_disabled(mod_path)) { + LOGD("partition_scan_from_modules: module %s disabled, skip", mod_path); + continue; + } + + if (path_join(mod_path, part_name, part_path, sizeof(part_path)) != 0) { + LOGE("partition_scan_from_modules: path_join failed for part=%s in module=%s", + part_name, mod_path); + continue; + } + + if (!path_is_dir(part_path)) { + LOGD("partition_scan_from_modules: module %s has no dir %s", mod_path, part_path); + continue; + } + + LOGD("partition_scan_from_modules: collecting part=%s from module=%s", + part_name, mod_de->d_name); + + bool sub = false; + if (node_scan_dir(ctx, parent_node, part_path, mod_de->d_name, &sub) != 0) { + LOGE("partition_scan_from_modules: node_scan_dir failed for module=%s part=%s", + mod_de->d_name, part_name); + closedir(mod_dir); + return -1; + } + + if (sub) { + LOGD("partition_scan_from_modules: module=%s contributed content to part=%s", + mod_de->d_name, part_name); + has_any = true; + } else { + LOGD("partition_scan_from_modules: module=%s had no effective content for part=%s", + mod_de->d_name, part_name); + } + } + + closedir(mod_dir); + LOGD("partition_scan_from_modules: result for part=%s has_any=%d", part_name, has_any); + return has_any ? 0 : 1; +} + +/* --- Helper for partition promotion --- */ + +static int partition_promote_to_root(Node *root, Node *system, const char *part_name, + bool need_symlink) +{ + char rp[PATH_MAX], sp[PATH_MAX]; + + LOGD("partition_promote_to_root: part=%s need_symlink=%d", part_name, need_symlink); + + if (path_join("/", part_name, rp, sizeof(rp)) != 0 || + path_join("/system", part_name, sp, sizeof(sp)) != 0) + return -1; + + if (!path_is_dir(rp)) { + LOGD("partition_promote_to_root: skip %s (real path %s not a dir)", part_name, rp); + return 0; + } + + if (need_symlink && !path_is_symlink(sp)) { + LOGD("partition_promote_to_root: skip %s (no symlink at %s)", part_name, sp); + return 0; + } + + Node *child = node_child_detach(system, part_name); + if (!child) { + LOGD("partition_promote_to_root: system node has no child '%s' to promote", part_name); + return 0; + } + + LOGD("partition_promote_to_root: promoting '%s' from /system to /", part_name); + + if (node_child_append(root, child) != 0) { + LOGE("partition_promote_to_root: failed to attach '%s' to root", part_name); + node_free(child); + return -1; + } + + return 0; +} + +/* --- Root collection --- */ + +Node *build_mount_tree(MagicMount *ctx) +{ + if (!ctx) { + LOGE("build_mount_tree: ctx is NULL"); + return NULL; + } + + const char *mdir = ctx->module_dir ? ctx->module_dir : DEFAULT_MODULE_DIR; + + LOGI("build_mount_tree: module_dir=%s", mdir); + + Node *root = node_new("", NFT_DIRECTORY); + Node *system = node_new("system", NFT_DIRECTORY); + + if (!root || !system) { + LOGE("build_mount_tree: failed to allocate root/system nodes"); + node_free(root); + node_free(system); + return NULL; + } + + DIR *d = opendir(mdir); + if (!d) { + LOGE("opendir %s: %s", mdir, strerror(errno)); + node_free(root); + node_free(system); + return NULL; + } + + struct dirent *de; + bool has_any = false; + + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + char mod[PATH_MAX], mod_sys[PATH_MAX]; + + if (path_join(mdir, de->d_name, mod, sizeof(mod)) != 0) { + LOGE("build_mount_tree: path_join failed for module=%s", de->d_name); + closedir(d); + node_free(root); + node_free(system); + return NULL; + } + + struct stat st; + if (stat(mod, &st) < 0 || !S_ISDIR(st.st_mode)) { + LOGD("build_mount_tree: skip non-dir entry %s", mod); + continue; + } + + if (module_is_disabled(mod)) { + LOGI("build_mount_tree: module %s is disabled", mod); + continue; + } + + if (path_join(mod, "system", mod_sys, sizeof(mod_sys)) != 0) { + LOGE("build_mount_tree: path_join failed for module=%s system dir", mod); + closedir(d); + node_free(root); + node_free(system); + return NULL; + } + + if (!path_is_dir(mod_sys)) { + LOGD("build_mount_tree: module %s has no system dir (%s), skip", mod, mod_sys); + continue; + } + + LOGI("build_mount_tree: collecting module %s", de->d_name); + ctx->stats.modules_total++; + + bool sub = false; + if (node_scan_dir(ctx, system, mod_sys, de->d_name, &sub) != 0) { + LOGE("build_mount_tree: node_scan_dir failed for module=%s", de->d_name); + closedir(d); + node_free(root); + node_free(system); + return NULL; + } + if (sub) { + LOGD("build_mount_tree: module %s contributed content", de->d_name); + has_any = true; + } else { + LOGD("build_mount_tree: module %s had no effective content", de->d_name); + } + } + + closedir(d); + + if (!has_any) { + LOGW("build_mount_tree: no module contributed any content, abort"); + node_free(root); + node_free(system); + return NULL; + } + + ctx->stats.nodes_total += 2; + + if (symlink_resolve_all_partition_links(ctx, system) != 0) { + LOGW("symlink compatibility handling encountered errors (continuing anyway)"); + } + + // Promote builtin partitions to root + const char *builtin_parts[] = { "vendor", "system_ext", "product" }; + for (size_t i = 0; i < sizeof(builtin_parts) / sizeof(builtin_parts[0]); ++i) { + const char *part = builtin_parts[i]; + LOGD("build_mount_tree: trying to promote builtin partition '%s' to /", part); + + if (partition_promote_to_root(root, system, part, true) != 0) { + LOGE("build_mount_tree: partition_promote_to_root failed for builtin partition '%s'", part); + node_free(root); + node_free(system); + return NULL; + } else { + LOGD("build_mount_tree: partition_promote_to_root finished for builtin partition '%s'", part); + } + } + + // Handle extra partitions + for (int i = 0; i < ctx->extra_parts_count; ++i) { + const char *name = ctx->extra_parts[i]; + char rp[PATH_MAX]; + + LOGD("build_mount_tree: handling extra partition '%s' (index=%d)", name, i); + + if (path_join("/", name, rp, sizeof(rp)) != 0) { + LOGE("build_mount_tree: path_join failed for extra partition '%s'", name); + continue; + } + + if (!path_is_dir(rp)) { + LOGD("build_mount_tree: extra partition '%s' skipped, real path '%s' is not a dir", name, rp); + continue; + } + + LOGD("build_mount_tree: extra partition '%s' has real dir '%s', creating node", name, rp); + + Node *child = node_new(name, NFT_DIRECTORY); + if (!child) { + LOGE("build_mount_tree: failed to allocate node for extra partition '%s'", name); + node_free(root); + node_free(system); + return NULL; + } + + LOGD("build_mount_tree: collecting extra partition '%s' from modules", name); + + int ret = partition_scan_from_modules(ctx, name, child); + if (ret == 0) { + LOGI("build_mount_tree: collected extra partition '%s' from module root", name); + if (node_child_append(root, child) != 0) { + LOGE("build_mount_tree: failed to attach extra partition '%s' node to root", name); + node_free(child); + node_free(root); + node_free(system); + return NULL; + } + LOGD("build_mount_tree: extra partition '%s' attached to root", name); + } else if (ret == 1) { + LOGD("build_mount_tree: no content found for extra partition '%s', dropping node", name); + node_free(child); + } else { + LOGE("build_mount_tree: partition_scan_from_modules failed for extra partition '%s' (ret=%d)", + name, ret); + node_free(child); + node_free(root); + node_free(system); + return NULL; + } + } + + LOGD("build_mount_tree: attaching /system node to root"); + if (node_child_append(root, system) != 0) { + LOGE("build_mount_tree: failed to attach /system node to root"); + node_free(root); + node_free(system); + return NULL; + } + + LOGI("build_mount_tree: root tree successfully built"); + return root; +} + +void module_tree_cleanup(MagicMount *ctx) +{ + if (!ctx) return; + + str_array_free(&ctx->failed_modules, &ctx->failed_modules_count); + str_array_free(&ctx->extra_parts, &ctx->extra_parts_count); +} diff --git a/src/module_tree.h b/src/module_tree.h new file mode 100644 index 0000000..d3a486c --- /dev/null +++ b/src/module_tree.h @@ -0,0 +1,49 @@ +#ifndef MODULE_TREE_H +#define MODULE_TREE_H + +#include +#include +#include +#include "magic_mount.h" + +/* Node Type */ +typedef enum { + NFT_REGULAR, + NFT_DIRECTORY, + NFT_SYMLINK, + NFT_WHITEOUT +} NodeFileType; + +/* Node */ +typedef struct Node { + char *name; + NodeFileType type; + struct Node **children; + size_t child_count; + char *module_path; + char *module_name; + bool replace; + bool skip; + bool done; +} Node; + +/* Node utils func */ +NodeFileType node_type_from_stat(const struct stat *st); +Node * node_child_find(Node *parent, const char *name); +void node_free(Node *n); + +/* Collect the root node from the module directory: + * ctx->module_dir...,Status writing ctx->stats + */ +Node *build_mount_tree(MagicMount *ctx); + +/* ctx->extra_parts */ +void extra_partition_register(MagicMount *ctx, const char *start, size_t len); + +/* ctx->failed_modules */ +void module_mark_failed(MagicMount *ctx, const char *module_name); + +/*(extra_parts/failed_modules) */ +void module_tree_cleanup(MagicMount *ctx); + +#endif /* MODULE_TREE_H */ diff --git a/src/utils.c b/src/utils.c index 33511d6..c07136b 100644 --- a/src/utils.c +++ b/src/utils.c @@ -9,36 +9,148 @@ #include #include #include +#include #include +#include -FILE *g_log_file = NULL; /* stderr */ -int g_log_level = 2; /* INFO */ +/* --- log func --- */ -static void vlog(const char *lv, const char *fmt, va_list ap) +FILE *g_log_file = NULL; +log_level_t g_log_level = LOG_INFO; + +bool g_log_initialized = false; + +struct log_entry { + char *line; +}; + +static struct log_entry *g_log_buf = NULL; +static size_t g_log_count = 0; +static size_t g_log_cap = 0; + +static const char *log_level_str(log_level_t lv) { + switch (lv) { + case LOG_ERROR: return "ERROR"; + case LOG_WARN: return "WARN"; + case LOG_INFO: return "INFO"; + case LOG_DEBUG: return "DEBUG"; + } + return "?"; +} + +static void log_buffer_append(const char *line) +{ + if (!line) + return; + + if (g_log_count == g_log_cap) { + size_t new_cap = g_log_cap ? g_log_cap * 2 : 16; + struct log_entry *new_buf = + realloc(g_log_buf, new_cap * sizeof(*new_buf)); + if (!new_buf) { + fprintf(stderr, "%s\n", line); + return; + } + g_log_buf = new_buf; + g_log_cap = new_cap; + } + + g_log_buf[g_log_count].line = strdup(line); + if (!g_log_buf[g_log_count].line) { + fprintf(stderr, "%s\n", line); + return; + } + g_log_count++; +} + +static void log_flush_buffer(FILE *out) +{ + if (!out) + out = stderr; + + for (size_t i = 0; i < g_log_count; i++) { + if (g_log_buf[i].line) { + fputs(g_log_buf[i].line, out); + fputc('\n', out); + free(g_log_buf[i].line); + } + } + + free(g_log_buf); + g_log_buf = NULL; + g_log_count = 0; + g_log_cap = 0; + + fflush(out); +} + +void log_set_file(FILE *fp) +{ + g_log_file = fp; + FILE *out = g_log_file ? g_log_file : stderr; - fprintf(out, "[%s] ", lv); - vfprintf(out, fmt, ap); + + if (!g_log_initialized) { + g_log_initialized = true; + if (g_log_count > 0) { + log_flush_buffer(out); + } + } +} + +void log_set_level(log_level_t lv) +{ + g_log_level = lv; +} + +void log_write(log_level_t lv, const char *file, int line, + const char *fmt, ...) +{ + (void)lv; + + char buf[1024]; + + int off = snprintf(buf, sizeof(buf), + "[%s] %s:%d: ", + log_level_str(lv), file, line); + if (off < 0) + return; + if ((size_t)off >= sizeof(buf)) + off = (int)(sizeof(buf) - 1); + + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(buf + off, sizeof(buf) - (size_t)off, fmt, ap); + va_end(ap); + + if (n < 0) + return; + + buf[sizeof(buf) - 1] = '\0'; + + if (!g_log_initialized) { + log_buffer_append(buf); + return; + } + + FILE *out = g_log_file ? g_log_file : stderr; + + fputs(buf, out); fputc('\n', out); fflush(out); } -void log_msg(const char *lv, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vlog(lv, fmt, ap); - va_end(ap); -} +/* --- path helpers --- */ int path_join(const char *base, const char *name, char *buf, size_t n) { - if (!base) { + if (!base || !buf || n == 0) { errno = EINVAL; return -1; } - if (!name || !name[0]) { + if (!name || name[0] == '\0') { if (snprintf(buf, n, "%s", base) >= (int)n) { errno = ENAMETOOLONG; return -1; @@ -46,23 +158,25 @@ int path_join(const char *base, const char *name, char *buf, size_t n) return 0; } - if (!base[0] || (base[0] == '/' && !base[1])) { + if (base[0] == '\0' || (base[0] == '/' && base[1] == '\0')) { if (snprintf(buf, n, "/%s", name) >= (int)n) { errno = ENAMETOOLONG; return -1; } + return 0; + } + + size_t len = strlen(base); + + if (base[len - 1] == '/') { + if (snprintf(buf, n, "%s%s", base, name) >= (int)n) { + errno = ENAMETOOLONG; + return -1; + } } else { - size_t len = strlen(base); - if (base[len - 1] == '/') { - if (snprintf(buf, n, "%s%s", base, name) >= (int)n) { - errno = ENAMETOOLONG; - return -1; - } - } else { - if (snprintf(buf, n, "%s/%s", base, name) >= (int)n) { - errno = ENAMETOOLONG; - return -1; - } + if (snprintf(buf, n, "%s/%s", base, name) >= (int)n) { + errno = ENAMETOOLONG; + return -1; } } return 0; @@ -97,7 +211,6 @@ int mkdir_p(const char *dir) if (stat(dir, &st) == 0) { if (S_ISDIR(st.st_mode)) return 0; - LOGE("%s is not directory", dir); errno = ENOTDIR; return -1; } @@ -110,19 +223,22 @@ int mkdir_p(const char *dir) strcpy(tmp, dir); char *s = strrchr(tmp, '/'); + if (s && s != tmp) { - *s = 0; - if (mkdir_p(tmp) != 0) + *s = '\0'; + if (mkdir_p(tmp) < 0) return -1; } if (mkdir(dir, 0755) == 0 || errno == EEXIST) return 0; - LOGE("mkdir %s: %s", dir, strerror(errno)); return -1; } + +/* --- tmpfs check and tempdir set --- */ + static bool is_rw_tmpfs(const char *path) { if (!path_is_dir(path)) @@ -131,7 +247,8 @@ static bool is_rw_tmpfs(const char *path) struct statfs s; if (statfs(path, &s) < 0) return false; - if ((unsigned long)s.f_type != (unsigned long)TMPFS_MAGIC) + + if ((unsigned long)s.f_type != TMPFS_MAGIC) return false; char tmpl[PATH_MAX]; @@ -141,94 +258,203 @@ static bool is_rw_tmpfs(const char *path) int fd = mkstemp(tmpl); if (fd < 0) return false; + close(fd); unlink(tmpl); return true; } -void free_string_array(char ***arr, int *count) +const char *select_auto_tempdir(char buf[PATH_MAX]) +{ + const char *candidates[] = { + "/mnt/vendor", + "/mnt", + "/debug_ramdisk", + }; + + size_t n = sizeof(candidates) / sizeof(candidates[0]); + + for (size_t i = 0; i < n; i++) { + + if (!is_rw_tmpfs(candidates[i])) + continue; + + if (path_join(candidates[i], ".magic_mount", buf, PATH_MAX) == 0) { + LOGI("auto tempdir selected: %s (from %s)", buf, candidates[i]); + return buf; + } + } + + LOGW("no rw tmpfs found, fallback to %s", DEFAULT_TEMP_DIR); + return DEFAULT_TEMP_DIR; +} + +/* --- str utils --- */ + +char *str_trim(char *str) +{ + if (!str) + return NULL; + + char *s = str; + + /* Trim leading whitespace */ + while (*s && isspace((unsigned char)*s)) + s++; + + if (*s == '\0') { + *str = '\0'; + return str; + } + + char *end = s + strlen(s) - 1; + while (end > s && isspace((unsigned char)*end)) + end--; + + end[1] = '\0'; + + if (s != str) + memmove(str, s, strlen(s) + 1); + + return str; +} + + +bool str_is_true(const char *str) +{ + if (!str) + return false; + + return !strcasecmp(str, "true") || + !strcasecmp(str, "yes") || + !strcasecmp(str, "1") || + !strcasecmp(str, "on"); +} + +bool str_array_append(char ***arr, int *count, const char *str) +{ + if (!arr || !count || !str) { + errno = EINVAL; + return false; + } + + char **old = *arr; + int old_count = *count; + int new_count = old_count + 1; + + char **tmp = realloc(old, (size_t)new_count * sizeof(char *)); + if (!tmp) + return false; + + tmp[old_count] = strdup(str); + if (!tmp[old_count]) { + *arr = tmp; + *count = old_count; + return false; + } + + *arr = tmp; + *count = new_count; + return true; +} + +void str_array_free(char ***arr, int *count) { if (!arr || !*arr || !count) return; - for (int i = 0; i < *count; ++i) + + for (int i = 0; i < *count; i++) free((*arr)[i]); + free(*arr); *arr = NULL; *count = 0; } -const char *select_auto_tempdir(char buf[PATH_MAX]) -{ - const char *tmp_list[] = { "/mnt/vendor", "/mnt", "/debug_ramdisk" }; - size_t n = sizeof(tmp_list) / sizeof(tmp_list[0]); +/* --- SELinux xattr --- */ - for (size_t i = 0; i < n; ++i) { - const char *base = tmp_list[i]; - if (!is_rw_tmpfs(base)) - continue; - if (path_join(base, ".magic_mount", buf, PATH_MAX) != 0) - continue; - - LOGI("auto tempdir selected: %s (from %s)", buf, base); - return buf; - } - - LOGW("no rw tmpfs found in candidates, fallback to %s", - DEFAULT_TEMP_DIR); - return DEFAULT_TEMP_DIR; -} - -int set_selinux(const char *path, const char *con) +int set_selcon(const char *path, const char *con) { if (!path || !con) { - LOGD("set_selinux called with path=%p, con=%p (skip)", - (void *)path, (void *)con); + LOGD("set_selcon: skip null args"); return 0; } - LOGD("set_selinux(%s, \"%s\")", path, con); + LOGD("set_selcon(%s, \"%s\")", path, con); if (lsetxattr(path, SELINUX_XATTR, con, strlen(con), 0) < 0) { LOGW("setcon %s: %s", path, strerror(errno)); return -1; } - LOGD("set_selinux(%s) ok", path); return 0; } -int get_selinux(const char *path, char **out) +int get_selcon(const char *path, char **out) { *out = NULL; + if (!path) { - LOGD("get_selinux called with NULL path"); + LOGD("get_selcon: null path"); errno = EINVAL; return -1; } - LOGD("get_selinux(%s)", path); - ssize_t sz = lgetxattr(path, SELINUX_XATTR, NULL, 0); if (sz < 0) { LOGW("getcon %s: %s", path, strerror(errno)); return -1; } - char *buf = malloc((size_t)sz + 1); + char *buf = malloc(sz + 1); if (!buf) { errno = ENOMEM; return -1; } - ssize_t r = lgetxattr(path, SELINUX_XATTR, buf, (size_t)sz); - if (r < 0) { - LOGW("getcon %s (read): %s", path, strerror(errno)); + sz = lgetxattr(path, SELINUX_XATTR, buf, sz); + if (sz < 0) { + LOGW("getcon %s: %s", path, strerror(errno)); free(buf); return -1; } - buf[r] = '\0'; + + buf[sz] = '\0'; *out = buf; - LOGD("get_selinux(%s) -> \"%s\"", path, buf); + LOGD("get_selcon(%s) -> \"%s\"", path, buf); + return 0; +} + +int copy_selcon(const char *src, const char *dst) +{ + if (!src || !dst) { + LOGD("copy_selcon: skip null args"); + errno = EINVAL; + return -1; + } + + LOGD("copy_selcon(%s -> %s)", src, dst); + + char *con = NULL; + if (get_selcon(src, &con) < 0) + return -1; + + int ret = set_selcon(dst, con); + free(con); + + return ret; +} + +/* --- Permission check --- */ + +int root_check(void) +{ + /* Check root privileges */ + if (geteuid() != 0) { + LOGE("Must run as root (current euid=%d)", (int)geteuid()); + return -1; + } + return 0; } diff --git a/src/utils.h b/src/utils.h index 2f139fd..28280d9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -6,8 +6,35 @@ #include #include -#define SELINUX_XATTR "security.selinux" -#define DEFAULT_TEMP_DIR "/dev/.magic_mount" +typedef enum { + LOG_ERROR = 0, + LOG_WARN = 1, + LOG_INFO = 2, + LOG_DEBUG = 3, +} log_level_t; + +/* global log level and log file */ +extern FILE *g_log_file; +extern log_level_t g_log_level; + +void log_set_file(FILE *fp); +void log_set_level(log_level_t lv); + +/* log core func */ +void log_write(log_level_t lv, const char *file, int line, + const char *fmt, ...) + __attribute__((format(printf, 4, 5))); + +#define LOG(lv, ...) \ + do { \ + if ((lv) <= g_log_level) \ + log_write((lv), __FILE__, __LINE__, __VA_ARGS__); \ + } while (0) + +#define LOGE(...) LOG(LOG_ERROR, __VA_ARGS__) +#define LOGW(...) LOG(LOG_WARN, __VA_ARGS__) +#define LOGI(...) LOG(LOG_INFO, __VA_ARGS__) +#define LOGD(...) LOG(LOG_DEBUG, __VA_ARGS__) #ifndef TMPFS_MAGIC #define TMPFS_MAGIC 0x01021994 @@ -17,45 +44,32 @@ #define PATH_MAX 4096 #endif -/* from linux/sched.h normally */ -int setns(int fd, int nstype); - -extern FILE *g_log_file; -extern int g_log_level; /* 0=ERROR,1=WARN,2=INFO,3=DEBUG */ - -void log_msg(const char *lv, const char *fmt, ...); - -#define LV_ERROR 0 -#define LV_WARN 1 -#define LV_INFO 2 -#define LV_DEBUG 3 - -#define LOG(lv, ...) \ - do { \ - if ((lv) <= g_log_level) \ - log_msg((lv) == LV_ERROR ? "ERROR" : \ - (lv) == LV_WARN ? "WARN" : \ - (lv) == LV_INFO ? "INFO" : \ - "DEBUG", \ - __VA_ARGS__); \ - } while (0) - -#define LOGE(...) LOG(LV_ERROR, __VA_ARGS__) -#define LOGW(...) LOG(LV_WARN, __VA_ARGS__) -#define LOGI(...) LOG(LV_INFO, __VA_ARGS__) -#define LOGD(...) LOG(LV_DEBUG, __VA_ARGS__) +#define SELINUX_XATTR "security.selinux" +#define DEFAULT_TEMP_DIR "/dev/.magic_mount" +/* path helpers */ int path_join(const char *base, const char *name, char *buf, size_t n); bool path_exists(const char *p); bool path_is_dir(const char *p); bool path_is_symlink(const char *p); int mkdir_p(const char *dir); +/* temp directory auto-selection */ const char *select_auto_tempdir(char buf[PATH_MAX]); -void free_string_array(char ***arr, int *count); +/* str utils */ -int set_selinux(const char *path, const char *con); -int get_selinux(const char *path, char **out); +char *str_trim(char *str); +bool str_is_true(const char *str); +bool str_array_append(char ***arr, int *count, const char *str); +void str_array_free(char ***arr, int *count); + +/* SELinux xattr helpers */ +int set_selcon(const char *path, const char *con); +int get_selcon(const char *path, char **out); +int copy_selcon(const char *src, const char *dst); + +/* Permission check */ +int root_check(void); #endif /* UTILS_H */ diff --git a/template/customize.sh b/template/customize.sh index a3ed1c1..00a1e01 100644 --- a/template/customize.sh +++ b/template/customize.sh @@ -70,12 +70,12 @@ fi mkdir -p "$MODPATH" || abort "! Failed to create module directory" # Install the binary -if ! cp "$TMPDIR/bin/$ARCH_BINARY" "$MODPATH/magic_mount"; then +if ! cp "$TMPDIR/bin/$ARCH_BINARY" "$MODPATH/mmd"; then abort "! Failed to install binary" fi # Set executable permissions -if ! chmod 755 "$MODPATH/magic_mount"; then +if ! chmod 755 "$MODPATH/mmd"; then abort "! Failed to set binary permissions" fi diff --git a/template/metamount.sh b/template/metamount.sh index da4c84d..8c0ea57 100644 --- a/template/metamount.sh +++ b/template/metamount.sh @@ -6,7 +6,7 @@ MODDIR="${0%/*}" # Binary path (architecture-specific binary selected during installation) -BINARY="$MODDIR/magic_mount" +BINARY="$MODDIR/mmd" if [ ! -f "$BINARY" ]; then log "ERROR: Binary not found: $BINARY"