chore: bump version to v0.2.0

This commit is contained in:
7a72 2025-11-25 00:44:28 +08:00
commit 10460811ed
12 changed files with 1680 additions and 1126 deletions

View file

@ -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

55
src/ksu.c Normal file
View file

@ -0,0 +1,55 @@
#include "ksu.h"
#include "utils.h"
#include <errno.h>
#include <stdatomic.h>
#include <sys/syscall.h>
#include <unistd.h>
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;
}

21
src/ksu.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef KSU_H
#define KSU_H
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#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 */

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,19 @@
#ifndef MAGIC_MOUNT_H
#define MAGIC_MOUNT_H
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#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 */

View file

@ -1,4 +1,5 @@
#include "magic_mount.h"
#include "module_tree.h"
#include "utils.h"
#include <sys/types.h>
@ -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;
}

844
src/module_tree.c Normal file
View file

@ -0,0 +1,844 @@
#include "module_tree.h"
#include "magic_mount.h"
#include "utils.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
/* --- 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);
}

49
src/module_tree.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef MODULE_TREE_H
#define MODULE_TREE_H
#include <stdbool.h>
#include <stddef.h>
#include <sys/stat.h>
#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 */

View file

@ -9,36 +9,148 @@
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>
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;
}

View file

@ -6,8 +6,35 @@
#include <stddef.h>
#include <stdarg.h>
#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 */

View file

@ -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

View file

@ -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"