vfs: Add support to debug umount failures

When umount of a partition fails with EBUSY there is
no indication as to what is keeping the mount point busy.

Add support to print a kernel log showing what files
are open on this mount point.

Also add a new new config option CONFIG_FILE_TABLE_DEBUG to
enable this feature.

Change-Id: Id7a3f5e7291b22ffd0f265848ec0a9757f713561
Signed-off-by: Nikhilesh Reddy <reddyn@codeaurora.org>
Signed-off-by: Ankit Jain <ankijain@codeaurora.org>
tirimbino
Nikhilesh Reddy 9 years ago committed by Ritesh Harjani
parent b6072a3c8e
commit ececbb5282
  1. 5
      fs/Kconfig
  2. 137
      fs/file_table.c
  3. 26
      fs/internal.h
  4. 2
      fs/namei.c
  5. 2
      fs/namespace.c
  6. 3
      include/linux/fs.h

@ -309,4 +309,9 @@ endif # NETWORK_FILESYSTEMS
source "fs/nls/Kconfig"
source "fs/dlm/Kconfig"
config FILE_TABLE_DEBUG
bool "Enable FILE_TABLE_DEBUG"
help
This option enables debug of the open files using a global filetable
endmenu

@ -42,6 +42,141 @@ static struct kmem_cache *filp_cachep __read_mostly;
static struct percpu_counter nr_files __cacheline_aligned_in_smp;
#ifdef CONFIG_FILE_TABLE_DEBUG
#include <linux/hashtable.h>
#include <mount.h>
static DEFINE_MUTEX(global_files_lock);
static DEFINE_HASHTABLE(global_files_hashtable, 10);
struct global_filetable_lookup_key {
struct work_struct work;
uintptr_t value;
};
void global_filetable_print_warning_once(void)
{
pr_err_once("\n**********************************************************\n");
pr_err_once("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n");
pr_err_once("** **\n");
pr_err_once("** VFS FILE TABLE DEBUG is enabled . **\n");
pr_err_once("** Allocating extra memory and slowing access to files **\n");
pr_err_once("** **\n");
pr_err_once("** This means that this is a DEBUG kernel and it is **\n");
pr_err_once("** unsafe for production use. **\n");
pr_err_once("** **\n");
pr_err_once("** If you see this message and you are not debugging **\n");
pr_err_once("** the kernel, report this immediately to your vendor! **\n");
pr_err_once("** **\n");
pr_err_once("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n");
pr_err_once("**********************************************************\n");
}
void global_filetable_add(struct file *filp)
{
struct mount *mnt;
if (filp->f_path.dentry->d_iname == NULL ||
strlen(filp->f_path.dentry->d_iname) == 0)
return;
mnt = real_mount(filp->f_path.mnt);
mutex_lock(&global_files_lock);
hash_add(global_files_hashtable, &filp->f_hash, (uintptr_t)mnt);
mutex_unlock(&global_files_lock);
}
void global_filetable_del(struct file *filp)
{
mutex_lock(&global_files_lock);
hash_del(&filp->f_hash);
mutex_unlock(&global_files_lock);
}
static void global_print_file(struct file *filp, char *path_buffer, int *count)
{
char *pathname;
pathname = d_path(&filp->f_path, path_buffer, PAGE_SIZE);
if (IS_ERR(pathname))
pr_err("VFS: File %d Address : %pa partial filename: %s ref_count=%ld\n",
++(*count), &filp, filp->f_path.dentry->d_iname,
atomic_long_read(&filp->f_count));
else
pr_err("VFS: File %d Address : %pa full filepath: %s ref_count=%ld\n",
++(*count), &filp, pathname,
atomic_long_read(&filp->f_count));
}
static void global_filetable_print(uintptr_t lookup_mnt)
{
struct hlist_node *tmp;
struct file *filp;
struct mount *mnt;
int index;
int count = 0;
char *path_buffer = (char *)__get_free_page(GFP_KERNEL);
mutex_lock(&global_files_lock);
pr_err("\n**********************************************************\n");
pr_err("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n");
pr_err("\n");
pr_err("VFS: The following files hold a reference to the mount\n");
pr_err("\n");
hash_for_each_possible_safe(global_files_hashtable, filp, tmp, f_hash,
lookup_mnt) {
mnt = real_mount(filp->f_path.mnt);
if ((uintptr_t)mnt == lookup_mnt)
global_print_file(filp, path_buffer, &count);
}
pr_err("\n");
pr_err("VFS: Found total of %d open files\n", count);
pr_err("\n");
count = 0;
pr_err("\n");
pr_err("VFS: The following files need to cleaned up\n");
pr_err("\n");
hash_for_each_safe(global_files_hashtable, index, tmp, filp, f_hash) {
if (atomic_long_read(&filp->f_count) == 0)
global_print_file(filp, path_buffer, &count);
}
pr_err("\n");
pr_err("VFS: Found total of %d files awaiting clean-up\n", count);
pr_err("\n");
pr_err("** NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE **\n");
pr_err("\n**********************************************************\n");
mutex_unlock(&global_files_lock);
free_page((unsigned long)path_buffer);
}
static void global_filetable_print_work_fn(struct work_struct *work)
{
struct global_filetable_lookup_key *key;
uintptr_t lookup_mnt;
key = container_of(work, struct global_filetable_lookup_key, work);
lookup_mnt = key->value;
kfree(key);
global_filetable_print(lookup_mnt);
}
void global_filetable_delayed_print(struct mount *mnt)
{
struct global_filetable_lookup_key *key;
key = kzalloc(sizeof(*key), GFP_KERNEL);
if (key == NULL)
return;
key->value = (uintptr_t)mnt;
INIT_WORK(&key->work, global_filetable_print_work_fn);
schedule_work(&key->work);
}
#endif /* CONFIG_FILE_TABLE_DEBUG */
static void file_free_rcu(struct rcu_head *head)
{
struct file *f = container_of(head, struct file, f_u.fu_rcuhead);
@ -221,6 +356,7 @@ static void __fput(struct file *file)
put_write_access(inode);
__mnt_drop_write(mnt);
}
global_filetable_del(file);
file->f_path.dentry = NULL;
file->f_path.mnt = NULL;
file->f_inode = NULL;
@ -320,6 +456,7 @@ void __init files_init(void)
filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
percpu_counter_init(&nr_files, 0, GFP_KERNEL);
global_filetable_print_warning_once();
}
/*

@ -186,3 +186,29 @@ loff_t iomap_apply(struct inode *inode, loff_t pos, loff_t length,
/* direct-io.c: */
int sb_init_dio_done_wq(struct super_block *sb);
#ifdef CONFIG_FILE_TABLE_DEBUG
void global_filetable_print_warning_once(void);
void global_filetable_add(struct file *filp);
void global_filetable_del(struct file *filp);
void global_filetable_delayed_print(struct mount *mnt);
#else /* i.e NOT CONFIG_FILE_TABLE_DEBUG */
static inline void global_filetable_print_warning_once(void)
{
}
static inline void global_filetable_add(struct file *filp)
{
}
static inline void global_filetable_del(struct file *filp)
{
}
static inline void global_filetable_delayed_print(struct mount *mnt)
{
}
#endif /* CONFIG_FILE_TABLE_DEBUG */

@ -3573,6 +3573,8 @@ out2:
error = -ESTALE;
}
file = ERR_PTR(error);
} else {
global_filetable_add(file);
}
return file;
}

@ -1668,6 +1668,8 @@ static int do_umount(struct mount *mnt, int flags)
}
unlock_mount_hash();
namespace_unlock();
if (retval == -EBUSY)
global_filetable_delayed_print(mnt);
return retval;
}

@ -887,6 +887,9 @@ struct file {
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
#ifdef CONFIG_FILE_TABLE_DEBUG
struct hlist_node f_hash;
#endif /* #ifdef CONFIG_FILE_TABLE_DEBUG */
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

Loading…
Cancel
Save