Add the ability for a subsystem to add a user-friendly symlink that points to a ref_tracker_dir's debugfs file. Add a separate debugfs_symlinks xarray and use that to track symlinks. The reaper workqueue job will remove symlinks before their corresponding dentries.
Signed-off-by: Jeff Layton <jlay...@kernel.org> --- include/linux/ref_tracker.h | 11 +++++++++++ lib/ref_tracker.c | 47 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/include/linux/ref_tracker.h b/include/linux/ref_tracker.h index 28bbf436a8f4646cfac181d618195a9460bda196..e1323de93bf6b891aa14ad8d9b4b28d02e10f9f7 100644 --- a/include/linux/ref_tracker.h +++ b/include/linux/ref_tracker.h @@ -29,6 +29,7 @@ struct ref_tracker_dir { #ifdef CONFIG_DEBUG_FS void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir); +void ref_tracker_dir_symlink(struct ref_tracker_dir *dir, const char *fmt, ...); #else /* CONFIG_DEBUG_FS */ @@ -36,6 +37,11 @@ static inline void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir) { } +static inline __ostream_printf +void ref_tracker_dir_symlink(struct ref_tracker_dir *dir, const char *fmt, ...) +{ +} + #endif /* CONFIG_DEBUG_FS */ static inline void ref_tracker_dir_init(struct ref_tracker_dir *dir, @@ -85,6 +91,11 @@ static inline void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir) { } +static inline __ostream_printf +void ref_tracker_dir_symlink(struct ref_tracker_dir *dir, const char *fmt, ...) +{ +} + static inline void ref_tracker_dir_exit(struct ref_tracker_dir *dir) { } diff --git a/lib/ref_tracker.c b/lib/ref_tracker.c index 4f1c4fe5e6b1274b6aeb733a3f8c06d1dff003f5..a78741308a2c2dc7aff65519970ad081b59e0de5 100644 --- a/lib/ref_tracker.c +++ b/lib/ref_tracker.c @@ -44,11 +44,13 @@ struct ref_tracker_dir_stats { * dentries asynchronously. */ static struct xarray debugfs_dentries; +static struct xarray debugfs_symlinks; static struct work_struct debugfs_reap_worker; #define REF_TRACKER_DIR_DEAD XA_MARK_0 static inline void ref_tracker_debugfs_mark(struct ref_tracker_dir *dir) { + xa_set_mark(&debugfs_symlinks, (unsigned long)dir, REF_TRACKER_DIR_DEAD); xa_set_mark(&debugfs_dentries, (unsigned long)dir, REF_TRACKER_DIR_DEAD); schedule_work(&debugfs_reap_worker); } @@ -446,6 +448,45 @@ void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir) } EXPORT_SYMBOL(ref_tracker_dir_debugfs); +void __ostream_printf ref_tracker_dir_symlink(struct ref_tracker_dir *dir, const char *fmt, ...) +{ + char name[NAME_MAX + 1]; + struct dentry *symlink, *dentry; + va_list args; + int ret; + + symlink = xa_load(&debugfs_symlinks, (unsigned long)dir); + dentry = xa_load(&debugfs_dentries, (unsigned long)dir); + + /* Already created?*/ + if (symlink && !xa_is_err(symlink)) + return; + + if (!dentry || xa_is_err(dentry)) + return; + + va_start(args, fmt); + ret = vsnprintf(name, sizeof(name), fmt, args); + va_end(args); + name[sizeof(name) - 1] = '\0'; + + if (ret < sizeof(name)) { + symlink = debugfs_create_symlink(name, ref_tracker_debug_dir, + dentry->d_name.name); + if (!IS_ERR(symlink)) { + void *old; + + old = xa_store(&debugfs_symlinks, (unsigned long)dir, + symlink, GFP_KERNEL); + if (xa_is_err(old)) + debugfs_remove(symlink); + else + WARN_ON_ONCE(old); + } + } +} +EXPORT_SYMBOL(ref_tracker_dir_symlink); + static void debugfs_reap_work(struct work_struct *work) { struct dentry *dentry; @@ -454,6 +495,11 @@ static void debugfs_reap_work(struct work_struct *work) do { reaped = false; + xa_for_each_marked(&debugfs_symlinks, index, dentry, REF_TRACKER_DIR_DEAD) { + xa_erase(&debugfs_symlinks, index); + debugfs_remove(dentry); + reaped = true; + } xa_for_each_marked(&debugfs_dentries, index, dentry, REF_TRACKER_DIR_DEAD) { xa_erase(&debugfs_dentries, index); debugfs_remove(dentry); @@ -466,6 +512,7 @@ static int __init ref_tracker_debugfs_init(void) { INIT_WORK(&debugfs_reap_worker, debugfs_reap_work); xa_init_flags(&debugfs_dentries, XA_FLAGS_LOCK_IRQ); + xa_init_flags(&debugfs_symlinks, XA_FLAGS_LOCK_IRQ); ref_tracker_debug_dir = debugfs_create_dir("ref_tracker", NULL); return 0; } -- 2.49.0