#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int setns(int fd, int nstype); /*============================================================================== * 常量 & 宏 *============================================================================*/ #define MAGIC_MOUNT_VERSION "0.1.0" #define DEFAULT_MOUNT_SOURCE "KSU" #define DEFAULT_MODULE_DIR "/data/adb/modules" #define DEFAULT_TEMP_DIR "/dev/.magic_mount" // fallback #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 SELINUX_XATTR "security.selinux" #ifndef PATH_MAX #define PATH_MAX 4096 #endif #ifndef TMPFS_MAGIC #define TMPFS_MAGIC 0x01021994 #endif #define UNUSED(x) (void)(x) /*============================================================================== * 统计信息 & 全局配置 *============================================================================*/ typedef struct { int modules_total; int nodes_total; int nodes_mounted; int nodes_skipped; int nodes_whiteout; int nodes_fail; } MountStats; static MountStats g_stats = {0}; static const char *g_module_dir = DEFAULT_MODULE_DIR; static const char *g_mount_source = DEFAULT_MOUNT_SOURCE; static FILE *g_log_file = NULL; // NULL => stderr static int g_log_level = 2; // 0=ERROR,1=WARN,2=INFO,3=DEBUG /* 记录发生失败的模块名 & 额外分区列表 */ static char **g_failed_modules = NULL; static int g_failed_modules_count = 0; static char **g_extra_parts = NULL; // 额外分区名,如 "my_custom" static int g_extra_parts_count = 0; /*============================================================================== * 日志 *============================================================================*/ static void vlog(const char *lv, const char *fmt, va_list ap) { FILE *out = g_log_file ? g_log_file : stderr; fprintf(out, "[%s] ", lv); vfprintf(out, fmt, ap); fputc('\n', out); fflush(out); } static void log_msg(const char *lv, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(lv, fmt, ap); va_end(ap); } #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__) /*============================================================================== * 工具函数 *============================================================================*/ static int path_join(const char *base, const char *name, char *buf, size_t n) { if (!base) { errno = EINVAL; return -1; } if (!name || !name[0]) { if (snprintf(buf, n, "%s", base) >= (int)n) { errno = ENAMETOOLONG; return -1; } return 0; } if (!base[0] || (base[0]=='/' && !base[1])) { if (snprintf(buf, n, "/%s", 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; } } } return 0; } static bool path_exists(const char *p) { struct stat st; return (stat(p, &st) == 0); } static bool path_is_dir(const char *p) { struct stat st; return (stat(p, &st) == 0) && S_ISDIR(st.st_mode); } static bool path_is_symlink(const char *p) { struct stat st; return (lstat(p, &st) == 0) && S_ISLNK(st.st_mode); } static int mkdir_p(const char *dir) { if (!dir || !dir[0]) { errno = EINVAL; return -1; } struct stat st; if (stat(dir, &st) == 0) { if (S_ISDIR(st.st_mode)) return 0; LOGE("%s is not directory", dir); errno = ENOTDIR; return -1; } char tmp[PATH_MAX]; if (strlen(dir) >= sizeof(tmp)) { errno = ENAMETOOLONG; return -1; } strcpy(tmp, dir); char *s = strrchr(tmp, '/'); if (s && s != tmp) { *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; } static bool is_rw_tmpfs(const char *path) { if (!path_is_dir(path)) return false; struct statfs s; if (statfs(path, &s) < 0) return false; if ((unsigned long)s.f_type != (unsigned long)TMPFS_MAGIC) return false; char tmpl[PATH_MAX]; if (path_join(path, ".magic_mount_testXXXXXX", tmpl, sizeof(tmpl)) != 0) return false; int fd = mkstemp(tmpl); if (fd < 0) return false; close(fd); unlink(tmpl); return true; } /* 释放字符串数组并清零 */ static void free_string_array(char ***arr, int *count) { if (!arr || !*arr || !count) return; for (int i = 0; i < *count; ++i) free((*arr)[i]); free(*arr); *arr = NULL; *count = 0; } /* 自动选择 tempdir:第一个 rw tmpfs 下的 .magic_mount,否则 fallback */ static 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]); 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; } /* 记录模块失败(去重) */ 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++; } /* 添加额外分区名,name 为单个 token(不含逗号) */ static 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; } /*============================================================================== * SELinux xattr(通用接口) *============================================================================*/ static int lsetfilecon(const char *path, const char *con) { #if defined(__linux__) if (!path || !con) { LOGD("lsetfilecon called with path=%p, con=%p (skip)", (void*)path, (void*)con); return 0; } LOGD("lsetfilecon(%s, \"%s\")", path, con); if (lsetxattr(path, SELINUX_XATTR, con, strlen(con), 0) < 0) { LOGW("setcon %s: %s", path, strerror(errno)); return -1; } LOGD("lsetfilecon(%s) ok", path); #else LOGD("lsetfilecon(%s, %s) skipped (no SELinux on this platform)", path ? path : "(null)", con ? con : "(null)"); UNUSED(path); UNUSED(con); #endif return 0; } static int lgetfilecon(const char *path, char **out) { *out = NULL; #if defined(__linux__) if (!path) { LOGD("lgetfilecon called with NULL path"); errno = EINVAL; return -1; } LOGD("lgetfilecon(%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); 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)); free(buf); return -1; } buf[r] = '\0'; *out = buf; LOGD("lgetfilecon(%s) -> \"%s\"", path, buf); #else LOGD("lgetfilecon(%s) skipped (no SELinux on this platform)", path ? path : "(null)"); UNUSED(path); #endif return 0; } /*============================================================================== * Node 结构 & 模块扫描 *============================================================================*/ 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; // 模块名(目录名),例如 "foo" 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) return false; buf[len] = '\0'; return (strcmp(buf, "y") == 0); } /* module_name 为模块名,例如 "foo" */ 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; } /* 递归收集模块文件,module_name 为模块名 */ 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 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; } /* root 和 system 自身也计入节点数 */ g_stats.nodes_total += 2; /* * 内置分区: * vendor, system_ext, product, odm * 其中前三个要求 /system/ 是 symlink。 * 额外分区(-p)行为类似 odm:只检查 / 是否为目录。 */ struct { const char *name; bool need_symlink; } builtin_parts[] = { { "vendor", true }, { "system_ext", true }, { "product", true }, { "odm", false }, }; char rp[PATH_MAX]; char sp[PATH_MAX]; // builtin 分区 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; } } // 额外分区(行为类似 odm) 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_take_child(system, name); if (child && node_add_child(root, child) != 0) { 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; } /*============================================================================== * magic mount 挂载逻辑 *============================================================================*/ static int clone_symlink(const char *src, const char *dst) { char target[PATH_MAX]; ssize_t len = readlink(src, target, sizeof(target) - 1); if (len < 0) { LOGE("readlink %s: %s", src, strerror(errno)); return -1; } target[len] = '\0'; if (symlink(target, dst) < 0) { LOGE("symlink %s->%s: %s", dst, target, strerror(errno)); return -1; } char *con = NULL; if (lgetfilecon(src, &con) == 0 && con) { lsetfilecon(dst, con); free(con); } 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 do_magic(const char *base, const char *wbase, Node *node, bool has_tmpfs) { char path[PATH_MAX]; char wpath[PATH_MAX]; if (path_join(base, node->name, path, sizeof(path)) != 0 || path_join(wbase, node->name, wpath, sizeof(wpath)) != 0) return -1; switch (node->type) { case NFT_REGULAR: { const char *target = has_tmpfs ? wpath : path; if (has_tmpfs) { if (mkdir_p(wbase) != 0) return -1; int fd = open(wpath, O_CREAT | O_WRONLY, 0644); if (fd < 0) { LOGE("create %s: %s", wpath, strerror(errno)); return -1; } close(fd); } if (!node->module_path) { LOGE("no module file for %s", path); errno = EINVAL; return -1; } LOGD("bind %s -> %s", node->module_path, target); if (mount(node->module_path, target, NULL, MS_BIND, NULL) < 0) { LOGE("bind %s->%s: %s", node->module_path, target, strerror(errno)); return -1; } (void)mount(NULL, target, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); g_stats.nodes_mounted++; return 0; } case NFT_SYMLINK: { if (!node->module_path) { LOGE("no module symlink for %s", path); errno = EINVAL; return -1; } if (clone_symlink(node->module_path, wpath) != 0) return -1; g_stats.nodes_mounted++; return 0; } case NFT_WHITEOUT: LOGD("whiteout %s", path); g_stats.nodes_whiteout++; return 0; case NFT_DIRECTORY: { bool create_tmp = (!has_tmpfs && node->replace && node->module_path); if (!has_tmpfs && !create_tmp) { for (size_t i = 0; i < node->child_count; ++i) { Node *c = node->children[i]; char rp[PATH_MAX]; if (path_join(path, c->name, rp, sizeof(rp)) != 0) return -1; bool need = false; if (c->type == NFT_SYMLINK) { need = true; } else if (c->type == NFT_WHITEOUT) { need = path_exists(rp); } else { struct stat st; if (lstat(rp, &st) == 0) { NodeFileType rt = node_type_from_stat(&st); if (rt != c->type || rt == NFT_SYMLINK) need = true; } else { need = true; } } if (need) { if (!node->module_path) { LOGE("cannot create tmpfs on %s (%s)", path, c->name); c->skip = true; g_stats.nodes_skipped++; continue; } create_tmp = true; break; } } } bool now_tmp = has_tmpfs || create_tmp; if (now_tmp) { if (mkdir_p(wpath) != 0) return -1; struct stat st; const char *meta_path = NULL; if (stat(path, &st) == 0) { meta_path = path; } else if (node->module_path && stat(node->module_path, &st) == 0) { meta_path = node->module_path; } else { LOGE("no dir meta for %s", path); errno = ENOENT; return -1; } chmod(wpath, st.st_mode & 07777); chown(wpath, st.st_uid, st.st_gid); char *con = NULL; if (lgetfilecon(meta_path, &con) == 0 && con) { lsetfilecon(wpath, con); free(con); } } if (create_tmp) { if (mount(wpath, wpath, NULL, MS_BIND, NULL) < 0) { LOGE("bind self %s: %s", wpath, strerror(errno)); return -1; } } if (path_exists(path) && !node->replace) { DIR *d = opendir(path); if (!d) { LOGE("opendir %s: %s", path, strerror(errno)); if (now_tmp) return -1; } else { struct dirent *de; while ((de = readdir(d))) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } Node *c = node_find_child(node, de->d_name); int r = 0; if (c) { if (c->skip) { c->done = true; continue; } c->done = true; r = do_magic(path, wpath, c, now_tmp); } else if (now_tmp) { r = mirror(path, wpath, de->d_name); } if (r != 0) { const char *mn = NULL; if (c && c->module_name) mn = c->module_name; else if (node->module_name) mn = node->module_name; if (mn) { LOGE("child %s/%s failed (module: %s)", path, c ? c->name : de->d_name, mn); register_module_failure(mn); } else { LOGE("child %s/%s failed (no module_name)", path, c ? c->name : de->d_name); } g_stats.nodes_fail++; if (now_tmp) { closedir(d); return -1; } } } closedir(d); } } for (size_t i = 0; i < node->child_count; ++i) { Node *c = node->children[i]; if (c->skip || c->done) continue; int r = do_magic(path, wpath, c, now_tmp); if (r != 0) { const char *mn = c->module_name ? c->module_name : node->module_name; if (mn) { LOGE("child %s/%s failed (module: %s)", path, c->name, mn); register_module_failure(mn); } else { LOGE("child %s/%s failed (no module_name)", path, c->name); } g_stats.nodes_fail++; if (now_tmp) return -1; } } if (create_tmp) { (void)mount(NULL, wpath, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); if (mount(wpath, path, NULL, MS_MOVE, NULL) < 0) { LOGE("move %s->%s failed: %s", wpath, path, strerror(errno)); if (node->module_name) register_module_failure(node->module_name); return -1; } LOGI("move mountpoint success: %s -> %s", wpath, path); (void)mount(NULL, path, NULL, MS_REC | MS_PRIVATE, NULL); } g_stats.nodes_mounted++; return 0; } } return 0; } static int mirror(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); char *con = NULL; if (lgetfilecon(src, &con) == 0 && con) { lsetfilecon(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; } static int magic_mount(const char *tmp_root) { Node *root = collect_root(); if (!root) { LOGI("no modules, magic_mount skipped"); return 0; } char tmp_dir[PATH_MAX]; if (path_join(tmp_root, "workdir", tmp_dir, sizeof(tmp_dir)) != 0) { node_free(root); return -1; } if (mkdir_p(tmp_dir) != 0) { node_free(root); return -1; } LOGI("starting magic_mount core logic: tmpfs_source=%s tmp_dir=%s", g_mount_source, tmp_dir); if (mount(g_mount_source, tmp_dir, "tmpfs", 0, "") < 0) { LOGE("mount tmpfs %s: %s", tmp_dir, strerror(errno)); node_free(root); return -1; } (void)mount(NULL, tmp_dir, NULL, MS_REC | MS_PRIVATE, NULL); int rc = do_magic("/", tmp_dir, root, false); if (rc != 0) g_stats.nodes_fail++; if (umount2(tmp_dir, MNT_DETACH) < 0) LOGE("umount %s: %s", tmp_dir, strerror(errno)); (void)rmdir(tmp_dir); node_free(root); return rc; } /*============================================================================== * 进程环境 & CLI *============================================================================*/ static void usage(const char *prog) { fprintf(stderr, "Magic Mount Version: %s\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. mi_ext,my_stock)\n" " -l FILE log file (default: stderr, '-'=stdout)\n" " -v N verbose (0=ERROR,1=WARN,2=INFO,3=DEBUG)\n" " -h help\n", MAGIC_MOUNT_VERSION, prog, DEFAULT_MODULE_DIR, DEFAULT_MOUNT_SOURCE); } static int enter_pid1_ns(void) { 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; } int main(int argc, char **argv) { const char *tmp_dir = NULL; const char *log_path = NULL; char auto_tmp[PATH_MAX] = {0}; for (int i = 1; i < argc; ++i) { const char *arg = argv[i]; if ((strcmp(arg, "-m") == 0 || strcmp(arg, "--module-dir") == 0) && i + 1 < argc) { g_module_dir = argv[++i]; } else if ((strcmp(arg, "-t") == 0 || strcmp(arg, "--temp-dir") == 0) && 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, "-l") == 0 || strcmp(arg, "--log-file") == 0) && i + 1 < argc) { log_path = argv[++i]; } else if ((strcmp(arg, "-v") == 0 || strcmp(arg, "--verbose") == 0) && i + 1 < argc) { g_log_level = atoi(argv[++i]); if (g_log_level < 0) g_log_level = 0; if (g_log_level > 3) g_log_level = 3; } 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, "-h") || !strcmp(arg, "--help")) { usage(argv[0]); return 0; } else { fprintf(stderr, "Unknown arg: %s\n", arg); usage(argv[0]); 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 (!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); 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); 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]); } } if (g_log_file && g_log_file != stdout && g_log_file != stderr) fclose(g_log_file); free_string_array(&g_failed_modules, &g_failed_modules_count); free_string_array(&g_extra_parts, &g_extra_parts_count); return rc == 0 ? 0 : 1; }