mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-21 22:22:28 -05:00
Harden managed mount teardown
This commit is contained in:
1 parent
03ad6d2e45
commit
e24a444147
7 files changed
+419
-70
No files matched your search
@@ -43,7 +43,7 @@ bool rollback_title_nullfs_mount(const char *title_id, const char *src_path);
|
||||
// Drop runtime layers for a title while preserving its metadata/link files.
|
||||
bool unmount_title_runtime_layers(const char *title_id);
|
||||
// Release every managed title layer while preserving link metadata.
|
||||
void unmount_all_title_runtime_layers(void);
|
||||
bool unmount_all_title_runtime_layers(void);
|
||||
// Reconcile the title mount stack against the expected source/backport state.
|
||||
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
|
||||
const char *expected_backport_path,
|
||||
@@ -54,7 +54,7 @@ bool mount_backport_overlay(const char *mount_point,
|
||||
const char *backport_path,
|
||||
const char *title_id);
|
||||
// Unmount all managed /system_ex/app/<title_id> mount stacks on shutdown.
|
||||
void shutdown_title_mounts(void);
|
||||
bool shutdown_title_mounts(void);
|
||||
// Remove stale mount links and optionally restore image-backed mounts.
|
||||
void cleanup_mount_links(const char *removed_source_root,
|
||||
bool unmount_system_ex_bind);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef SM_MOUNT_DIAG_H
|
||||
#define SM_MOUNT_DIAG_H
|
||||
|
||||
// Log read-only evidence explaining why unmount(path) returned EBUSY.
|
||||
void sm_mount_diag_log_busy(const char *mount_point);
|
||||
|
||||
#endif
|
||||
+8
-3
@@ -3,6 +3,7 @@
|
||||
#include "sm_fakelib.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_mount_diag.h"
|
||||
#include "sm_types.h"
|
||||
|
||||
#include <pthread.h>
|
||||
@@ -52,15 +53,19 @@ static bool mount_fakelib_overlay(const char *title_id,
|
||||
|
||||
static bool unmount_fakelib_overlay(const fakelib_layer_t *layer) {
|
||||
const char *mount_path = layer->mount_path;
|
||||
if (unmount(mount_path, MNT_FORCE) == 0 || errno == ENOENT ||
|
||||
if (unmount(mount_path, 0) == 0 || errno == ENOENT ||
|
||||
errno == EINVAL) {
|
||||
log_debug(" [FAKELIB] %s libraries unmounted: %s -> %s", layer->label,
|
||||
layer->source_path, mount_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
log_debug(" [FAKELIB] %s unmount failed for %s: %s", layer->label,
|
||||
mount_path, strerror(errno));
|
||||
int unmount_errno = errno;
|
||||
if (unmount_errno == EBUSY)
|
||||
sm_mount_diag_log_busy(mount_path);
|
||||
log_debug(" [FAKELIB] %s unmount deferred for %s: %s", layer->label,
|
||||
mount_path, strerror(unmount_errno));
|
||||
errno = unmount_errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+61
-64
@@ -4,6 +4,7 @@
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_mount_diag.h"
|
||||
#include "sm_image_cache.h"
|
||||
#include "sm_image.h"
|
||||
#include "sm_path_utils.h"
|
||||
@@ -89,7 +90,7 @@ typedef bool (*title_app_dir_iter_fn)(const char *title_id,
|
||||
const title_link_paths_t *paths,
|
||||
void *ctx);
|
||||
|
||||
static void for_each_title_app_dir(const char *open_error_context,
|
||||
static bool for_each_title_app_dir(const char *open_error_context,
|
||||
bool stop_on_signal,
|
||||
title_app_dir_iter_fn fn, void *ctx) {
|
||||
DIR *d = opendir(APP_BASE);
|
||||
@@ -97,23 +98,30 @@ static void for_each_title_app_dir(const char *open_error_context,
|
||||
if (errno != ENOENT)
|
||||
log_debug(" [LINK] open %s failed%s: %s", APP_BASE,
|
||||
open_error_context ? open_error_context : "", strerror(errno));
|
||||
return;
|
||||
return errno == ENOENT;
|
||||
}
|
||||
|
||||
bool completed = true;
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(d)) != NULL) {
|
||||
if (stop_on_signal && should_stop_requested())
|
||||
if (stop_on_signal && should_stop_requested()) {
|
||||
completed = false;
|
||||
break;
|
||||
}
|
||||
if (!resolve_title_app_dir(entry, NULL, 0))
|
||||
continue;
|
||||
|
||||
title_link_paths_t paths;
|
||||
build_title_link_paths(entry->d_name, &paths);
|
||||
if (!fn(entry->d_name, &paths, ctx))
|
||||
if (!fn(entry->d_name, &paths, ctx)) {
|
||||
completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
if (closedir(d) != 0)
|
||||
completed = false;
|
||||
return completed;
|
||||
}
|
||||
|
||||
bool read_mount_link(const char *title_id, char *out, size_t out_size) {
|
||||
@@ -286,8 +294,7 @@ bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool source_path_needs_cleanup(const char *source_path,
|
||||
bool *tried_image_recovery) {
|
||||
static bool source_path_needs_cleanup(const char *source_path) {
|
||||
if (is_under_image_mount_base(source_path)) {
|
||||
char image_source_path[MAX_PATH];
|
||||
bool has_image_source = resolve_image_source_from_mount_cache(
|
||||
@@ -314,11 +321,6 @@ static bool source_path_needs_cleanup(const char *source_path,
|
||||
if (path_exists(eboot_path))
|
||||
return false;
|
||||
|
||||
if (!*tried_image_recovery && is_under_image_mount_base(source_path)) {
|
||||
cleanup_stale_image_mounts();
|
||||
*tried_image_recovery = true;
|
||||
}
|
||||
|
||||
return !path_exists(eboot_path);
|
||||
}
|
||||
|
||||
@@ -608,10 +610,13 @@ static bool unmount_top_controlled_layer(const char *path) {
|
||||
|
||||
if (unmount(path, 0) == 0 || errno == ENOENT || errno == EINVAL)
|
||||
return true;
|
||||
if (unmount(path, MNT_FORCE) == 0 || errno == ENOENT || errno == EINVAL)
|
||||
return true;
|
||||
int unmount_errno = errno;
|
||||
if (unmount_errno == EBUSY)
|
||||
sm_mount_diag_log_busy(path);
|
||||
|
||||
log_debug(" [LINK] unmount failed for %s: %s", path, strerror(errno));
|
||||
log_debug(" [LINK] unmount deferred for %s: %s", path,
|
||||
strerror(unmount_errno));
|
||||
errno = unmount_errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -650,24 +655,32 @@ bool unmount_title_runtime_layers(const char *title_id) {
|
||||
|
||||
static bool unmount_managed_title_entry(
|
||||
const char *title_id, const title_link_paths_t *paths, void *ctx) {
|
||||
(void)ctx;
|
||||
bool *all_released = (bool *)ctx;
|
||||
char source_path[MAX_PATH];
|
||||
if (!read_mount_link_file(paths->mount_link, source_path,
|
||||
sizeof(source_path))) {
|
||||
return true;
|
||||
}
|
||||
title_mount_state_t state;
|
||||
if (!inspect_title_stack(title_id, source_path, NULL, &state) ||
|
||||
!state.has_our_nullfs) {
|
||||
if (!inspect_title_stack(title_id, source_path, NULL, &state)) {
|
||||
*all_released = false;
|
||||
return true;
|
||||
}
|
||||
(void)unmount_title_runtime_layers(title_id);
|
||||
if (!state.has_our_nullfs) {
|
||||
return true;
|
||||
}
|
||||
if (!unmount_title_runtime_layers(title_id))
|
||||
*all_released = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void unmount_all_title_runtime_layers(void) {
|
||||
for_each_title_app_dir(" during runtime mount cleanup", false,
|
||||
unmount_managed_title_entry, NULL);
|
||||
bool unmount_all_title_runtime_layers(void) {
|
||||
bool all_released = true;
|
||||
if (!for_each_title_app_dir(" during runtime mount cleanup", false,
|
||||
unmount_managed_title_entry, &all_released)) {
|
||||
all_released = false;
|
||||
}
|
||||
return all_released;
|
||||
}
|
||||
|
||||
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
|
||||
@@ -767,23 +780,6 @@ static void unmount_mount_point_for_recovery(const char *path) {
|
||||
if (!unmount_controlled_mount_stack(path)) {
|
||||
log_debug(" [LINK] failed to reset mount stack for recovery: %s", path);
|
||||
}
|
||||
|
||||
if (unmount(path, 0) == 0) {
|
||||
log_debug(" [LINK] extra unmount for recovery: %s", path);
|
||||
return;
|
||||
}
|
||||
if (errno == ENOENT || errno == EINVAL)
|
||||
return;
|
||||
|
||||
if (unmount(path, MNT_FORCE) == 0) {
|
||||
log_debug(" [LINK] extra forced unmount for recovery: %s", path);
|
||||
return;
|
||||
}
|
||||
if (errno == ENOENT || errno == EINVAL)
|
||||
return;
|
||||
|
||||
log_debug(" [LINK] extra unmount for recovery did not complete for %s: %s",
|
||||
path, strerror(errno));
|
||||
}
|
||||
|
||||
static bool recover_mount_directory_after_stat_failure(
|
||||
@@ -1049,8 +1045,7 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
|
||||
return false;
|
||||
}
|
||||
if (!path_exists(src_eboot)) {
|
||||
bool tried_image_recovery = false;
|
||||
if (source_path_needs_cleanup(src_path, &tried_image_recovery)) {
|
||||
if (source_path_needs_cleanup(src_path)) {
|
||||
log_debug(" [LINK] source eboot.bin missing for %s: %s", title_id,
|
||||
src_eboot);
|
||||
return false;
|
||||
@@ -1114,20 +1109,16 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
|
||||
}
|
||||
|
||||
if (runtime_sleep_mode_active()) {
|
||||
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL)
|
||||
(void)unmount(dst, MNT_FORCE);
|
||||
if (!unmount_top_controlled_layer(dst))
|
||||
log_debug(" [LINK] nullfs rollback deferred during sleep: %s", dst);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!path_exists(dst_eboot)) {
|
||||
log_debug(" [LINK] mounted nullfs but eboot.bin is missing at target: %s",
|
||||
dst_eboot);
|
||||
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL) {
|
||||
if (unmount(dst, MNT_FORCE) != 0 && errno != ENOENT && errno != EINVAL) {
|
||||
log_debug(" [LINK] failed to rollback empty nullfs mount %s: %s", dst,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
if (!unmount_top_controlled_layer(dst))
|
||||
log_debug(" [LINK] empty nullfs rollback deferred: %s", dst);
|
||||
return false;
|
||||
}
|
||||
log_debug(" [LINK] nullfs mounted: %s -> %s", src_path, dst);
|
||||
@@ -1137,7 +1128,6 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
|
||||
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;
|
||||
bool preserve_mount_links;
|
||||
@@ -1184,12 +1174,10 @@ static bool cleanup_mount_links_entry(const char *title_id,
|
||||
return true;
|
||||
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);
|
||||
source_path_needs_cleanup(source_path);
|
||||
} else {
|
||||
should_remove = (has_image_source && !path_exists(image_source_path)) ||
|
||||
source_path_needs_cleanup(source_path,
|
||||
&ctx->tried_image_recovery);
|
||||
source_path_needs_cleanup(source_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,7 +1260,6 @@ void cleanup_mount_links(const char *removed_source_root,
|
||||
cleanup_mount_links_ctx_t ctx = {
|
||||
.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,
|
||||
.preserve_mount_links = false,
|
||||
@@ -1288,7 +1275,6 @@ void cleanup_mount_links_for_source_unmount(const char *source_root) {
|
||||
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,
|
||||
.preserve_mount_links = false,
|
||||
@@ -1301,7 +1287,6 @@ 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,
|
||||
.preserve_mount_links = true,
|
||||
@@ -1313,7 +1298,7 @@ void cleanup_usb_mount_links_for_suspend(void) {
|
||||
static bool shutdown_title_mounts_entry(const char *title_id,
|
||||
const title_link_paths_t *paths,
|
||||
void *ctx) {
|
||||
(void)ctx;
|
||||
bool *all_released = (bool *)ctx;
|
||||
|
||||
char source_path[MAX_PATH];
|
||||
if (!read_mount_link_file(paths->mount_link, source_path, sizeof(source_path))) {
|
||||
@@ -1321,20 +1306,32 @@ static bool shutdown_title_mounts_entry(const char *title_id,
|
||||
}
|
||||
|
||||
title_mount_state_t state;
|
||||
if (!inspect_title_stack(title_id, source_path, NULL, &state) ||
|
||||
!state.has_our_nullfs ||
|
||||
!title_stack_top_is_managed(&state)) {
|
||||
if (!inspect_title_stack(title_id, source_path, NULL, &state)) {
|
||||
*all_released = false;
|
||||
return true;
|
||||
}
|
||||
if (!state.has_our_nullfs)
|
||||
return true;
|
||||
if (!title_stack_top_is_managed(&state)) {
|
||||
log_debug(" [LINK] shutdown stack blocked by unmanaged top for %s",
|
||||
state.system_ex_path);
|
||||
*all_released = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!unmount_controlled_mount_stack(state.system_ex_path)) {
|
||||
log_debug(" [LINK] failed to unmount shutdown stack for %s",
|
||||
state.system_ex_path);
|
||||
*all_released = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown_title_mounts(void) {
|
||||
for_each_title_app_dir(" during shutdown", false,
|
||||
shutdown_title_mounts_entry, NULL);
|
||||
bool shutdown_title_mounts(void) {
|
||||
bool all_released = true;
|
||||
if (!for_each_title_app_dir(" during shutdown", false,
|
||||
shutdown_title_mounts_entry, &all_released)) {
|
||||
all_released = false;
|
||||
}
|
||||
return all_released;
|
||||
}
|
||||
+12
-1
@@ -105,7 +105,18 @@ bool resolve_device_from_mount(const char *mount_point,
|
||||
|
||||
static bool is_path_mountpoint(const char *path) {
|
||||
struct statfs sfs;
|
||||
return (statfs(path, &sfs) == 0 && strcmp(sfs.f_mntonname, path) == 0);
|
||||
if (statfs(path, &sfs) == 0 && strcmp(sfs.f_mntonname, path) == 0)
|
||||
return true;
|
||||
|
||||
// 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);
|
||||
for (int i = 0; i < mntcount && mntbuf; ++i) {
|
||||
if (strcmp(mntbuf[i].f_mntonname, path) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_active_image_mount_point(const char *path) {
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "sm_log.h"
|
||||
#include "sm_mount_diag.h"
|
||||
#include "sm_path_utils.h"
|
||||
|
||||
#define MOUNT_DIAG_MAX_ROOTS 16u
|
||||
#define MOUNT_DIAG_MAX_VM_HITS 32u
|
||||
#define MOUNT_DIAG_MAX_VNODE_HITS 16u
|
||||
|
||||
typedef struct {
|
||||
char paths[MOUNT_DIAG_MAX_ROOTS][MAX_PATH];
|
||||
size_t count;
|
||||
} mount_diag_roots_t;
|
||||
|
||||
static void *read_sysctl_snapshot(const int *mib, u_int mib_count,
|
||||
size_t *size_out) {
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
size_t needed = 0;
|
||||
if (sysctl(mib, mib_count, NULL, &needed, NULL, 0) != 0)
|
||||
return NULL;
|
||||
if (needed == 0) {
|
||||
*size_out = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t capacity = needed + needed / 8u + 4096u;
|
||||
uint8_t *buffer = malloc(capacity);
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
size_t used = capacity;
|
||||
if (sysctl(mib, mib_count, buffer, &used, NULL, 0) == 0) {
|
||||
*size_out = used;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int snapshot_errno = errno;
|
||||
free(buffer);
|
||||
if (snapshot_errno != ENOMEM) {
|
||||
errno = snapshot_errno;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool copy_record_path(char out[MAX_PATH], const uint8_t *record,
|
||||
size_t record_size, size_t path_offset) {
|
||||
out[0] = '\0';
|
||||
if (record_size <= path_offset)
|
||||
return false;
|
||||
|
||||
const char *path = (const char *)(record + path_offset);
|
||||
size_t available = record_size - path_offset;
|
||||
size_t length = strnlen(path, available);
|
||||
if (length == 0 || length == available)
|
||||
return false;
|
||||
|
||||
(void)strlcpy(out, path, MAX_PATH);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copy_process_name(char out[TDNAMLEN + 1], const uint8_t *record,
|
||||
size_t record_size) {
|
||||
size_t name_offset = offsetof(struct kinfo_proc, ki_tdname);
|
||||
out[0] = '\0';
|
||||
if (record_size <= name_offset)
|
||||
return;
|
||||
|
||||
const char *name = (const char *)(record + name_offset);
|
||||
size_t available = record_size - name_offset;
|
||||
size_t length = strnlen(name, available);
|
||||
if (length == available)
|
||||
return;
|
||||
if (length > TDNAMLEN)
|
||||
length = TDNAMLEN;
|
||||
memcpy(out, name, length);
|
||||
out[length] = '\0';
|
||||
}
|
||||
|
||||
static bool add_diag_root(mount_diag_roots_t *roots, const char *path) {
|
||||
if (!path || path[0] == '\0')
|
||||
return false;
|
||||
for (size_t i = 0; i < roots->count; ++i) {
|
||||
if (strcmp(roots->paths[i], path) == 0)
|
||||
return true;
|
||||
}
|
||||
if (roots->count >= MOUNT_DIAG_MAX_ROOTS)
|
||||
return false;
|
||||
(void)strlcpy(roots->paths[roots->count++], path, MAX_PATH);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool path_matches_diag_roots(const mount_diag_roots_t *roots,
|
||||
const char *path) {
|
||||
for (size_t i = 0; i < roots->count; ++i) {
|
||||
if (path_matches_root_or_child(path, roots->paths[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *normalized_mount_source(const char *source) {
|
||||
return strncmp(source, "<above>:", 8) == 0 ? source + 8 : source;
|
||||
}
|
||||
|
||||
static void collect_mount_dependencies(const char *mount_point,
|
||||
mount_diag_roots_t *roots,
|
||||
size_t *dependency_count_out) {
|
||||
(void)add_diag_root(roots, mount_point);
|
||||
|
||||
struct statfs *mounts = NULL;
|
||||
int mount_count = getmntinfo(&mounts, MNT_NOWAIT);
|
||||
if (mount_count <= 0 || !mounts) {
|
||||
log_debug(" [MOUNT-BUSY] mount table unavailable for %s: %s",
|
||||
mount_point, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
size_t dependency_count = 0;
|
||||
for (int i = 0; i < mount_count; ++i) {
|
||||
const char *source = normalized_mount_source(mounts[i].f_mntfromname);
|
||||
const char *target = mounts[i].f_mntonname;
|
||||
bool consumes_mount = path_matches_root_or_child(source, mount_point);
|
||||
bool child_mount = strcmp(target, mount_point) != 0 &&
|
||||
path_matches_root_or_child(target, mount_point);
|
||||
if (!consumes_mount && !child_mount)
|
||||
continue;
|
||||
|
||||
dependency_count++;
|
||||
log_debug(" [MOUNT-BUSY] dependent type=%s from=%s on=%s flags=0x%lX",
|
||||
mounts[i].f_fstypename, source, target,
|
||||
(unsigned long)mounts[i].f_flags);
|
||||
if (consumes_mount)
|
||||
(void)add_diag_root(roots, target);
|
||||
}
|
||||
*dependency_count_out = dependency_count;
|
||||
}
|
||||
|
||||
static void log_process_vm_refs(pid_t pid, const char *name,
|
||||
const mount_diag_roots_t *roots,
|
||||
size_t *hit_count) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, pid};
|
||||
size_t data_size = 0;
|
||||
uint8_t *data = read_sysctl_snapshot(mib, 4, &data_size);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
uint8_t *cursor = data;
|
||||
uint8_t *end = data + data_size;
|
||||
while ((size_t)(end - cursor) >= sizeof(int)) {
|
||||
int record_size = 0;
|
||||
memcpy(&record_size, cursor, sizeof(record_size));
|
||||
if (record_size <= 0 || (size_t)record_size > (size_t)(end - cursor))
|
||||
break;
|
||||
|
||||
const struct kinfo_vmentry *entry =
|
||||
(const struct kinfo_vmentry *)cursor;
|
||||
if ((size_t)record_size > offsetof(struct kinfo_vmentry, kve_path) &&
|
||||
entry->kve_type == KVME_TYPE_VNODE) {
|
||||
char path[MAX_PATH];
|
||||
if (copy_record_path(path, cursor, (size_t)record_size,
|
||||
offsetof(struct kinfo_vmentry, kve_path)) &&
|
||||
path_matches_diag_roots(roots, path)) {
|
||||
if (*hit_count < MOUNT_DIAG_MAX_VM_HITS) {
|
||||
log_debug(" [MOUNT-BUSY] pid=%ld name=%s vm=0x%llX-0x%llX "
|
||||
"prot=0x%X path=%s",
|
||||
(long)pid, name, (unsigned long long)entry->kve_start,
|
||||
(unsigned long long)entry->kve_end,
|
||||
entry->kve_protection, path);
|
||||
}
|
||||
(*hit_count)++;
|
||||
}
|
||||
}
|
||||
cursor += record_size;
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void log_all_process_vm_refs(const mount_diag_roots_t *roots,
|
||||
size_t *hit_count_out) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0};
|
||||
size_t data_size = 0;
|
||||
uint8_t *data = read_sysctl_snapshot(mib, 4, &data_size);
|
||||
if (!data) {
|
||||
log_debug(" [MOUNT-BUSY] process list unavailable: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
size_t hit_count = 0;
|
||||
uint8_t *cursor = data;
|
||||
uint8_t *end = data + data_size;
|
||||
while ((size_t)(end - cursor) >= sizeof(int)) {
|
||||
int record_size = 0;
|
||||
memcpy(&record_size, cursor, sizeof(record_size));
|
||||
if (record_size <= 0 || (size_t)record_size > (size_t)(end - cursor))
|
||||
break;
|
||||
if ((size_t)record_size <= offsetof(struct kinfo_proc, ki_tdname))
|
||||
break;
|
||||
|
||||
const struct kinfo_proc *process = (const struct kinfo_proc *)cursor;
|
||||
char process_name[TDNAMLEN + 1];
|
||||
copy_process_name(process_name, cursor, (size_t)record_size);
|
||||
const char *name = process_name[0] ? process_name : "?";
|
||||
if (process->ki_pid > 0)
|
||||
log_process_vm_refs(process->ki_pid, name, roots, &hit_count);
|
||||
cursor += record_size;
|
||||
}
|
||||
|
||||
free(data);
|
||||
*hit_count_out = hit_count;
|
||||
if (hit_count > MOUNT_DIAG_MAX_VM_HITS) {
|
||||
log_debug(" [MOUNT-BUSY] %zu additional VM references suppressed",
|
||||
hit_count - MOUNT_DIAG_MAX_VM_HITS);
|
||||
}
|
||||
}
|
||||
|
||||
static void log_vnode_refs(const char *mount_point, size_t *hit_count_out) {
|
||||
struct stat root_stat;
|
||||
if (stat(mount_point, &root_stat) != 0)
|
||||
return;
|
||||
|
||||
int mib[2] = {CTL_KERN, KERN_VNODE};
|
||||
size_t data_size = 0;
|
||||
uint8_t *data = read_sysctl_snapshot(mib, 2, &data_size);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
void *mount_address = NULL;
|
||||
uint8_t *cursor = data;
|
||||
uint8_t *end = data + data_size;
|
||||
while ((size_t)(end - cursor) >= sizeof(size_t)) {
|
||||
const struct xvnode *vnode = (const struct xvnode *)cursor;
|
||||
if (vnode->xv_size < sizeof(struct xvnode) ||
|
||||
vnode->xv_size > (size_t)(end - cursor)) {
|
||||
break;
|
||||
}
|
||||
if (vnode->xv_dev == root_stat.st_dev &&
|
||||
vnode->xv_ino == root_stat.st_ino) {
|
||||
mount_address = vnode->xv_mount;
|
||||
break;
|
||||
}
|
||||
cursor += vnode->xv_size;
|
||||
}
|
||||
|
||||
size_t hit_count = 0;
|
||||
size_t hold_only_count = 0;
|
||||
if (mount_address) {
|
||||
cursor = data;
|
||||
while ((size_t)(end - cursor) >= sizeof(size_t)) {
|
||||
const struct xvnode *vnode = (const struct xvnode *)cursor;
|
||||
if (vnode->xv_size < sizeof(struct xvnode) ||
|
||||
vnode->xv_size > (size_t)(end - cursor)) {
|
||||
break;
|
||||
}
|
||||
if (vnode->xv_mount == mount_address) {
|
||||
bool active = vnode->xv_usecount > 0 || vnode->xv_writecount > 0 ||
|
||||
vnode->xv_numoutput > 0;
|
||||
if (active) {
|
||||
if (hit_count < MOUNT_DIAG_MAX_VNODE_HITS) {
|
||||
log_debug(" [MOUNT-BUSY] vnode=%p dev=0x%llX ino=0x%llX "
|
||||
"use=%d hold=%d write=%d output=%ld",
|
||||
vnode->xv_vnode, (unsigned long long)vnode->xv_dev,
|
||||
(unsigned long long)vnode->xv_ino, vnode->xv_usecount,
|
||||
vnode->xv_holdcnt, vnode->xv_writecount,
|
||||
vnode->xv_numoutput);
|
||||
}
|
||||
hit_count++;
|
||||
} else if (vnode->xv_holdcnt > 0) {
|
||||
hold_only_count++;
|
||||
}
|
||||
}
|
||||
cursor += vnode->xv_size;
|
||||
}
|
||||
}
|
||||
|
||||
free(data);
|
||||
*hit_count_out = hit_count;
|
||||
if (hit_count > MOUNT_DIAG_MAX_VNODE_HITS) {
|
||||
log_debug(" [MOUNT-BUSY] %zu additional active vnodes suppressed",
|
||||
hit_count - MOUNT_DIAG_MAX_VNODE_HITS);
|
||||
}
|
||||
if (hold_only_count > 0) {
|
||||
log_debug(" [MOUNT-BUSY] hold-only cached vnodes=%zu", hold_only_count);
|
||||
}
|
||||
}
|
||||
|
||||
void sm_mount_diag_log_busy(const char *mount_point) {
|
||||
if (!mount_point || mount_point[0] == '\0')
|
||||
return;
|
||||
|
||||
int saved_errno = errno;
|
||||
log_debug(" [MOUNT-BUSY] diagnostic begin: %s", mount_point);
|
||||
|
||||
mount_diag_roots_t roots;
|
||||
memset(&roots, 0, sizeof(roots));
|
||||
size_t dependency_count = 0;
|
||||
collect_mount_dependencies(mount_point, &roots, &dependency_count);
|
||||
|
||||
size_t vm_hit_count = 0;
|
||||
log_all_process_vm_refs(&roots, &vm_hit_count);
|
||||
|
||||
size_t vnode_hit_count = 0;
|
||||
log_vnode_refs(mount_point, &vnode_hit_count);
|
||||
|
||||
if (dependency_count == 0 && vm_hit_count == 0 &&
|
||||
vnode_hit_count == 0) {
|
||||
log_debug(" [MOUNT-BUSY] no user-visible owner found; likely transient "
|
||||
"VFS, buffer-cache, or backing-device reference");
|
||||
}
|
||||
log_debug(" [MOUNT-BUSY] diagnostic end: dependencies=%zu vm_refs=%zu "
|
||||
"active_vnodes=%zu",
|
||||
dependency_count, vm_hit_count, vnode_hit_count);
|
||||
errno = saved_errno;
|
||||
}
|
||||
@@ -572,6 +572,12 @@ static sm_scan_tree_dir_visit_t register_watch_directory_visit(
|
||||
const char *dir_path, unsigned int depth_from_root, void *ctx_ptr) {
|
||||
register_watch_tree_ctx_t *ctx = (register_watch_tree_ctx_t *)ctx_ptr;
|
||||
if (depth_from_root > 0u) {
|
||||
// Runtime image descendants can be active PFS/PFSC mounts. Keeping a
|
||||
// directory fd open there pins the mounted vnode and makes unmount return
|
||||
// EBUSY. The managed mount-base watcher is sufficient: mounted images are
|
||||
// read-only and every discovery pass rebuilds this tree.
|
||||
if (is_under_image_mount_base(dir_path))
|
||||
return SM_SCAN_TREE_DIR_SKIP_DESCEND;
|
||||
if (depth_from_root >= ctx->scan_depth ||
|
||||
directory_has_param_json(dir_path, NULL)) {
|
||||
return SM_SCAN_TREE_DIR_SKIP_DESCEND;
|
||||
|
||||
Reference in new issue
Block a user