diff --git a/include/sm_filesystem.h b/include/sm_filesystem.h index 8017eba..7db68e2 100644 --- a/include/sm_filesystem.h +++ b/include/sm_filesystem.h @@ -35,11 +35,13 @@ bool mount_backport_overlay(const char *mount_point, const char *title_id); // Unmount all managed /system_ex/app/ mount stacks on shutdown. void shutdown_title_mounts(void); -// Return true when path is equal to root or is under it. -bool path_matches_root_or_child(const char *path, const char *root); // Remove stale mount links and optionally restore image-backed mounts. void cleanup_mount_links(const char *removed_source_root, bool unmount_system_ex_bind); +// Unmount and remove title links that point under a source root. +void cleanup_mount_links_for_source_unmount(const char *source_root); +// Unmount and remove title links backed by USB sources or USB-backed images. +void cleanup_usb_mount_links_for_suspend(void); // Recursively copy a directory tree. int copy_dir(const char *src, const char *dst); // Copy a single file. diff --git a/include/sm_image.h b/include/sm_image.h index a39c691..17c4549 100644 --- a/include/sm_image.h +++ b/include/sm_image.h @@ -15,6 +15,8 @@ bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend) void cleanup_stale_image_mounts(void); // Reconcile cached image mounts that belong to a specific scan root. void cleanup_stale_image_mounts_for_root(const char *root); +// Unmount cached image mounts backed by USB storage during suspend. +bool unmount_usb_image_mounts_for_suspend(void); // Unmount every cached image mount during shutdown. bool shutdown_image_mounts(void); // Remove empty directories left under the image mount root. diff --git a/include/sm_path_utils.h b/include/sm_path_utils.h index 0df1b6d..6a5948a 100644 --- a/include/sm_path_utils.h +++ b/include/sm_path_utils.h @@ -12,6 +12,10 @@ const char *get_filename_component(const char *path); bool path_exists(const char *path); // Return true when a path lives under the image mount root. bool is_under_image_mount_base(const char *path); +// Return true when path is equal to root or is under it. +bool path_matches_root_or_child(const char *path, const char *root); +// Return true when a path lives on USB-backed storage. +bool is_usb_storage_path(const char *path); // Build "/backports" for a managed scan root. bool build_backports_root_path(const char *scan_path, char out[MAX_PATH]); diff --git a/include/sm_runtime.h b/include/sm_runtime.h index 9f23f05..597374c 100644 --- a/include/sm_runtime.h +++ b/include/sm_runtime.h @@ -13,6 +13,14 @@ pid_t find_pid_by_name(const char *name, bool exclude_self); bool should_stop_requested(void); // Request graceful shutdown with a descriptive source string. void request_shutdown_stop(const char *reason); +// Return true when the payload is paused for system suspend/resume. +bool runtime_sleep_mode_active(void); +// Enter or leave sleep mode with a descriptive source string. +void request_runtime_sleep_mode(bool active, const char *reason); +// Return true when scan/remount work is paused by power-state handling. +bool runtime_scan_blocked(void); +// Enter or leave scan/remount pause with a descriptive source string. +void request_runtime_scan_block(bool active, const char *reason); // Request an immediate scan cycle with a descriptive source string. void request_scan_now(const char *reason); // Consume a pending immediate scan request and copy its reason into caller storage. diff --git a/include/sm_scan.h b/include/sm_scan.h index ebf479f..bb0fa56 100644 --- a/include/sm_scan.h +++ b/include/sm_scan.h @@ -9,6 +9,8 @@ typedef struct scan_candidate scan_candidate_t; void cleanup_lost_sources_before_scan(void); // Unmount and clean up mounts whose backing sources disappeared under one root. void cleanup_lost_sources_for_scan_root(const char *scan_root); +// Immediately unmount runtime mounts backed by USB storage for suspend. +void unmount_usb_sources_for_suspend(void); // Scan configured roots and collect install candidates. int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates, int *total_found_out, diff --git a/src/main.c b/src/main.c index 23d6e46..122d37e 100644 --- a/src/main.c +++ b/src/main.c @@ -41,6 +41,8 @@ static volatile sig_atomic_t g_stop_requested = 0; static atomic_bool g_shutdown_on_going_stop_requested = false; +static atomic_bool g_runtime_sleep_mode_active = false; +static atomic_bool g_runtime_scan_blocked = false; static _Atomic(uintptr_t) g_shutdown_stop_reason_bits = 0; typedef struct { @@ -101,19 +103,75 @@ void request_shutdown_stop(const char *reason) { } g_stop_requested = 1; sm_scanner_wake(); + wake_game_lifecycle_watcher(); +} + +bool runtime_sleep_mode_active(void) { + return atomic_load_explicit(&g_runtime_sleep_mode_active, + memory_order_acquire); +} + +bool runtime_scan_blocked(void) { + return runtime_sleep_mode_active() || + atomic_load_explicit(&g_runtime_scan_blocked, memory_order_acquire); +} + +static void clear_scan_now_request(void) { + pthread_mutex_lock(&g_scan_now.reason_mutex); + g_scan_now.reason[0] = '\0'; + pthread_mutex_unlock(&g_scan_now.reason_mutex); +} + +void request_runtime_sleep_mode(bool active, const char *reason) { + bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, active, + memory_order_acq_rel); + if (previous == active) + return; + + if (active) + clear_scan_now_request(); + + const char *resolved_reason = + (reason && reason[0] != '\0') ? reason : "unknown sleep source"; + log_debug("[SLEEP] %s by %s", active ? "entered" : "left", + resolved_reason); + sm_scanner_wake(); + wake_game_lifecycle_watcher(); +} + +void request_runtime_scan_block(bool active, const char *reason) { + bool previous = atomic_exchange_explicit(&g_runtime_scan_blocked, active, + memory_order_acq_rel); + if (previous == active) + return; + + if (active) + clear_scan_now_request(); + + const char *resolved_reason = + (reason && reason[0] != '\0') ? reason : "unknown scan block source"; + log_debug("[SCAN] %s by %s", active ? "paused" : "resumed", + resolved_reason); + sm_scanner_wake(); } void request_scan_now(const char *reason) { const char *resolved_reason = (reason && reason[0] != '\0') ? reason : "unknown scan source"; + bool resume_scan = + strcmp(resolved_reason, "SceSystemStateMgrInfo=WORKING") == 0; + if (runtime_scan_blocked() && !resume_scan) + return; + char log_reason[sizeof(g_scan_now.reason)]; - bool should_log = false; + bool should_log = !resume_scan; pthread_mutex_lock(&g_scan_now.reason_mutex); if (g_scan_now.reason[0] == '\0') { (void)strlcpy(g_scan_now.reason, resolved_reason, sizeof(g_scan_now.reason)); (void)strlcpy(log_reason, g_scan_now.reason, sizeof(log_reason)); - should_log = true; + } else { + should_log = false; } pthread_mutex_unlock(&g_scan_now.reason_mutex); diff --git a/src/sm_filesystem.c b/src/sm_filesystem.c index 3b84a01..42f0ec2 100644 --- a/src/sm_filesystem.c +++ b/src/sm_filesystem.c @@ -830,12 +830,21 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) { IOVEC_ENTRY("fspath"), IOVEC_ENTRY(dst), }; + if (runtime_scan_blocked()) + return false; + if (nmount(iov, IOVEC_SIZE(iov), 0) != 0) { log_debug(" [LINK] Failed to auto-mount nullfs title=%s src=%s dst=%s: %s", title_id, src_path, dst, strerror(errno)); return false; } + if (runtime_scan_blocked()) { + if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL) + (void)unmount(dst, MNT_FORCE); + return false; + } + if (!path_exists(dst_eboot)) { log_debug(" [LINK] mounted nullfs but eboot.bin is missing at target: %s", dst_eboot); @@ -851,19 +860,12 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) { return true; } -bool path_matches_root_or_child(const char *path, const char *root) { - if (!path || !root || root[0] == '\0') - return false; - size_t root_len = strlen(root); - if (strncmp(path, root, root_len) != 0) - return false; - return (path[root_len] == '\0' || path[root_len] == '/'); -} - typedef struct { const char *removed_source_root; bool unmount_system_ex_bind; bool tried_image_recovery; + bool force_remove_matching_source; + bool match_usb_sources; } cleanup_mount_links_ctx_t; static bool cleanup_mount_links_entry(const char *title_id, @@ -877,6 +879,8 @@ static bool cleanup_mount_links_entry(const char *title_id, char source_path[MAX_PATH]; char image_source_path[MAX_PATH]; + source_path[0] = '\0'; + image_source_path[0] = '\0'; bool has_image_source = false; bool should_remove = false; bool matches_removed_source = false; @@ -888,7 +892,14 @@ static bool cleanup_mount_links_entry(const char *title_id, } if (!should_remove) { - if (ctx->removed_source_root && ctx->removed_source_root[0] != '\0') { + if (ctx->match_usb_sources) { + matches_removed_source = + is_usb_storage_path(source_path) || + (has_image_source && is_usb_storage_path(image_source_path)); + if (!matches_removed_source) + return true; + should_remove = true; + } else if (ctx->removed_source_root && ctx->removed_source_root[0] != '\0') { matches_removed_source = path_matches_root_or_child(source_path, ctx->removed_source_root) || (has_image_source && @@ -896,7 +907,8 @@ static bool cleanup_mount_links_entry(const char *title_id, ctx->removed_source_root)); if (!matches_removed_source) return true; - should_remove = (has_image_source && !path_exists(image_source_path)) || + should_remove = ctx->force_remove_matching_source || + (has_image_source && !path_exists(image_source_path)) || source_path_needs_cleanup(source_path, &ctx->tried_image_recovery); } else { @@ -976,11 +988,40 @@ void cleanup_mount_links(const char *removed_source_root, .removed_source_root = removed_source_root, .unmount_system_ex_bind = unmount_system_ex_bind, .tried_image_recovery = false, + .force_remove_matching_source = false, + .match_usb_sources = false, }; for_each_title_app_dir("", !unmount_system_ex_bind, cleanup_mount_links_entry, &ctx); } +void cleanup_mount_links_for_source_unmount(const char *source_root) { + if (!source_root || source_root[0] == '\0') + return; + + cleanup_mount_links_ctx_t ctx = { + .removed_source_root = source_root, + .unmount_system_ex_bind = true, + .tried_image_recovery = false, + .force_remove_matching_source = true, + .match_usb_sources = false, + }; + for_each_title_app_dir(" during source unmount cleanup", false, + cleanup_mount_links_entry, &ctx); +} + +void cleanup_usb_mount_links_for_suspend(void) { + cleanup_mount_links_ctx_t ctx = { + .removed_source_root = NULL, + .unmount_system_ex_bind = true, + .tried_image_recovery = false, + .force_remove_matching_source = false, + .match_usb_sources = true, + }; + for_each_title_app_dir(" during USB suspend cleanup", false, + cleanup_mount_links_entry, &ctx); +} + static bool shutdown_title_mounts_entry(const char *title_id, const title_link_paths_t *paths, void *ctx) { diff --git a/src/sm_game_lifecycle.c b/src/sm_game_lifecycle.c index e699e38..437c159 100644 --- a/src/sm_game_lifecycle.c +++ b/src/sm_game_lifecycle.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "sm_fakelib.h" #include "sm_game_lifecycle.h" @@ -169,6 +170,31 @@ static void close_game_lifecycle_wake_pipe(void) { } } +static void drain_game_lifecycle_wake_pipe(void) { + char drain_buf[32]; + while (read(g_game_lifecycle_wake_pipe[0], drain_buf, sizeof(drain_buf)) > 0) { + } +} + +static bool discard_game_lifecycle_events_nowait(int kq) { + struct kevent events[16]; + struct timespec timeout; + memset(&timeout, 0, sizeof(timeout)); + + while (true) { + int nev = kevent(kq, NULL, 0, events, sizeof(events) / sizeof(events[0]), + &timeout); + if (nev < 0) { + if (errno == EINTR) + continue; + log_debug(" [GAME] stale event drain failed: %s", strerror(errno)); + return false; + } + if (nev == 0) + return true; + } +} + static bool set_fd_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) @@ -375,6 +401,23 @@ static void handle_game_exit(pid_t pid) { sm_kstuff_game_on_exit(pid); } +static void restore_suspended_game_if_alive(int kq, pid_t pid) { + if (pid <= 0 || !is_process_alive(pid)) + return; + + char title_id[MAX_TITLE_ID]; + uint32_t app_id = 0; + if (!resolve_game_title_id(pid, title_id, &app_id)) + return; + if (!is_supported_title_id(title_id)) + return; + + uint64_t now_us = monotonic_time_us(); + log_debug(" [GAME] resumed: %s pid=%ld app_id=0x%08X", title_id, + (long)pid, app_id); + (void)dispatch_game_launch(kq, pid, now_us, title_id, app_id); +} + static void *game_lifecycle_watcher_main(void *arg) { (void)arg; @@ -415,7 +458,41 @@ static void *game_lifecycle_watcher_main(void *arg) { set_game_lifecycle_start_result(true); log_debug(" [GAME] lifecycle watcher started"); + bool sleep_cleanup_done = false; + pid_t suspended_game_pid = 0; while (!g_game_lifecycle_stop_requested && !should_stop_requested()) { + if (runtime_sleep_mode_active()) { + if (!sleep_cleanup_done) { + suspended_game_pid = atomic_load(&g_active_game_pid); + clear_all_pending_game_launches(); + sm_fakelib_game_shutdown(); + sm_kstuff_game_shutdown(); + publish_active_game_pid(0); + sleep_cleanup_done = true; + } + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(g_game_lifecycle_wake_pipe[0], &readfds); + int rc = select(g_game_lifecycle_wake_pipe[0] + 1, &readfds, NULL, NULL, + NULL); + if (rc < 0) { + if (errno == EINTR) + continue; + log_debug(" [GAME] sleep wait failed: %s", strerror(errno)); + break; + } + drain_game_lifecycle_wake_pipe(); + continue; + } + if (sleep_cleanup_done) { + if (!discard_game_lifecycle_events_nowait(kq)) + break; + sleep_cleanup_done = false; + restore_suspended_game_if_alive(kq, suspended_game_pid); + suspended_game_pid = 0; + } + struct kevent event; struct timespec timeout; const struct timespec *timeout_ptr = compute_game_wait_timeout(&timeout); @@ -431,9 +508,7 @@ static void *game_lifecycle_watcher_main(void *arg) { if (nev > 0) { if (event.filter == EVFILT_READ && event.ident == (uintptr_t)g_game_lifecycle_wake_pipe[0]) { - char drain_buf[32]; - (void)read(g_game_lifecycle_wake_pipe[0], drain_buf, - sizeof(drain_buf)); + drain_game_lifecycle_wake_pipe(); } else if ((event.fflags & NOTE_TRACKERR) != 0) { log_debug(" [GAME] NOTE_TRACKERR for pid=%ld", (long)event.ident); } else { @@ -444,6 +519,8 @@ static void *game_lifecycle_watcher_main(void *arg) { } } + if (runtime_sleep_mode_active()) + continue; poll_game_modules(kq); } diff --git a/src/sm_image.c b/src/sm_image.c index b73b462..efdf9f9 100644 --- a/src/sm_image.c +++ b/src/sm_image.c @@ -756,6 +756,8 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) { return false; if (!stat_image_file(file_path, &st)) return false; + if (runtime_scan_blocked()) + return false; log_debug(" [IMG] Mounting image (%s): %s -> %s", image_fs_name(fs_type), file_path, mount_point); @@ -777,10 +779,18 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) { attach_backend, &unit_id, devname, sizeof(devname))) { return false; } + if (runtime_scan_blocked()) { + (void)detach_attached_unit(attach_backend, unit_id); + return false; + } if (!perform_image_nmount(file_path, fs_type, attach_backend, unit_id, devname, mount_point, mount_read_only, force_mount)) { return false; } + if (runtime_scan_blocked()) { + (void)unmount_image(file_path, unit_id, attach_backend); + return false; + } if (!validate_mounted_image(file_path, fs_type, attach_backend, unit_id, devname, mount_point)) { @@ -825,7 +835,7 @@ bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend) // Remove mount.lnk and unmount /system_ex/app/ that point to this // source before unmounting the virtual disk itself. - cleanup_mount_links(mount_point, true); + cleanup_mount_links_for_source_unmount(mount_point); clear_cached_game(mount_point); // Unmount stacked layers (unionfs over image fs). @@ -979,6 +989,32 @@ void cleanup_stale_image_mounts_for_root(const char *root) { } } +bool unmount_usb_image_mounts_for_suspend(void) { + bool all_unmounted = true; + + for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) { + image_cache_entry_t cached_entry; + if (!get_image_cache_entry(k, &cached_entry)) + continue; + if (!is_usb_storage_path(cached_entry.path)) + continue; + + log_debug(" [IMG][%s] USB suspend unmount: %s", + attach_backend_name(cached_entry.backend), cached_entry.path); + if (unmount_image(cached_entry.path, cached_entry.unit_id, + cached_entry.backend)) { + invalidate_image_cache_entry(k); + continue; + } + + all_unmounted = false; + log_debug(" [IMG][%s] USB suspend unmount pending: %s", + attach_backend_name(cached_entry.backend), cached_entry.path); + } + + return all_unmounted; +} + void cleanup_mount_dirs(void) { DIR *d = opendir(IMAGE_MOUNT_BASE); if (!d) { diff --git a/src/sm_install.c b/src/sm_install.c index 39a2542..05c030a 100644 --- a/src/sm_install.c +++ b/src/sm_install.c @@ -170,7 +170,7 @@ static bool mount_and_install(const char *src_path, const char *title_id, restage_staging = (!is_remount || should_register); restage_appmeta = (!is_remount || appmeta_missing); - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return false; // COPY FILES @@ -215,7 +215,7 @@ static bool mount_and_install(const char *src_path, const char *title_id, metadata_restaged = true; } - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return false; if (!mount_title_nullfs(title_id, src_path)) { @@ -320,10 +320,13 @@ static bool mount_and_install(const char *src_path, const char *title_id, // --- Execution (per discovered candidate) --- void process_scan_candidates(const scan_candidate_t *candidates, int candidate_count) { + if (runtime_scan_blocked()) + return; + sm_install_poll_pending(); for (int i = 0; i < candidate_count; i++) { - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return; const scan_candidate_t *c = &candidates[i]; @@ -347,7 +350,7 @@ void process_scan_candidates(const scan_candidate_t *candidates, if (!sm_install_queue_candidate(c, has_src_snd0)) { log_debug(" [REG] Failed to queue staged install: %s (%s)", c->title_name, c->title_id); - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return; uint8_t failed_attempts = bump_failed_mount_attempts(c->title_id); @@ -365,7 +368,7 @@ void process_scan_candidates(const scan_candidate_t *candidates, cache_game_entry(c->path, c->title_id, c->title_name); } } else { - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return; uint8_t failed_attempts = bump_failed_mount_attempts(c->title_id); diff --git a/src/sm_install_queue.c b/src/sm_install_queue.c index a9c2c1d..7240cd4 100644 --- a/src/sm_install_queue.c +++ b/src/sm_install_queue.c @@ -7,6 +7,7 @@ #include "sm_game_cache.h" #include "sm_limits.h" #include "sm_log.h" +#include "sm_runtime.h" #include "sm_time.h" #include "sm_title_state.h" @@ -265,6 +266,9 @@ static void poll_pending_installs(void) { } uint64_t sm_install_next_wake_us(uint64_t now_us) { + if (runtime_scan_blocked()) + return 0; + if (!install_queue_active()) return 0; @@ -290,10 +294,16 @@ uint64_t sm_install_next_wake_us(uint64_t now_us) { } void sm_install_poll_pending(void) { + if (runtime_scan_blocked()) + return; + poll_pending_installs(); } void sm_install_service_pending(void) { + if (runtime_scan_blocked()) + return; + if (!install_queue_active()) return; @@ -379,6 +389,9 @@ static void drop_queued_install_entry(pending_install_entry_t *entry) { } bool sm_install_submit_queued(void) { + if (runtime_scan_blocked()) + return true; + int queued_count = count_queued_installs(); if (queued_count <= 0) return true; diff --git a/src/sm_path_utils.c b/src/sm_path_utils.c index 0f38069..3f22d5e 100644 --- a/src/sm_path_utils.c +++ b/src/sm_path_utils.c @@ -20,6 +20,32 @@ bool is_under_image_mount_base(const char *path) { path[image_prefix_len] == '/'); } +bool path_matches_root_or_child(const char *path, const char *root) { + if (!path || !root || root[0] == '\0') + return false; + size_t root_len = strlen(root); + if (strncmp(path, root, root_len) != 0) + return false; + return path[root_len] == '\0' || path[root_len] == '/'; +} + +bool is_usb_storage_path(const char *path) { + static const char *usb_roots[] = { + "/mnt/usb0", "/mnt/usb1", "/mnt/usb2", "/mnt/usb3", + "/mnt/usb4", "/mnt/usb5", "/mnt/usb6", "/mnt/usb7", + "/mnt/ext0", + }; + + if (!path || path[0] == '\0') + return false; + + for (size_t i = 0; i < sizeof(usb_roots) / sizeof(usb_roots[0]); i++) { + if (path_matches_root_or_child(path, usb_roots[i])) + return true; + } + return false; +} + bool build_backports_root_path(const char *scan_path, char out[MAX_PATH]) { if (is_under_image_mount_base(scan_path)) return false; diff --git a/src/sm_scan.c b/src/sm_scan.c index 527d5e8..137a034 100644 --- a/src/sm_scan.c +++ b/src/sm_scan.c @@ -489,12 +489,22 @@ void cleanup_lost_sources_for_scan_root(const char *scan_root) { prune_path_state_for_root(scan_root); } +void unmount_usb_sources_for_suspend(void) { + log_debug("[SLEEP] USB-backed mount cleanup begin"); + cleanup_usb_mount_links_for_suspend(); + if (!unmount_usb_image_mounts_for_suspend()) { + log_debug("[SLEEP] some USB-backed image mounts remain busy"); + } + cleanup_mount_dirs(); + log_debug("[SLEEP] USB-backed mount cleanup done"); +} + static void collect_scan_candidates_from_root( const char *scan_path, scan_candidate_t *candidates, int max_candidates, int *candidate_count, const struct AppDbTitleList *app_db_titles, bool app_db_titles_ready, char discovered_param_roots[][MAX_PATH], int *discovered_param_root_count, bool *unstable_found_out) { - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return; unsigned int scan_depth = runtime_config()->scan_depth; @@ -557,7 +567,7 @@ int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates, log_debug(" [DB] app.db title list unavailable for this scan cycle"); for (int i = 0; i < get_scan_path_count(); i++) { - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) break; collect_scan_candidates_from_root(get_scan_path(i), candidates, max_candidates, diff --git a/src/sm_scan_tree.c b/src/sm_scan_tree.c index 162f266..5f9e6f4 100644 --- a/src/sm_scan_tree.c +++ b/src/sm_scan_tree.c @@ -47,7 +47,7 @@ bool sm_scan_tree_walk(const char *scan_root, const char *dir_path, unsigned int depth_from_root, unsigned int remaining_depth, const sm_scan_tree_callbacks_t *callbacks, void *ctx) { - if (should_stop_requested()) + if (should_stop_requested() || runtime_scan_blocked()) return true; if (depth_from_root > 0u && @@ -79,7 +79,7 @@ bool sm_scan_tree_walk(const char *scan_root, const char *dir_path, struct dirent *entry; while ((entry = readdir(d)) != NULL) { - if (should_stop_requested()) { + if (should_stop_requested() || runtime_scan_blocked()) { closedir(d); return true; } diff --git a/src/sm_scanner.c b/src/sm_scanner.c index 7f9b050..dfe03aa 100644 --- a/src/sm_scanner.c +++ b/src/sm_scanner.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "sm_config_mount.h" #include "sm_fakelib.h" @@ -886,7 +887,7 @@ static void apply_runtime_config_reload_effects(const runtime_config_t *old_cfg, } static bool should_abort_scan_cycle(void) { - return should_stop_requested(); + return should_stop_requested() || runtime_scan_blocked(); } static bool run_full_scan_cycle(bool startup_sync, const char *reason, @@ -937,7 +938,7 @@ static bool run_full_scan_cycle(bool startup_sync, const char *reason, total_found_games); } - return !should_stop_requested(); + return !should_abort_scan_cycle(); } static bool run_targeted_scan_cycle(int scan_root_index, @@ -971,7 +972,7 @@ static bool run_targeted_scan_cycle(int scan_root_index, if (unstable_found_out) *unstable_found_out = unstable_found; - return !should_stop_requested(); + return !should_abort_scan_cycle(); } static int find_pending_cleanup_scan_root(void) { @@ -1177,6 +1178,24 @@ static bool drain_scanner_events_nowait(int kq) { return true; } +static bool discard_scanner_events_nowait(int kq) { + struct kevent events[SCANNER_EVENT_BATCH]; + struct timespec timeout; + memset(&timeout, 0, sizeof(timeout)); + + while (true) { + int nev = kevent(kq, NULL, 0, events, SCANNER_EVENT_BATCH, &timeout); + if (nev < 0) { + if (errno == EINTR) + continue; + log_debug(" [SCAN] stale event drain failed: %s", strerror(errno)); + return false; + } + if (nev == 0) + return true; + } +} + static char g_scanner_shutdown_reason[128]; static void request_scanner_shutdown(const char *reason) { @@ -1237,10 +1256,19 @@ void sm_scanner_wake(void) { } bool sm_scanner_run_startup_sync(void) { - if (should_stop_requested()) - return false; + while (!should_stop_requested()) { + while (runtime_scan_blocked() && !should_stop_requested()) + sceKernelUsleep(200000); - return run_full_scan_cycle(true, NULL, NULL); + if (should_stop_requested()) + return false; + if (run_full_scan_cycle(true, NULL, NULL)) + return true; + if (!runtime_scan_blocked()) + return false; + } + + return false; } void sm_scanner_run_loop(void) { @@ -1281,6 +1309,7 @@ void sm_scanner_run_loop(void) { uint64_t next_full_resync_us = monotonic_time_us() + scanner_full_resync_interval_us(); + bool was_sleeping = false; while (true) { if (should_stop_requested()) { @@ -1288,11 +1317,42 @@ void sm_scanner_run_loop(void) { break; } + if (runtime_scan_blocked()) { + was_sleeping = true; + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(g_scanner_wake_pipe[0], &readfds); + int rc = select(g_scanner_wake_pipe[0] + 1, &readfds, NULL, NULL, NULL); + if (rc < 0) { + if (errno == EINTR) + continue; + log_debug(" [SCAN] sleep wait failed: %s", strerror(errno)); + close(kq); + clear_scanner_watch_entries(); + request_scanner_shutdown("scanner sleep wait failed"); + return; + } + drain_scanner_wake_pipe(); + continue; + } + if (was_sleeping) { + was_sleeping = false; + if (!discard_scanner_events_nowait(kq)) { + close(kq); + clear_scanner_watch_entries(); + request_scanner_shutdown("scanner stale event drain failed"); + return; + } + } + char scan_reason[128]; if (consume_scan_now_request(scan_reason, sizeof(scan_reason))) { bool unstable_found = false; - if (!run_full_scan_cycle(false, scan_reason, &unstable_found)) + if (!run_full_scan_cycle(false, scan_reason, &unstable_found)) { + if (runtime_scan_blocked()) + continue; break; + } clear_all_dirty_scan_roots(); if (!rebuild_all_scan_root_watch_trees(kq) || !drain_scanner_events_nowait(kq)) { @@ -1354,8 +1414,11 @@ void sm_scanner_run_loop(void) { if (next_full_resync_us != 0 && now_us >= next_full_resync_us) { bool unstable_found = false; - if (!run_full_scan_cycle(false, NULL, &unstable_found)) + if (!run_full_scan_cycle(false, NULL, &unstable_found)) { + if (runtime_scan_blocked()) + continue; break; + } clear_all_dirty_scan_roots(); if (!rebuild_all_scan_root_watch_trees(kq) || !drain_scanner_events_nowait(kq)) { @@ -1384,6 +1447,8 @@ void sm_scanner_run_loop(void) { int dirty_root_index = find_due_dirty_scan_root(now_us); if (dirty_root_index >= 0) { + bool cleanup_pending = + g_scanner_root_states[dirty_root_index].cleanup_pending; bool rebuild_watch_tree = g_scanner_root_states[dirty_root_index].watch_tree_stale; uint8_t rebuild_watch_tree_depth = @@ -1401,8 +1466,25 @@ void sm_scanner_run_loop(void) { clear_scan_root_watch_tree_state(dirty_root_index); bool unstable_found = false; - if (!run_targeted_scan_cycle(dirty_root_index, &unstable_found)) + if (!run_targeted_scan_cycle(dirty_root_index, &unstable_found)) { + if (runtime_scan_blocked()) { + scanner_root_state_t *state = + &g_scanner_root_states[dirty_root_index]; + if (cleanup_pending) + schedule_scan_root_cleanup(dirty_root_index); + schedule_scan_root_dirty(dirty_root_index, monotonic_time_us(), true); + if (rebuild_watch_tree) { + state->watch_tree_stale = true; + state->watch_tree_rebuild_depth = rebuild_watch_tree_depth; + state->watch_tree_rebuild_kind = rebuild_watch_tree_kind; + (void)strlcpy(state->watch_tree_rebuild_path, + rebuild_watch_tree_path, + sizeof(state->watch_tree_rebuild_path)); + } + continue; + } break; + } if (rebuild_watch_tree && !rebuild_scan_root_watch_subtree(kq, dirty_root_index, diff --git a/src/sm_shellcore_flags.c b/src/sm_shellcore_flags.c index 7d5f724..144fa78 100644 --- a/src/sm_shellcore_flags.c +++ b/src/sm_shellcore_flags.c @@ -7,6 +7,7 @@ #include "sm_limits.h" #include "sm_log.h" #include "sm_runtime.h" +#include "sm_scan.h" #include "sm_shellcore_flags.h" #define SHELLCORE_FLAG_WAITMODE_OR 2u @@ -610,6 +611,8 @@ static void poll_shellcore_flag(shellcore_flag_monitor_t *flag) { int rc; bool changed = false; bool entered_shutdown_on_going = false; + bool entered_main_on_standby = false; + bool entered_suspend_on_going = false; bool entered_resume_working = false; bool is_system_state_mgr_info = false; unsigned current_state = 0; @@ -646,6 +649,14 @@ static void poll_shellcore_flag(shellcore_flag_monitor_t *flag) { current_state == SYSTEM_STATE_MGR_STATE_SHUTDOWN_ON_GOING && (!flag->has_last_pattern || previous_state != SYSTEM_STATE_MGR_STATE_SHUTDOWN_ON_GOING); + entered_main_on_standby = + current_state == SYSTEM_STATE_MGR_STATE_MAIN_ON_STANDBY && + (!flag->has_last_pattern || + previous_state != SYSTEM_STATE_MGR_STATE_MAIN_ON_STANDBY); + entered_suspend_on_going = + current_state == SYSTEM_STATE_MGR_STATE_SUSPEND_ON_GOING && + (!flag->has_last_pattern || + previous_state != SYSTEM_STATE_MGR_STATE_SUSPEND_ON_GOING); entered_resume_working = flag->has_last_pattern && current_state == SYSTEM_STATE_MGR_STATE_WORKING && @@ -664,8 +675,17 @@ static void poll_shellcore_flag(shellcore_flag_monitor_t *flag) { if (entered_shutdown_on_going) { request_shutdown_stop("SceSystemStateMgrInfo=SHUTDOWN_ON_GOING"); } + if (entered_main_on_standby) { + request_runtime_scan_block(true, "SceSystemStateMgrInfo=MAIN_ON_STANDBY"); + unmount_usb_sources_for_suspend(); + } + if (entered_suspend_on_going) { + request_runtime_sleep_mode(true, "SceSystemStateMgrInfo=SUSPEND_ON_GOING"); + } if (entered_resume_working) { request_scan_now("SceSystemStateMgrInfo=WORKING"); + request_runtime_sleep_mode(false, "SceSystemStateMgrInfo=WORKING"); + request_runtime_scan_block(false, "SceSystemStateMgrInfo=WORKING"); } }