diff options
Diffstat (limited to 'fs/file_table.c')
-rw-r--r-- | fs/file_table.c | 129 |
1 files changed, 127 insertions, 2 deletions
diff --git a/fs/file_table.c b/fs/file_table.c index 5fff9030be34..e900ca518635 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -36,6 +36,8 @@ struct files_stat_struct files_stat = { .max_files = NR_FILE }; +DEFINE_STATIC_LGLOCK(files_lglock); + /* SLAB cache for file structures */ static struct kmem_cache *filp_cachep __read_mostly; @@ -132,6 +134,7 @@ struct file *get_empty_filp(void) return ERR_PTR(error); } + INIT_LIST_HEAD(&f->f_u.fu_list); atomic_long_set(&f->f_count, 1); rwlock_init(&f->f_owner.lock); spin_lock_init(&f->f_lock); @@ -237,11 +240,11 @@ static void __fput(struct file *file) locks_remove_flock(file); if (unlikely(file->f_flags & FASYNC)) { - if (file->f_op->fasync) + if (file->f_op && file->f_op->fasync) file->f_op->fasync(-1, file, 0); } ima_file_free(file); - if (file->f_op->release) + if (file->f_op && file->f_op->release) file->f_op->release(inode, file); security_file_free(file); if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL && @@ -301,6 +304,7 @@ void fput(struct file *file) if (atomic_long_dec_and_test(&file->f_count)) { struct task_struct *task = current; + file_sb_list_del(file); if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) { init_task_work(&file->f_u.fu_rcuhead, ____fput); if (!task_work_add(task, &file->f_u.fu_rcuhead, true)) @@ -329,6 +333,7 @@ void __fput_sync(struct file *file) { if (atomic_long_dec_and_test(&file->f_count)) { struct task_struct *task = current; + file_sb_list_del(file); BUG_ON(!(task->flags & PF_KTHREAD)); __fput(file); } @@ -340,10 +345,129 @@ void put_filp(struct file *file) { if (atomic_long_dec_and_test(&file->f_count)) { security_file_free(file); + file_sb_list_del(file); file_free(file); } } +static inline int file_list_cpu(struct file *file) +{ +#ifdef CONFIG_SMP + return file->f_sb_list_cpu; +#else + return smp_processor_id(); +#endif +} + +/* helper for file_sb_list_add to reduce ifdefs */ +static inline void __file_sb_list_add(struct file *file, struct super_block *sb) +{ + struct list_head *list; +#ifdef CONFIG_SMP + int cpu; + cpu = smp_processor_id(); + file->f_sb_list_cpu = cpu; + list = per_cpu_ptr(sb->s_files, cpu); +#else + list = &sb->s_files; +#endif + list_add(&file->f_u.fu_list, list); +} + +/** + * file_sb_list_add - add a file to the sb's file list + * @file: file to add + * @sb: sb to add it to + * + * Use this function to associate a file with the superblock of the inode it + * refers to. + */ +void file_sb_list_add(struct file *file, struct super_block *sb) +{ + if (likely(!(file->f_mode & FMODE_WRITE))) + return; + if (!S_ISREG(file_inode(file)->i_mode)) + return; + lg_local_lock(&files_lglock); + __file_sb_list_add(file, sb); + lg_local_unlock(&files_lglock); +} + +/** + * file_sb_list_del - remove a file from the sb's file list + * @file: file to remove + * @sb: sb to remove it from + * + * Use this function to remove a file from its superblock. + */ +void file_sb_list_del(struct file *file) +{ + if (!list_empty(&file->f_u.fu_list)) { + lg_local_lock_cpu(&files_lglock, file_list_cpu(file)); + list_del_init(&file->f_u.fu_list); + lg_local_unlock_cpu(&files_lglock, file_list_cpu(file)); + } +} + +#ifdef CONFIG_SMP + +/* + * These macros iterate all files on all CPUs for a given superblock. + * files_lglock must be held globally. + */ +#define do_file_list_for_each_entry(__sb, __file) \ +{ \ + int i; \ + for_each_possible_cpu(i) { \ + struct list_head *list; \ + list = per_cpu_ptr((__sb)->s_files, i); \ + list_for_each_entry((__file), list, f_u.fu_list) + +#define while_file_list_for_each_entry \ + } \ +} + +#else + +#define do_file_list_for_each_entry(__sb, __file) \ +{ \ + struct list_head *list; \ + list = &(sb)->s_files; \ + list_for_each_entry((__file), list, f_u.fu_list) + +#define while_file_list_for_each_entry \ +} + +#endif + +/** + * mark_files_ro - mark all files read-only + * @sb: superblock in question + * + * All files are marked read-only. We don't care about pending + * delete files so this should be used in 'force' mode only. + */ +void mark_files_ro(struct super_block *sb) +{ + struct file *f; + + lg_global_lock(&files_lglock); + do_file_list_for_each_entry(sb, f) { + if (!file_count(f)) + continue; + if (!(f->f_mode & FMODE_WRITE)) + continue; + spin_lock(&f->f_lock); + f->f_mode &= ~FMODE_WRITE; + spin_unlock(&f->f_lock); + if (file_check_writeable(f) != 0) + continue; + __mnt_drop_write(f->f_path.mnt); + file_release_write(f); + } while_file_list_for_each_entry; + lg_global_unlock(&files_lglock); +} + void __init files_init(unsigned long mempages) { unsigned long n; @@ -359,5 +483,6 @@ void __init files_init(unsigned long mempages) n = (mempages * (PAGE_SIZE / 1024)) / 10; files_stat.max_files = max_t(unsigned long, n, NR_FILE); files_defer_init(); + lg_lock_init(&files_lglock, "files_lglock"); percpu_counter_init(&nr_files, 0); } |