feat: add symlink compatibility for module partition layout

Support non-standard module structure where system/<partition> uses
symlinks (e.g., system/vendor -> ../vendor) pointing to sibling
directories at module root level.

Problem:
When a module uses the layout:
  module/
  ├── system/
  │   └── vendor -> ../vendor
  └── vendor/
      └── etc/config

The symlink node in system/ would conflict with the real vendor/
directory, causing mounts to be skipped or fail.
This commit is contained in:
7a72 2025-11-24 04:19:39 +08:00
commit e3ffb43a1b

View file

@ -314,6 +314,170 @@ static int node_collect(Node *self, const char *dir, const char *module_name,
return 0;
}
static int handle_symlink_compatibility(Node *system, const char *part_name)
{
if (!system || !part_name)
return -1;
Node *sys_child = node_find_child(system, part_name);
if (!sys_child || sys_child->type != NFT_SYMLINK)
return 0;
if (!sys_child->module_path)
return 0;
char link_target[PATH_MAX];
ssize_t len = readlink(sys_child->module_path, link_target, sizeof(link_target) - 1);
if (len < 0) {
LOGW("readlink %s failed: %s", sys_child->module_path, strerror(errno));
return 0;
}
link_target[len] = '\0';
size_t target_len = strlen(link_target);
while (target_len > 0 && link_target[target_len - 1] == '/') {
link_target[--target_len] = '\0';
}
if (target_len == 0) {
LOGW("empty symlink target for %s", part_name);
return 0;
}
// check ../<part_name> or ./<part_name>
char expected_relative[PATH_MAX];
char expected_absolute[PATH_MAX];
snprintf(expected_relative, sizeof(expected_relative), "../%s", part_name);
snprintf(expected_absolute, sizeof(expected_absolute), "/%s", part_name);
bool is_relative = (strcmp(link_target, expected_relative) == 0);
bool is_absolute = (strcmp(link_target, expected_absolute) == 0);
if (!is_relative && !is_absolute) {
LOGD("symlink %s -> %s (not pointing to sibling directory, expected %s or %s)",
part_name, link_target, expected_relative, expected_absolute);
return 0;
}
LOGI("found compatible symlink: system/%s -> %s", part_name, link_target);
DIR *mod_dir = opendir(g_module_dir);
if (!mod_dir) {
LOGE("opendir %s: %s", g_module_dir, strerror(errno));
return -1;
}
struct dirent *mod_de;
bool found_real_dir = false;
char real_part_path[PATH_MAX];
char module_name_buf[256] = {0};
while ((mod_de = readdir(mod_dir))) {
if (!strcmp(mod_de->d_name, ".") || !strcmp(mod_de->d_name, ".."))
continue;
char mod_path[PATH_MAX];
char part_path[PATH_MAX];
if (path_join(g_module_dir, mod_de->d_name, mod_path, sizeof(mod_path)) != 0)
continue;
struct stat mod_st;
if (stat(mod_path, &mod_st) < 0 || !S_ISDIR(mod_st.st_mode))
continue;
if (module_disabled(mod_path))
continue;
// check <module>/<part_name>
if (path_join(mod_path, part_name, part_path, sizeof(part_path)) != 0)
continue;
if (path_is_dir(part_path)) {
found_real_dir = true;
strncpy(real_part_path, part_path, sizeof(real_part_path) - 1);
real_part_path[sizeof(real_part_path) - 1] = '\0';
strncpy(module_name_buf, mod_de->d_name, sizeof(module_name_buf) - 1);
module_name_buf[sizeof(module_name_buf) - 1] = '\0';
break;
}
}
closedir(mod_dir);
if (!found_real_dir) {
LOGD("no real directory found for %s, keeping symlink", part_name);
return 0;
}
LOGI("symlink compatibility: system/%s -> %s, real dir in module '%s'",
part_name, link_target, module_name_buf);
Node *new_part = node_new(part_name, NFT_DIRECTORY);
if (!new_part) {
LOGE("failed to create node for %s", part_name);
return -1;
}
bool part_has_any = false;
if (node_collect(new_part, real_part_path, module_name_buf, &part_has_any) != 0) {
LOGE("failed to collect %s from %s", part_name, real_part_path);
node_free(new_part);
return -1;
}
if (!part_has_any) {
LOGD("no content in %s, keeping symlink", part_name);
node_free(new_part);
return 0;
}
Node *removed = node_take_child(system, part_name);
if (removed) {
node_free(removed);
LOGD("removed symlink node: system/%s", part_name);
}
new_part->module_name = strdup(module_name_buf);
if (node_add_child(system, new_part) != 0) {
LOGE("failed to add directory node for %s", part_name);
node_free(new_part);
return -1;
}
LOGI("replaced symlink with directory node: %s (from module '%s')",
part_name, module_name_buf);
return 0;
}
static int handle_all_symlink_compatibility(Node *system)
{
if (!system)
return -1;
const char *builtin_parts[] = {
"vendor",
"system_ext",
"product",
"odm",
};
for (size_t i = 0; i < sizeof(builtin_parts) / sizeof(builtin_parts[0]); ++i) {
if (handle_symlink_compatibility(system, builtin_parts[i]) != 0) {
LOGE("failed to handle symlink compatibility for %s", builtin_parts[i]);
}
}
for (int i = 0; i < g_extra_parts_count; ++i) {
if (handle_symlink_compatibility(system, g_extra_parts[i]) != 0) {
LOGE("failed to handle symlink compatibility for extra part %s",
g_extra_parts[i]);
}
}
return 0;
}
static Node *collect_root(void)
{
const char *mdir = g_module_dir;
@ -391,6 +555,12 @@ static Node *collect_root(void)
g_stats.nodes_total += 2;
// ============ 处理 Symlink 兼容性 ============
if (handle_all_symlink_compatibility(system) != 0) {
LOGW("symlink compatibility handling encountered errors (continuing anyway)");
}
// ============================================
struct {
const char *name;
bool need_symlink;
@ -566,27 +736,39 @@ static int do_magic(const char *base, const char *wbase, Node *node,
if (path_join(path, c->name, rp, sizeof(rp)) != 0)
return -1;
LOGD("checking child: parent=%s, child=%s, joined_path=%s",
path, c->name, rp);
bool need = false;
if (c->type == NFT_SYMLINK) {
need = true;
LOGD("child %s is SYMLINK", c->name);
} else if (c->type == NFT_WHITEOUT) {
need = path_exists(rp);
LOGD("child %s is WHITEOUT, path_exists=%d, need=%d", c->name, need, need);
} else {
struct stat st;
if (lstat(rp, &st) == 0) {
NodeFileType rt = node_type_from_stat(&st);
LOGD("type mismatch check: %s - expected=%d, actual=%d, is_symlink=%d",
rp, c->type, rt, rt == NFT_SYMLINK ? 1 : 0);
if (rt != c->type || rt == NFT_SYMLINK)
need = true;
} else {
LOGD("lstat failed for %s: %s (errno=%d), path_exists=%d",
rp, strerror(errno), errno, path_exists(rp) ? 1 : 0);
need = true;
}
}
LOGD("child check: parent=%s, child=%s, type=%d, need=%d, has_module_path=%d",
path, c->name, c->type, need, node->module_path ? 1 : 0);
if (need) {
if (!node->module_path) {
LOGE("cannot create tmpfs on %s (%s)",
path, c->name);
LOGE("cannot create tmpfs on %s (%s) - child type: %d, target exists: %d",
path, c->name, c->type, path_exists(rp) ? 1 : 0);
c->skip = true;
g_stats.nodes_skipped++;
continue;