chore: bump version to v0.2.0
This commit is contained in:
parent
b588da2e15
commit
10460811ed
12 changed files with 1680 additions and 1126 deletions
|
|
@ -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
55
src/ksu.c
Normal 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
21
src/ksu.h
Normal 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
|
|
@ -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 */
|
||||
|
|
|
|||
447
src/main.c
447
src/main.c
|
|
@ -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
844
src/module_tree.c
Normal 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
49
src/module_tree.h
Normal 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 */
|
||||
362
src/utils.c
362
src/utils.c
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
78
src/utils.h
78
src/utils.h
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue