diff --git a/include/sm_scanner.h b/include/sm_scanner.h new file mode 100644 index 0000000..e18841c --- /dev/null +++ b/include/sm_scanner.h @@ -0,0 +1,17 @@ +#ifndef SM_SCANNER_H +#define SM_SCANNER_H + +#include + +// Initialize scanner service resources such as wake pipe and control-dir watch. +bool sm_scanner_init(void); +// Wake a blocked scanner wait. Safe to call from signal context. +void sm_scanner_wake(void); +// Run the initial full synchronization cycle. +bool sm_scanner_run_startup_sync(void); +// Run the steady-state scanner wait/scan loop until shutdown is requested. +void sm_scanner_run_loop(void); +// Release scanner service resources. +void sm_scanner_shutdown(void); + +#endif diff --git a/src/main.c b/src/main.c index 24be24a..4eae485 100644 --- a/src/main.c +++ b/src/main.c @@ -14,6 +14,7 @@ #include "sm_filesystem.h" #include "sm_image.h" #include "sm_scan.h" +#include "sm_scanner.h" #include "sm_install.h" #include "sm_appdb.h" #include "sm_limits.h" @@ -40,6 +41,7 @@ static _Atomic(uintptr_t) g_scan_now_reason_bits = 0; static void on_signal(int sig) { (void)sig; g_stop_requested = 1; + sm_scanner_wake(); } void install_signal_handlers(void) { @@ -76,6 +78,7 @@ void request_shutdown_stop(const char *reason) { log_debug("[SHUTDOWN] requested by %s", resolved_reason); } g_stop_requested = 1; + sm_scanner_wake(); } void request_scan_now(const char *reason) { @@ -88,6 +91,7 @@ void request_scan_now(const char *reason) { memory_order_release); log_debug("[SCAN] immediate scan requested by %s", resolved_reason); } + sm_scanner_wake(); } bool consume_scan_now_request(const char **reason_out) { @@ -280,9 +284,6 @@ int main(void) { bool restarted_previous_instance = false; bool kstuff_runtime_enabled = false; pid_t existing_pid = 0; - const char *scan_reason = NULL; - scan_candidate_t candidates[MAX_PENDING]; - memset(candidates, 0, sizeof(candidates)); sceUserServiceInitialize(0); sceAppInstUtilInitialize(); @@ -315,6 +316,8 @@ int main(void) { (void)unlink(LOG_FILE_PREV); (void)rename(LOG_FILE, LOG_FILE_PREV); + if (!sm_scanner_init()) + log_debug(" [SCAN] scanner service init incomplete; fallback path remains available"); char firmware_version[32]; get_firmware_version_string(firmware_version); @@ -362,53 +365,14 @@ int main(void) { cleanup_staged_mount_links(); cleanup_duplicate_title_mounts(); - cleanup_lost_sources_before_scan(); - int total_found_games = 0; - int candidate_count = - collect_scan_candidates(candidates, MAX_PENDING, &total_found_games); - if (candidate_count > 0) { - int new_games = 0; - for (int i = 0; i < candidate_count; i++) { - if (!candidates[i].installed) - new_games++; - } - if (new_games > 0) - notify_system_info("Found %d new games. Executing...", new_games); - process_scan_candidates(candidates, candidate_count); - } - mount_backport_overlays(); - notify_system_rich(true, "Library Synchronized.\nFound %d games.", - total_found_games); - - while (true) { - if (should_stop_requested()) { - log_debug("[SHUTDOWN] stop requested"); - break; - } - - if (!consume_scan_now_request(&scan_reason)) { - if (sleep_with_stop_check(runtime_config()->scan_interval_us)) { - log_debug("[SHUTDOWN] stop requested during sleep"); - break; - } - (void)consume_scan_now_request(&scan_reason); - } - - if (scan_reason) { - log_debug("[SCAN] running immediate scan (%s)", scan_reason); - scan_reason = NULL; - } - - cleanup_lost_sources_before_scan(); - candidate_count = collect_scan_candidates(candidates, MAX_PENDING, NULL); - if (candidate_count > 0) - process_scan_candidates(candidates, candidate_count); - mount_backport_overlays(); - } + if (!sm_scanner_run_startup_sync()) + goto shutdown; + sm_scanner_run_loop(); shutdown: sm_shellcore_flags_stop(); stop_game_lifecycle_watcher(); + sm_scanner_shutdown(); if (kstuff_runtime_enabled) sm_kstuff_shutdown(); shutdown_title_mounts(); diff --git a/src/sm_scanner.c b/src/sm_scanner.c new file mode 100644 index 0000000..63cdc51 --- /dev/null +++ b/src/sm_scanner.c @@ -0,0 +1,288 @@ +#include "sm_platform.h" + +#include +#include + +#include "sm_install.h" +#include "sm_limits.h" +#include "sm_log.h" +#include "sm_paths.h" +#include "sm_config_mount.h" +#include "sm_runtime.h" +#include "sm_scan.h" +#include "sm_scanner.h" +#include "sm_types.h" + +static int g_scanner_wake_pipe[2] = {-1, -1}; +static int g_scanner_control_dir_fd = -1; +static volatile sig_atomic_t g_scanner_wake_write_fd = -1; + +static bool set_fd_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return false; + + return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0; +} + +static void close_scanner_wake_pipe(void) { + g_scanner_wake_write_fd = -1; + if (g_scanner_wake_pipe[0] >= 0) { + close(g_scanner_wake_pipe[0]); + g_scanner_wake_pipe[0] = -1; + } + if (g_scanner_wake_pipe[1] >= 0) { + close(g_scanner_wake_pipe[1]); + g_scanner_wake_pipe[1] = -1; + } +} + +static void close_scanner_control_dir(void) { + if (g_scanner_control_dir_fd >= 0) { + close(g_scanner_control_dir_fd); + g_scanner_control_dir_fd = -1; + } +} + +static void drain_scanner_wake_pipe(void) { + if (g_scanner_wake_pipe[0] < 0) + return; + + char buf[64]; + while (read(g_scanner_wake_pipe[0], buf, sizeof(buf)) > 0) { + } +} + +static void log_immediate_scan_reason(const char *reason) { + if (!reason || reason[0] == '\0') + return; + log_debug("[SCAN] running immediate scan (%s)", reason); +} + +static bool run_scan_cycle(bool startup_sync, const char *reason) { + scan_candidate_t candidates[MAX_PENDING]; + memset(candidates, 0, sizeof(candidates)); + + log_immediate_scan_reason(reason); + + cleanup_lost_sources_before_scan(); + + int total_found_games = 0; + int *total_found_ptr = startup_sync ? &total_found_games : NULL; + int candidate_count = + collect_scan_candidates(candidates, MAX_PENDING, total_found_ptr); + if (candidate_count > 0) { + if (startup_sync) { + int new_games = 0; + for (int i = 0; i < candidate_count; i++) { + if (!candidates[i].installed) + new_games++; + } + if (new_games > 0) + notify_system_info("Found %d new games. Executing...", new_games); + } + + process_scan_candidates(candidates, candidate_count); + } + + mount_backport_overlays(); + + if (startup_sync) { + notify_system_rich(true, "Library Synchronized.\nFound %d games.", + total_found_games); + } + + return !should_stop_requested(); +} + +static bool wait_for_scan_event(int kq, bool *timed_out_out) { + if (timed_out_out) + *timed_out_out = false; + + struct timespec timeout; + timeout.tv_sec = (time_t)(runtime_config()->scan_interval_us / 1000000u); + timeout.tv_nsec = + (long)((runtime_config()->scan_interval_us % 1000000u) * 1000u); + + struct kevent event; + int nev = kevent(kq, NULL, 0, &event, 1, &timeout); + if (nev < 0) { + if (errno == EINTR) + return true; + + log_debug(" [SCAN] kevent wait failed: %s", strerror(errno)); + return false; + } + + if (nev == 0) { + if (timed_out_out) + *timed_out_out = true; + return true; + } + + if (event.filter == EVFILT_READ && + event.ident == (uintptr_t)g_scanner_wake_pipe[0]) { + drain_scanner_wake_pipe(); + } + + return true; +} + +static bool register_scanner_kevents(int kq) { + struct kevent kev; + + EV_SET(&kev, (uintptr_t)g_scanner_wake_pipe[0], EVFILT_READ, + EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, NULL); + if (kevent(kq, &kev, 1, NULL, 0, NULL) != 0) { + log_debug(" [SCAN] wake pipe registration failed: %s", strerror(errno)); + return false; + } + + if (g_scanner_control_dir_fd >= 0) { + EV_SET(&kev, (uintptr_t)g_scanner_control_dir_fd, EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_DELETE | NOTE_RENAME | + NOTE_REVOKE, + 0, NULL); + if (kevent(kq, &kev, 1, NULL, 0, NULL) != 0) { + log_debug(" [SCAN] control dir watcher registration failed: %s", + strerror(errno)); + return false; + } + } + + return true; +} + +static void run_scanner_polling_loop(void) { + const char *scan_reason = NULL; + + while (true) { + if (should_stop_requested()) { + log_debug("[SHUTDOWN] stop requested"); + break; + } + + if (!consume_scan_now_request(&scan_reason)) { + if (sleep_with_stop_check(runtime_config()->scan_interval_us)) { + log_debug("[SHUTDOWN] stop requested during sleep"); + break; + } + (void)consume_scan_now_request(&scan_reason); + } + + if (!run_scan_cycle(false, scan_reason)) + break; + scan_reason = NULL; + } +} + +bool sm_scanner_init(void) { + close_scanner_wake_pipe(); + close_scanner_control_dir(); + + if (pipe(g_scanner_wake_pipe) != 0) { + log_debug(" [SCAN] wake pipe creation failed: %s", strerror(errno)); + close_scanner_wake_pipe(); + return false; + } + if (!set_fd_nonblocking(g_scanner_wake_pipe[0]) || + !set_fd_nonblocking(g_scanner_wake_pipe[1])) { + log_debug(" [SCAN] wake pipe nonblocking setup failed: %s", + strerror(errno)); + close_scanner_wake_pipe(); + return false; + } + g_scanner_wake_write_fd = (sig_atomic_t)g_scanner_wake_pipe[1]; + + g_scanner_control_dir_fd = open(LOG_DIR, O_RDONLY | O_DIRECTORY); + if (g_scanner_control_dir_fd < 0) { + log_debug(" [SCAN] control dir watch unavailable for %s: %s", LOG_DIR, + strerror(errno)); + } + + return true; +} + +void sm_scanner_wake(void) { + sig_atomic_t wake_fd = g_scanner_wake_write_fd; + if (wake_fd < 0) + return; + + static const char token = 'S'; + (void)write((int)wake_fd, &token, sizeof(token)); +} + +bool sm_scanner_run_startup_sync(void) { + if (should_stop_requested()) + return false; + + return run_scan_cycle(true, NULL); +} + +void sm_scanner_run_loop(void) { + if (g_scanner_wake_pipe[0] < 0 || g_scanner_wake_pipe[1] < 0) { + log_debug(" [SCAN] scanner wake pipe unavailable, using polling loop"); + run_scanner_polling_loop(); + return; + } + + int kq = kqueue(); + if (kq < 0) { + log_debug(" [SCAN] kqueue init failed, using polling loop: %s", + strerror(errno)); + run_scanner_polling_loop(); + return; + } + + if (!register_scanner_kevents(kq)) { + close(kq); + run_scanner_polling_loop(); + return; + } + + while (true) { + if (should_stop_requested()) { + log_debug("[SHUTDOWN] stop requested"); + break; + } + + const char *scan_reason = NULL; + if (consume_scan_now_request(&scan_reason)) { + if (!run_scan_cycle(false, scan_reason)) + break; + continue; + } + + bool timed_out = false; + if (!wait_for_scan_event(kq, &timed_out)) { + log_debug(" [SCAN] falling back to polling loop after kevent failure"); + close(kq); + run_scanner_polling_loop(); + return; + } + if (should_stop_requested()) { + log_debug("[SHUTDOWN] stop requested during scanner wait"); + break; + } + + if (consume_scan_now_request(&scan_reason)) { + if (!run_scan_cycle(false, scan_reason)) + break; + continue; + } + + if (!timed_out) + continue; + + if (!run_scan_cycle(false, NULL)) + break; + } + + close(kq); +} + +void sm_scanner_shutdown(void) { + close_scanner_control_dir(); + close_scanner_wake_pipe(); +}