Fix cross-root image duplicate remount loops

This commit is contained in:
Roman Tarasov committed 2026-10-20 17:10:45 +03:00
1 parent 091dffbfcb
commit c2ddd8005d
3 files changed
+55 -13

No files matched your search

+2
View File
@@ -29,6 +29,8 @@ bool sm_image_index_visit_ready_titles(
sm_image_index_title_visitor_t visitor, const void *visitor_ctx);
// Return true when a title is mapped to an existing backing image.
bool sm_image_index_has_source_for_title(const char *title_id);
// Validate a title against a complete index with an unchanged regular source.
bool sm_image_index_source_has_title(const char *path, const char *title_id);
// Start a new fingerprint generation before mounting an image.
void sm_image_index_begin_scan(const char *path, const struct stat *st);
// Associate a discovered mounted game with its outer backing image. Returns
+36 -6
View File
@@ -324,6 +324,36 @@ static void begin_scan_locked(const char *path, const struct stat *st) {
g_image_index_dirty = true;
}
// Caller holds g_image_index_mutex. A retained link alone cannot prove that a
// replaced image still contains the same title.
static bool indexed_source_has_title(const char *path, const char *title_id) {
int entry_index = find_entry(path);
if (entry_index < 0 || !g_image_index[entry_index].complete)
return false;
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
if (g_image_titles[i].valid &&
g_image_titles[i].image_index == (uint16_t)entry_index &&
strcmp(g_image_titles[i].title_id, title_id) == 0) {
struct stat st;
return stat(path, &st) == 0 && S_ISREG(st.st_mode) &&
stamp_matches(&g_image_index[entry_index], &st);
}
}
return false;
}
bool sm_image_index_source_has_title(const char *path, const char *title_id) {
if (!path || !title_id || path[0] == '\0' || title_id[0] == '\0')
return false;
pthread_mutex_lock(&g_image_index_mutex);
load_index();
bool found = indexed_source_has_title(path, title_id);
pthread_mutex_unlock(&g_image_index_mutex);
return found;
}
static bool cached_titles_ready(int entry_index,
const struct AppDbTitleList *app_db_titles,
bool app_db_titles_ready) {
@@ -334,18 +364,18 @@ static bool cached_titles_ready(int entry_index,
continue;
}
const char *title_id = g_image_titles[i].title_id;
if (!is_installed(title_id) || !has_appmeta_data(title_id))
if ((app_db_titles_ready &&
!app_db_title_list_contains(app_db_titles, title_id)) ||
!is_installed(title_id) || !has_appmeta_data(title_id)) {
return false;
}
char runtime_path[MAX_PATH];
char linked_image[MAX_PATH];
if (!read_mount_link(title_id, runtime_path, sizeof(runtime_path)) ||
!is_under_image_mount_base(runtime_path) ||
!read_mount_image_link(title_id, linked_image, sizeof(linked_image)) ||
strcmp(linked_image, image_path) != 0) {
return false;
}
if (app_db_titles_ready &&
!app_db_title_list_contains(app_db_titles, title_id)) {
(strcmp(linked_image, image_path) != 0 &&
!indexed_source_has_title(linked_image, title_id))) {
return false;
}
}
+17 -7
View File
@@ -68,10 +68,11 @@ static void note_found_indexed_title(const char *title_id, const void *ctx) {
char linked_image[MAX_PATH];
if (!image_path ||
!read_mount_link(title_id, runtime_path, sizeof(runtime_path)) ||
!read_mount_image_link(title_id, linked_image, sizeof(linked_image)) ||
strcmp(linked_image, image_path) != 0) {
!read_mount_image_link(title_id, linked_image, sizeof(linked_image))) {
return;
}
if (strcmp(linked_image, image_path) != 0)
notify_duplicate_title_once(title_id, image_path, linked_image);
cache_game_entry(runtime_path, title_id, "");
}
@@ -338,11 +339,20 @@ static existing_directory_result_t handle_existing_directory_candidate(
}
if (installed && appmeta_present && has_tracked_path &&
strcmp(tracked_path, full_path) != 0 &&
is_data_mounted(info->title_id)) {
(void)strlcpy(preferred_existing_path_out, tracked_path, MAX_PATH);
cache_game_entry(tracked_path, info->title_id, info->title_name);
return EXISTING_DIRECTORY_PREFER_CACHED;
strcmp(tracked_path, full_path) != 0) {
char linked_image[MAX_PATH];
// Runtime layers are normally released between scans. Keep the selected
// image while its fingerprint still proves that it contains this title.
bool linked_source_valid =
is_under_image_mount_base(tracked_path) &&
read_mount_image_link(info->title_id, linked_image,
sizeof(linked_image)) &&
sm_image_index_source_has_title(linked_image, info->title_id);
if (linked_source_valid || is_data_mounted(info->title_id)) {
(void)strlcpy(preferred_existing_path_out, tracked_path, MAX_PATH);
cache_game_entry(tracked_path, info->title_id, info->title_name);
return EXISTING_DIRECTORY_PREFER_CACHED;
}
}
if (!in_app_db && is_title_install_pending(info->title_id))