diff --git a/.gitignore b/.gitignore index 8bdba75..2694037 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ docs/* .idea/ .vscode/ legacy +/src/icon_asset.c +/src/config_ini_example_asset.c diff --git a/Makefile b/Makefile index 784b116..137b552 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,16 @@ CFLAGS += -DMICROMOUNT_VERSION=\"$(VERSION_TAG)\" LDFLAGS := -flto=thin -Wl,--gc-sections LIBS := -lSceNotification -lSceUserService +ASSET_SRCS := src/icon_asset.c \ + src/config_ini_example_asset.c SRCS := src/main.c \ src/mm_config.c \ src/mm_log.c \ src/mm_mount.c \ src/mm_scan.c \ src/mm_sha256.c \ - src/mm_util.c + src/mm_util.c \ + $(ASSET_SRCS) OBJS := $(SRCS:.c=.o) HEADERS := $(wildcard include/*.h) TARGET := micromount.elf @@ -28,5 +31,11 @@ $(TARGET): $(OBJS) src/%.o: src/%.c $(HEADERS) $(CC) $(CFLAGS) -c -o $@ $< +src/icon_asset.c: assets/icon.png + xxd -i $< > $@ + +src/config_ini_example_asset.c: config.ini.example + xxd -i $< > $@ + clean: - rm -f $(TARGET) src/*.o + rm -f $(TARGET) src/*.o src/icon_asset.c src/config_ini_example_asset.c diff --git a/README.md b/README.md index 6a8930a..866d782 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ MicroMount runtime behavior: - Cleans stale managed mounts before each reconcile pass. - Skips images that are already correctly mounted. - Writes logs to `/data/micromount/debug.log` on every run. -- Reads config from `/data/micromount/config.ini` (defaults are used when missing). +- Reads config from `/data/micromount/config.ini` (auto-created from template when missing). ## 💖 Sponsorship @@ -58,7 +58,7 @@ mkpfs pack file --compress --verify ./GAME1234.exfat ./GAME1234.ffpfsc ## ⚙️ How it works -1. MicroMount starts, loads defaults, then loads `/data/micromount/config.ini` if present. +1. MicroMount starts, loads defaults, creates `/data/micromount/config.ini` from the embedded template when missing, then loads it. 2. It scans `scan_paths` recursively up to `scan_depth` for `.ffpfsc`. 3. For each candidate image, it builds: - `GAMEID` from filename (PlayStation-style `AAAA0000` or `AAAA00000` when found). @@ -78,6 +78,7 @@ mkpfs pack file --compress --verify ./GAME1234.exfat ./GAME1234.ffpfsc ## 🛠️ Configuration Use `config.ini.example` as the template for `/data/micromount/config.ini`. +If `/data/micromount/config.ini` does not exist at runtime, MicroMount automatically creates it from the embedded `config.ini.example`. Core keys: @@ -85,7 +86,7 @@ Core keys: - `scanpath` (repeatable) - `scan_paths` (comma/semicolon-separated list) - `scan_depth` (default: `1`) -- `scan_interval_seconds` (default: `15`) +- `scan_interval_seconds` (default: `30`) - `debug` (default: `1`) Mount profile keys: diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000..411e559 Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/icon.psd b/assets/icon.psd new file mode 100755 index 0000000..b88b00a Binary files /dev/null and b/assets/icon.psd differ diff --git a/assets/icon_large.png b/assets/icon_large.png new file mode 100755 index 0000000..84da3b5 Binary files /dev/null and b/assets/icon_large.png differ diff --git a/config.ini.example b/config.ini.example index c359586..acfb49b 100644 --- a/config.ini.example +++ b/config.ini.example @@ -17,7 +17,7 @@ # scan_depth=1 # Full scan interval in seconds. -# scan_interval_seconds=15 +# scan_interval_seconds=30 # Default scan roots copied from ShadowMountPlus are used automatically. # To override them, add one or more scanpath entries or a scan_paths list. diff --git a/include/mm_paths.h b/include/mm_paths.h index 538192a..b8edd34 100644 --- a/include/mm_paths.h +++ b/include/mm_paths.h @@ -5,6 +5,8 @@ #define MM_CONFIG_FILE MM_ROOT_DIR "/config.ini" #define MM_LOG_FILE MM_ROOT_DIR "/debug.log" #define MM_MANAGED_PREFIX "micromount-" +#define MM_NOTIFY_ICON_DIR "/user/data/micromount" +#define MM_NOTIFY_ICON_FILE MM_NOTIFY_ICON_DIR "/icon.png" #define MM_DEFAULT_SCAN_PATHS_INITIALIZER \ { \ diff --git a/include/mm_runtime.h b/include/mm_runtime.h new file mode 100644 index 0000000..29aef45 --- /dev/null +++ b/include/mm_runtime.h @@ -0,0 +1,8 @@ +#ifndef MM_RUNTIME_H +#define MM_RUNTIME_H + +#include "mm_platform.h" + +bool mm_should_stop(void); + +#endif diff --git a/src/main.c b/src/main.c index 55e3792..2844f34 100644 --- a/src/main.c +++ b/src/main.c @@ -3,8 +3,15 @@ #include "mm_mount.h" #include "mm_paths.h" #include "mm_platform.h" +#include "mm_runtime.h" #include "mm_scan.h" +#include +#include + +#define MM_RESTART_WAIT_POLL_US 200000u +#define MM_RESTART_WAIT_TIMEOUT_US 15000000u + static volatile sig_atomic_t g_stop_requested = 0; static void mm_on_signal(int signal_number) { @@ -24,12 +31,12 @@ static void mm_install_signal_handlers(void) { sigaction(SIGQUIT, &action, NULL); } -static bool mm_should_stop(void) { +bool mm_should_stop(void) { return g_stop_requested != 0; } -static bool mm_sleep_with_stop(unsigned int seconds) { - unsigned int remaining_us = seconds * 1000000u; +static bool mm_sleep_with_stop_us(unsigned int total_us) { + unsigned int remaining_us = total_us; while (remaining_us > 0) { unsigned int step = remaining_us > 200000u ? 200000u : remaining_us; @@ -42,6 +49,125 @@ static bool mm_sleep_with_stop(unsigned int seconds) { return mm_should_stop(); } +static bool mm_sleep_with_stop(unsigned int seconds) { + if (seconds > UINT_MAX / 1000000u) + return mm_sleep_with_stop_us(UINT_MAX); + return mm_sleep_with_stop_us(seconds * 1000000u); +} + +static pid_t mm_find_pid_by_name(const char *name, bool exclude_self) { + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0}; + size_t buffer_size = 0; + uint8_t *buffer = NULL; + uint8_t *cursor; + uint8_t *end; + size_t name_length; + pid_t self_pid = exclude_self ? getpid() : -1; + pid_t found_pid = 0; + + if (!name) + return -1; + + name_length = strlen(name); + if (name_length >= sizeof(((struct kinfo_proc *)0)->ki_tdname)) + return 0; + + if (sysctl(mib, 4, NULL, &buffer_size, NULL, 0) != 0) + return -1; + if (buffer_size == 0) + return 0; + + buffer = malloc(buffer_size); + if (!buffer) + return -1; + + if (sysctl(mib, 4, buffer, &buffer_size, NULL, 0) != 0) { + free(buffer); + return -1; + } + + cursor = buffer; + end = buffer + buffer_size; + while (cursor < end) { + const struct kinfo_proc *entry; + int entry_size; + size_t thread_name_length; + + if ((size_t)(end - cursor) < sizeof(int)) + break; + + entry_size = *(const int *)cursor; + if (entry_size <= 0 || (size_t)entry_size > (size_t)(end - cursor)) + break; + + if ((size_t)entry_size < + offsetof(struct kinfo_proc, ki_tdname) + + sizeof(((struct kinfo_proc *)0)->ki_tdname)) { + cursor += entry_size; + continue; + } + + entry = (const struct kinfo_proc *)(const void *)cursor; + thread_name_length = + strnlen(entry->ki_tdname, sizeof(entry->ki_tdname)); + if ((!exclude_self || entry->ki_pid != self_pid) && + thread_name_length == name_length && + memcmp(entry->ki_tdname, name, name_length) == 0) { + found_pid = entry->ki_pid; + break; + } + + cursor += entry_size; + } + + free(buffer); + return found_pid; +} + +static bool mm_terminate_existing_processes(void) { + pid_t target_pid; + pid_t active_pid = 0; + unsigned int waited_us = 0; + + target_pid = mm_find_pid_by_name(MM_PAYLOAD_NAME, true); + if (target_pid < 0) + return false; + if (target_pid == 0) + return true; + + mm_notify_debug( + "Found an existing MicroMount process. Attempting to terminate it."); + + while (!mm_should_stop()) { + target_pid = mm_find_pid_by_name(MM_PAYLOAD_NAME, true); + if (target_pid < 0) + return false; + if (target_pid == 0) + return true; + + if (target_pid != active_pid) { + active_pid = target_pid; + waited_us = 0; + } + + if (waited_us >= MM_RESTART_WAIT_TIMEOUT_US) { + if (kill(target_pid, SIGKILL) != 0 && errno != ESRCH) + return false; + if (waited_us >= MM_RESTART_WAIT_TIMEOUT_US + 2000000u) + return false; + } else { + if (kill(target_pid, SIGTERM) != 0 && errno != ESRCH) + return false; + } + + if (mm_sleep_with_stop_us(MM_RESTART_WAIT_POLL_US)) + return false; + waited_us += MM_RESTART_WAIT_POLL_US; + } + + return false; +} + static void mm_run_cycle(mm_config_t *config, bool first_cycle, size_t *last_notified_total) { mm_candidate_list_t candidates; @@ -53,17 +179,25 @@ static void mm_run_cycle(mm_config_t *config, bool first_cycle, if (!mm_config_reload_if_changed(config, &config_reloaded)) summary.errors++; + if (mm_should_stop()) + goto cleanup; if (config_reloaded) mm_notify_debug("Reloaded %s", MM_CONFIG_FILE); - if (!mm_scan_for_images(config, &candidates)) + if (!mm_scan_for_images(config, &candidates) && !mm_should_stop()) summary.errors++; + if (mm_should_stop()) + goto cleanup; summary.discovered = candidates.count; mm_cleanup_managed_mounts(config, &candidates, &summary.cleaned, &summary.errors); + if (mm_should_stop()) + goto cleanup; mm_reconcile_mounts(config, &candidates, &summary.mounted, &summary.skipped, &summary.errors); + if (mm_should_stop()) + goto cleanup; summary.total_mounted = mm_count_managed_mounts(config); mm_log_info("CYCLE", @@ -82,25 +216,49 @@ static void mm_run_cycle(mm_config_t *config, bool first_cycle, *last_notified_total = summary.total_mounted; } +cleanup: mm_candidate_list_free(&candidates); } int main(void) { mm_config_t config; bool user_service_initialized = false; + bool replaced_previous_process = false; bool first_cycle = true; size_t last_notified_total = SIZE_MAX; + pid_t existing_pid; kernel_set_ucred_authid(-1, MM_AUTHID_BASE); - (void)syscall(SYS_thr_set_name, -1, MM_PAYLOAD_NAME); mm_install_signal_handlers(); - - mm_log_init(true); if (sceUserServiceInitialize(NULL) == 0) user_service_initialized = true; else mm_log_warn("CORE", "sceUserServiceInitialize failed; notifications may be limited"); + existing_pid = mm_find_pid_by_name(MM_PAYLOAD_NAME, true); + if (existing_pid < 0) { + mm_log_error("CORE", "failed to enumerate running processes"); + if (user_service_initialized) + sceUserServiceTerminate(); + return 1; + } + if (existing_pid > 0) { + if (!mm_terminate_existing_processes()) { + mm_log_error("CORE", "failed to stop previous MicroMount process"); + if (user_service_initialized) + sceUserServiceTerminate(); + return 1; + } + replaced_previous_process = true; + } + + (void)syscall(SYS_thr_set_name, -1, MM_PAYLOAD_NAME); + mm_log_init(true); + if (replaced_previous_process) { + mm_notify_debug( + "Previous process terminated. Starting a new micromount process..."); + } + if (!mm_config_load(&config)) { mm_log_error("CORE", "failed to load config, continuing with defaults"); mm_config_init_defaults(&config); diff --git a/src/mm_config.c b/src/mm_config.c index a462a97..8eacd74 100644 --- a/src/mm_config.c +++ b/src/mm_config.c @@ -7,6 +7,9 @@ static const char *const k_default_scan_paths[] = MM_DEFAULT_SCAN_PATHS_INITIALIZER; +extern unsigned char config_ini_example[]; +extern unsigned int config_ini_example_len; + static bool mm_config_add_scan_path(mm_config_t *config, const char *path) { size_t index; @@ -53,7 +56,7 @@ static void mm_config_apply_defaults(mm_config_t *config) { (void)mm_copy_string(config->target_directory, sizeof(config->target_directory), "/data/homebrew"); config->scan_depth = 1u; - config->scan_interval_seconds = 15u; + config->scan_interval_seconds = 30u; config->debug_enabled = true; config->mount_profile.lvd_image_type = 0u; @@ -313,6 +316,52 @@ static bool mm_config_parse_file(mm_config_t *config) { return true; } +static bool mm_config_write_example_file(void) { + FILE *fp; + size_t written; + + fp = fopen(MM_CONFIG_FILE, "wb"); + if (!fp) { + mm_log_warn("CFG", "failed to create %s: %s", MM_CONFIG_FILE, + strerror(errno)); + return false; + } + + written = fwrite(config_ini_example, 1, config_ini_example_len, fp); + if (written != (size_t)config_ini_example_len) { + mm_log_warn("CFG", "failed to write %s", MM_CONFIG_FILE); + fclose(fp); + (void)unlink(MM_CONFIG_FILE); + return false; + } + + if (fclose(fp) != 0) { + mm_log_warn("CFG", "failed to finalize %s: %s", MM_CONFIG_FILE, + strerror(errno)); + (void)unlink(MM_CONFIG_FILE); + return false; + } + + mm_log_info("CFG", "created default config from embedded template: %s", + MM_CONFIG_FILE); + return true; +} + +static void mm_config_create_if_missing(void) { + struct stat st; + + if (stat(MM_CONFIG_FILE, &st) == 0) + return; + + if (errno != ENOENT) { + mm_log_warn("CFG", "failed to stat %s: %s", MM_CONFIG_FILE, + strerror(errno)); + return; + } + + (void)mm_config_write_example_file(); +} + void mm_config_init_defaults(mm_config_t *config) { if (!config) return; @@ -330,6 +379,8 @@ bool mm_config_load(mm_config_t *config) { mm_log_warn("CFG", "failed to create %s: %s", MM_ROOT_DIR, strerror(errno)); } + mm_config_create_if_missing(); + config->config_file_present = (stat(MM_CONFIG_FILE, &st) == 0); if (config->config_file_present) config->config_mtime = st.st_mtime; diff --git a/src/mm_log.c b/src/mm_log.c index e339910..238929f 100644 --- a/src/mm_log.c +++ b/src/mm_log.c @@ -7,6 +7,16 @@ static FILE *g_log_file = NULL; static bool g_debug_enabled = true; +static bool g_log_initialized = false; +static bool g_notification_icon_checked = false; +static bool g_notification_icon_ready = false; + +#define MM_LOG_FILE_1 MM_LOG_FILE ".1" +#define MM_LOG_FILE_2 MM_LOG_FILE ".2" +#define MM_LOG_FILE_3 MM_LOG_FILE ".3" + +extern unsigned char icon_png[]; +extern unsigned int icon_png_len; static FILE *mm_ensure_log_file_open(void) { if (g_log_file) @@ -17,6 +27,26 @@ static FILE *mm_ensure_log_file_open(void) { return g_log_file; } +static void mm_rotate_file_if_present(const char *from, const char *to) { + if (rename(from, to) != 0 && errno != ENOENT) { + fprintf(stdout, "[WARN] [LOG] failed to rotate %s to %s: %s\n", from, to, + strerror(errno)); + fflush(stdout); + } +} + +static void mm_rotate_log_files(void) { + if (unlink(MM_LOG_FILE_3) != 0 && errno != ENOENT) { + fprintf(stdout, "[WARN] [LOG] failed to remove %s: %s\n", MM_LOG_FILE_3, + strerror(errno)); + fflush(stdout); + } + + mm_rotate_file_if_present(MM_LOG_FILE_2, MM_LOG_FILE_3); + mm_rotate_file_if_present(MM_LOG_FILE_1, MM_LOG_FILE_2); + mm_rotate_file_if_present(MM_LOG_FILE, MM_LOG_FILE_1); +} + static void mm_build_timestamp(char out[32]) { time_t now; struct tm local_tm; @@ -99,7 +129,9 @@ static void mm_log_emit_v(const char *level, const char *subsystem, fputc('\n', stdout); fflush(stdout); - fp = mm_ensure_log_file_open(); + fp = NULL; + if (g_log_initialized) + fp = mm_ensure_log_file_open(); if (fp) { fprintf(fp, "[%s] [%s] [%s] ", timestamp, level, subsystem); vfprintf(fp, fmt, file_args); @@ -127,6 +159,52 @@ static void mm_notify_plain_message(const char *message) { (void)sceKernelSendNotificationRequest(0, &request, sizeof(request), 0); } +static bool mm_ensure_notification_icon_present(void) { + struct stat st; + FILE *fp; + size_t written; + int saved_errno = 0; + + if (stat(MM_NOTIFY_ICON_FILE, &st) == 0 && st.st_size > 0) + return true; + + (void)mkdir("/user/data", 0777); + (void)mkdir(MM_NOTIFY_ICON_DIR, 0777); + + fp = fopen(MM_NOTIFY_ICON_FILE, "wb"); + if (!fp) + return false; + + written = fwrite(icon_png, 1, icon_png_len, fp); + if (written != (size_t)icon_png_len) + saved_errno = ferror(fp) ? errno : EIO; + if (fflush(fp) != 0 && saved_errno == 0) + saved_errno = errno; + if (fclose(fp) != 0 && saved_errno == 0) + saved_errno = errno; + + if (saved_errno != 0) { + errno = saved_errno; + (void)unlink(MM_NOTIFY_ICON_FILE); + return false; + } + + return true; +} + +static bool mm_notifications_init(void) { + if (!g_notification_icon_checked) { + g_notification_icon_ready = mm_ensure_notification_icon_present(); + g_notification_icon_checked = true; + if (!g_notification_icon_ready) { + mm_log_emit("WARN", "NOTIFY", "failed to prepare icon at %s: %s", + MM_NOTIFY_ICON_FILE, strerror(errno)); + } + } + + return g_notification_icon_ready; +} + static bool mm_notify_rich_message(const char *message) { char payload[8192]; char created_at[32]; @@ -137,6 +215,8 @@ static bool mm_notify_rich_message(const char *message) { if (!message || message[0] == '\0') return false; + if (!mm_notifications_init()) + return false; if (!mm_build_notification_timestamp(created_at)) return false; @@ -161,7 +241,8 @@ static bool mm_notify_rich_message(const char *message) { "\"isImmediate\":true," "\"priority\":100," "\"viewData\":{" - "\"icon\":{\"type\":\"Predefined\",\"parameters\":{\"icon\":\"community\"}}," + "\"icon\":{\"type\":\"Url\",\"parameters\":{\"url\":\"" MM_NOTIFY_ICON_FILE + "\"}}," "\"message\":{\"body\":\"%s\"}," "\"subMessage\":{\"body\":\"MicroMount %s\"}" "}" @@ -191,6 +272,13 @@ static void mm_notify_v(bool allow_when_debug_disabled, const char *fmt, void mm_log_init(bool debug_enabled) { g_debug_enabled = debug_enabled; + if (g_log_file) { + fclose(g_log_file); + g_log_file = NULL; + } + g_log_initialized = true; + (void)mm_ensure_dir_recursive(MM_ROOT_DIR); + mm_rotate_log_files(); (void)mm_ensure_log_file_open(); } @@ -199,6 +287,7 @@ void mm_log_shutdown(void) { fclose(g_log_file); g_log_file = NULL; } + g_log_initialized = false; } void mm_log_set_debug_enabled(bool enabled) { diff --git a/src/mm_mount.c b/src/mm_mount.c index 0d32380..3c61dcf 100644 --- a/src/mm_mount.c +++ b/src/mm_mount.c @@ -2,6 +2,7 @@ #include "mm_log.h" #include "mm_paths.h" +#include "mm_runtime.h" #include "mm_util.h" #define MM_LVD_CTRL_PATH "/dev/lvdctl" @@ -57,6 +58,8 @@ typedef struct { size_t capacity; } mm_managed_mount_list_t; +static void mm_lvd_detach(int unit_id); + static uint16_t mm_normalize_raw_flags(uint16_t raw_flags) { switch (raw_flags) { case 0x8: @@ -90,6 +93,8 @@ static bool mm_wait_for_dev_node(const char *devname, bool should_exist) { for (retry = 0; retry < MM_LVD_NODE_WAIT_RETRIES; ++retry) { struct stat st; bool exists = (stat(devname, &st) == 0); + if (mm_should_stop()) + return false; if (exists == should_exist) return true; (void)sceKernelUsleep(MM_LVD_NODE_WAIT_US); @@ -152,6 +157,7 @@ static int mm_lvd_attach(const char *image_path, off_t image_size, } if (!mm_wait_for_dev_node(devname_out, true)) { + mm_lvd_detach(unit_id); mm_log_error("MOUNT", "device node did not appear: %s", devname_out); return -1; } @@ -333,6 +339,12 @@ static bool mm_collect_managed_mounts(const mm_config_t *config, struct stat st; mm_managed_mount_t item; + if (mm_should_stop()) { + closedir(dir); + mm_managed_mount_list_free(list); + return false; + } + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; if (strncmp(entry->d_name, MM_MANAGED_PREFIX, @@ -413,6 +425,9 @@ static bool mm_mount_candidate(const mm_config_t *config, char errmsg[256]; int unit_id; + if (mm_should_stop()) + return false; + if (stat(candidate->source_path, &st) != 0) { mm_log_error("MOUNT", "source missing before mount %s: %s", candidate->source_path, strerror(errno)); @@ -438,6 +453,11 @@ static bool mm_mount_candidate(const mm_config_t *config, return false; } + if (mm_should_stop()) { + mm_lvd_detach(unit_id); + return false; + } + errmsg[0] = '\0'; if (!mm_try_pfs_nmount(devname, candidate->mount_path, &config->mount_profile, errmsg, sizeof(errmsg))) { @@ -479,6 +499,8 @@ void mm_cleanup_managed_mounts(const mm_config_t *config, *errors_out = 0; if (!mm_collect_managed_mounts(config, &list)) { + if (mm_should_stop()) + return; if (errors_out) (*errors_out)++; return; @@ -490,6 +512,9 @@ void mm_cleanup_managed_mounts(const mm_config_t *config, bool should_cleanup = (!desired) || item->empty || (desired && item->mounted && item->empty); + if (mm_should_stop()) + break; + if (!should_cleanup) continue; @@ -524,6 +549,9 @@ void mm_reconcile_mounts(const mm_config_t *config, bool empty = true; struct stat st; + if (mm_should_stop()) + break; + (void)mm_lookup_mount_info(candidate->mount_path, &mounted, NULL); if (mounted) { if (mm_dir_is_empty(candidate->mount_path, &empty) && empty) { @@ -567,7 +595,7 @@ void mm_reconcile_mounts(const mm_config_t *config, if (mm_mount_candidate(config, candidate)) { if (mounted_out) (*mounted_out)++; - } else if (errors_out) { + } else if (!mm_should_stop() && errors_out) { (*errors_out)++; } } diff --git a/src/mm_scan.c b/src/mm_scan.c index aae1a42..436ffcc 100644 --- a/src/mm_scan.c +++ b/src/mm_scan.c @@ -2,6 +2,7 @@ #include "mm_log.h" #include "mm_paths.h" +#include "mm_runtime.h" #include "mm_sha256.h" #include "mm_util.h" @@ -130,6 +131,9 @@ static bool mm_scan_directory(const mm_config_t *config, DIR *dir; struct dirent *entry; + if (mm_should_stop()) + return false; + dir = opendir(current_path); if (!dir) { if (errno != ENOENT) @@ -142,6 +146,11 @@ static bool mm_scan_directory(const mm_config_t *config, char full_path[PATH_MAX]; struct stat st; + if (mm_should_stop()) { + closedir(dir); + return false; + } + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; if (strncmp(entry->d_name, MM_MANAGED_PREFIX, @@ -225,9 +234,13 @@ bool mm_scan_for_images(const mm_config_t *config, mm_candidate_list_t *list) { mm_candidate_list_init(list); for (index = 0; index < config->scan_path_count; ++index) { + if (mm_should_stop()) + return false; mm_log_debug("SCAN", "walking root=%s depth=%u", config->scan_paths[index], config->scan_depth); if (!mm_scan_directory(config, config->scan_paths[index], 0u, list)) { + if (mm_should_stop()) + return false; success = false; } }