From a67ff8fc4aabd78cf4106e3732fd277feeb66733 Mon Sep 17 00:00:00 2001 From: Roman Tarasov Date: Mon, 21 Sep 2026 16:59:46 +0300 Subject: [PATCH] Use private mount snapshots and preserve unknown mount state --- include/sm_filesystem.h | 6 +++ src/sm_api_service.c | 17 ++++--- src/sm_fakelib.c | 9 ++-- src/sm_filesystem.c | 98 +++++++++++++++++++++++++++++------------ src/sm_mount_device.c | 34 +++++++++++--- src/sm_mount_diag.c | 6 ++- 6 files changed, 125 insertions(+), 45 deletions(-) diff --git a/include/sm_filesystem.h b/include/sm_filesystem.h index d1d006f..bb44bb0 100644 --- a/include/sm_filesystem.h +++ b/include/sm_filesystem.h @@ -8,6 +8,12 @@ #include "sm_limits.h" +struct statfs; + +// Return a private mount-table snapshot; the caller frees *mounts_out. +// Returns the entry count, or -1 with *mounts_out == NULL on failure. +int sm_mount_table_snapshot(struct statfs **mounts_out); + // Check whether a title is present in the installed app set. bool is_installed(const char *title_id); // Check whether /user/appmeta//param.json exists. diff --git a/src/sm_api_service.c b/src/sm_api_service.c index ab7d5dd..6d73644 100644 --- a/src/sm_api_service.c +++ b/src/sm_api_service.c @@ -701,8 +701,8 @@ static struct json_object *filesystem_to_json(const struct statfs *mount) { static void handle_storage(struct MHD_Connection *connection) { struct statfs *mounts = NULL; - int mount_count = getmntinfo(&mounts, MNT_NOWAIT); - if (mount_count < 0 || !mounts) { + int mount_count = sm_mount_table_snapshot(&mounts); + if (mount_count < 0) { int status = errno != 0 ? errno : EIO; send_error_response(connection, operation_http_status(status), status, strerror(status)); @@ -712,6 +712,7 @@ static void handle_storage(struct MHD_Connection *connection) { struct json_object *response = new_status_response(0); struct json_object *items = json_object_new_array_ext(mount_count); if (!response || !items) { + free(mounts); if (response) json_object_put(response); if (items) @@ -726,6 +727,7 @@ static void handle_storage(struct MHD_Connection *connection) { continue; struct json_object *item = filesystem_to_json(&mounts[i]); if (!item || json_object_array_add(items, item) != 0) { + free(mounts); if (item) json_object_put(item); json_object_put(items); @@ -735,9 +737,15 @@ static void handle_storage(struct MHD_Connection *connection) { } count++; } + free(mounts); - if (!add_json_int(response, "count", (int64_t)count) || - !add_json_value(response, "mounts", items)) { + if (!add_json_int(response, "count", (int64_t)count)) { + json_object_put(items); + json_object_put(response); + send_out_of_memory_response(connection); + return; + } + if (!add_json_value(response, "mounts", items)) { json_object_put(response); send_out_of_memory_response(connection); return; @@ -2555,7 +2563,6 @@ static void *storage_job_thread_main(void *arg) { if (unpack_mounted) { if (!sm_shellcore_release_title_runtime(title_id) && status == 0) status = errno == EBUSY ? EBUSY : EIO; - unpack_mounted = false; } if (status == 0 && operation == GAME_STORAGE_UNPACK && delete_source && sm_storage_delete_path(source) != 0) { diff --git a/src/sm_fakelib.c b/src/sm_fakelib.c index 1715f5e..9f36d53 100644 --- a/src/sm_fakelib.c +++ b/src/sm_fakelib.c @@ -565,17 +565,20 @@ static bool cache_root_is_mounted(const char *cache_root) { return false; struct statfs *mounts = NULL; - int mount_count = getmntinfo(&mounts, MNT_NOWAIT); - if (mount_count <= 0 || !mounts) + int mount_count = sm_mount_table_snapshot(&mounts); + if (mount_count < 0) return true; for (int i = 0; i < mount_count; ++i) { const char *source = mounts[i].f_mntfromname; if (strncmp(source, ":", 8) == 0) source += 8; - if (strcmp(source, cache_fakelib) == 0) + if (strcmp(source, cache_fakelib) == 0) { + free(mounts); return true; + } } + free(mounts); return false; } diff --git a/src/sm_filesystem.c b/src/sm_filesystem.c index 54e8497..5ac72f8 100644 --- a/src/sm_filesystem.c +++ b/src/sm_filesystem.c @@ -14,6 +14,41 @@ static bool unmount_controlled_mount_stack(const char *path); static bool unmount_controlled_mount_stack_impl(const char *path, bool diagnose_busy); +int sm_mount_table_snapshot(struct statfs **mounts_out) { + *mounts_out = NULL; + int count = getfsstat(NULL, 0, MNT_NOWAIT); + if (count < 0) + return -1; + + // A full buffer may be truncated by concurrent mounts. Grow it a bounded + // number of times before reporting an unavailable table. + size_t capacity = (size_t)count + 16u; + for (int attempt = 0; attempt < 3; ++attempt) { + if (capacity > (size_t)LONG_MAX / sizeof(struct statfs)) { + errno = EOVERFLOW; + return -1; + } + size_t bytes = capacity * sizeof(struct statfs); + struct statfs *mounts = malloc(bytes); + if (!mounts) + return -1; + count = getfsstat(mounts, (long)bytes, MNT_NOWAIT); + if (count >= 0 && (size_t)count < capacity) { + *mounts_out = mounts; + return count; + } + int saved_errno = errno; + free(mounts); + if (count < 0) { + errno = saved_errno; + return -1; + } + capacity *= 2u; + } + errno = EAGAIN; + return -1; +} + // --- FILESYSTEM --- bool is_installed(const char *title_id) { char path[MAX_PATH]; @@ -320,9 +355,6 @@ static bool source_path_needs_cleanup(const char *source_path) { snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin", source_path); if (written < 0 || (size_t)written >= sizeof(eboot_path)) return true; - if (path_exists(eboot_path)) - return false; - return !path_exists(eboot_path); } @@ -410,16 +442,17 @@ void cleanup_staged_mount_links(void) { cleanup_staged_mount_links_entry, NULL); } -static bool get_top_mount(const char *path, struct statfs *mount_st_out) { +// 1: mounted, 0: absent, -1: mount table unavailable. +static int get_top_mount(const char *path, struct statfs *mount_st_out) { if (statfs(path, mount_st_out) == 0 && strcmp(mount_st_out->f_mntonname, path) == 0) { - return true; + return 1; } struct statfs *mntbuf = NULL; - int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT); - if (mntcount <= 0 || !mntbuf) - return false; + int mntcount = sm_mount_table_snapshot(&mntbuf); + if (mntcount < 0) + return -1; const struct statfs *best_mount = NULL; for (int i = 0; i < mntcount; i++) { @@ -432,11 +465,11 @@ static bool get_top_mount(const char *path, struct statfs *mount_st_out) { } } - if (!best_mount) - return false; - - *mount_st_out = *best_mount; - return true; + bool found = best_mount != NULL; + if (found) + *mount_st_out = *best_mount; + free(mntbuf); + return found; } typedef enum { @@ -502,7 +535,9 @@ static bool inspect_title_stack(const char *title_id, const char *source_path, } struct statfs *mntbuf = NULL; - int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT); + int mntcount = sm_mount_table_snapshot(&mntbuf); + if (mntcount < 0) + return false; const struct statfs *best_mount = NULL; bool found_mount_entry = false; if (mntcount > 0 && mntbuf) { @@ -558,19 +593,15 @@ static bool inspect_title_stack(const char *title_id, const char *source_path, } else if (!found_mount_entry) { log_debug(" [LINK] no mount entries for %s", state_out->system_ex_path); } - for (int i = 0; i < mntcount; i++) { - if (strcmp(mntbuf[i].f_mntonname, state_out->system_ex_path) != 0) - continue; - log_debug(" [LINK] mount entry for %s: type=%s from=%s flags=0x%lX", - state_out->system_ex_path, mntbuf[i].f_fstypename, - mntbuf[i].f_mntfromname, (unsigned long)mntbuf[i].f_flags); - } + free(mntbuf); errno = inspect_errno; return false; } - if (!top_mount) + if (!top_mount) { + free(mntbuf); return true; + } const char *top_from = top_mount->f_mntfromname; if (strcmp(top_mount->f_fstypename, "unionfs") == 0 && @@ -594,14 +625,16 @@ static bool inspect_title_stack(const char *title_id, const char *source_path, state_out->has_our_backport = true; } + free(mntbuf); return true; } static bool unmount_top_controlled_layer_impl(const char *path, bool diagnose_busy) { struct statfs mount_st; - if (!get_top_mount(path, &mount_st)) - return true; + int mounted = get_top_mount(path, &mount_st); + if (mounted <= 0) + return mounted == 0; bool controlled_top = (strcmp(mount_st.f_fstypename, "unionfs") == 0) || @@ -802,8 +835,8 @@ static void log_backport_overlay_failure(const char *mount_point, log_backport_overlay_path("covered", mount_point, NULL, mounted_sfs); struct statfs *mounts = NULL; - int mount_count = getmntinfo(&mounts, MNT_NOWAIT); - if (mount_count <= 0 || !mounts) { + int mount_count = sm_mount_table_snapshot(&mounts); + if (mount_count < 0) { int table_errno = errno; log_debug(" [IMG][UNIONFS] mount table unavailable: error=%d (%s)", table_errno, strerror(table_errno)); @@ -823,6 +856,7 @@ static void log_backport_overlay_failure(const char *mount_point, log_debug(" [IMG][UNIONFS] no exact mount-table entry for %s", mount_point); } + free(mounts); } bool mount_backport_overlay(const char *mount_point, @@ -872,14 +906,15 @@ static bool unmount_controlled_mount_stack_impl(const char *path, bool diagnose_busy) { for (int i = 0; i < MAX_LAYERED_UNMOUNT_ATTEMPTS * 4; i++) { struct statfs mount_st; - if (!get_top_mount(path, &mount_st)) - return true; + int mounted = get_top_mount(path, &mount_st); + if (mounted <= 0) + return mounted == 0; if (!unmount_top_controlled_layer_impl(path, diagnose_busy)) return false; } struct statfs mount_st; - return !get_top_mount(path, &mount_st); + return get_top_mount(path, &mount_st) == 0; } static bool unmount_controlled_mount_stack(const char *path) { @@ -1422,6 +1457,11 @@ static bool cleanup_mount_links_entry(const char *title_id, memset(&state, 0, sizeof(state)); bool inspected = inspect_title_stack(title_id, source_path, ctx->removed_source_root, &state); + if (!inspected) { + log_debug(" [LINK] keeping mount link for %s: mount state unavailable", + title_id); + return true; + } bool top_is_ours = inspected && title_stack_top_is_managed(&state); bool stack_matches_link = false; if (inspected && source_path[0] != '\0') { diff --git a/src/sm_mount_device.c b/src/sm_mount_device.c index b52e8fd..bfd93fa 100644 --- a/src/sm_mount_device.c +++ b/src/sm_mount_device.c @@ -1,6 +1,7 @@ #include "sm_platform.h" #include "sm_runtime.h" #include "sm_mount_device.h" +#include "sm_filesystem.h" #include "sm_image_cache.h" #include "sm_limits.h" #include "sm_log.h" @@ -87,8 +88,8 @@ bool resolve_device_from_mount(const char *mount_point, } struct statfs *mntbuf = NULL; - int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT); - if (mntcount <= 0 || !mntbuf) + int mntcount = sm_mount_table_snapshot(&mntbuf); + if (mntcount < 0) return false; for (int i = 0; i < mntcount; i++) { @@ -96,14 +97,17 @@ bool resolve_device_from_mount(const char *mount_point, continue; if (parse_unit_from_dev_path(mntbuf[i].f_mntfromname, "/dev/lvd", unit_out)) { *backend_out = ATTACH_BACKEND_LVD; + free(mntbuf); return true; } if (parse_unit_from_dev_path(mntbuf[i].f_mntfromname, "/dev/md", unit_out)) { *backend_out = ATTACH_BACKEND_MD; + free(mntbuf); return true; } } + free(mntbuf); return false; } @@ -115,11 +119,16 @@ static bool is_path_mountpoint(const char *path) { // An unreadable filesystem can make statfs(path) fail while it is still in // the mount table. Treat it as active until unmount removes that entry. struct statfs *mntbuf = NULL; - int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT); + int mntcount = sm_mount_table_snapshot(&mntbuf); + if (mntcount < 0) + return true; // Do not detach a device when its mount state is unknown. for (int i = 0; i < mntcount && mntbuf; ++i) { - if (strcmp(mntbuf[i].f_mntonname, path) == 0) + if (strcmp(mntbuf[i].f_mntonname, path) == 0) { + free(mntbuf); return true; + } } + free(mntbuf); return false; } @@ -130,7 +139,11 @@ bool is_active_image_mount_point(const char *path) { bool wait_for_lvd_release(void) { for (unsigned int waited_us = 0;; waited_us += LVD_RELEASE_WAIT_POLL_US) { struct statfs *mntbuf = NULL; - int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT); + int mntcount = sm_mount_table_snapshot(&mntbuf); + if (mntcount < 0) { + log_debug(" [IMG][LVD] mount table unavailable: %s", strerror(errno)); + return false; + } bool mounted = false; for (int i = 0; i < mntcount && mntbuf; i++) { if (strcmp(mntbuf[i].f_mntfromname, "/dev/lvd2") != 0) @@ -139,6 +152,7 @@ bool wait_for_lvd_release(void) { break; } if (!mounted) { + free(mntbuf); if (waited_us != 0) log_debug(" [IMG][LVD] /dev/lvd2 released"); return true; @@ -164,6 +178,7 @@ bool wait_for_lvd_release(void) { (unsigned long)mntbuf[i].f_flags); } } + free(mntbuf); if (should_stop_requested()) return false; if (waited_us >= LVD_RELEASE_WAIT_MAX_US) { @@ -198,14 +213,21 @@ static bool read_device_node_identity( static bool device_node_is_mounted(const char *devname, char mount_point[MNAMELEN]) { struct statfs *mounts = NULL; - int mount_count = getmntinfo(&mounts, MNT_NOWAIT); + int mount_count = sm_mount_table_snapshot(&mounts); + if (mount_count < 0) { + if (mount_point) + (void)strlcpy(mount_point, "unknown", MNAMELEN); + return true; + } for (int i = 0; i < mount_count && mounts; ++i) { if (strcmp(mounts[i].f_mntfromname, devname) != 0) continue; if (mount_point) (void)strlcpy(mount_point, mounts[i].f_mntonname, MNAMELEN); + free(mounts); return true; } + free(mounts); return false; } diff --git a/src/sm_mount_diag.c b/src/sm_mount_diag.c index 9f4b8f2..8efda0f 100644 --- a/src/sm_mount_diag.c +++ b/src/sm_mount_diag.c @@ -5,6 +5,7 @@ #include #include "sm_log.h" +#include "sm_filesystem.h" #include "sm_mount_diag.h" #include "sm_path_utils.h" @@ -117,8 +118,8 @@ static void collect_mount_dependencies(const char *mount_point, (void)add_diag_root(roots, mount_point); struct statfs *mounts = NULL; - int mount_count = getmntinfo(&mounts, MNT_NOWAIT); - if (mount_count <= 0 || !mounts) { + int mount_count = sm_mount_table_snapshot(&mounts); + if (mount_count < 0) { log_debug(" [MOUNT-BUSY] mount table unavailable for %s: %s", mount_point, strerror(errno)); return; @@ -141,6 +142,7 @@ static void collect_mount_dependencies(const char *mount_point, if (consumes_mount) (void)add_diag_root(roots, target); } + free(mounts); *dependency_count_out = dependency_count; }