introduce __fcheck_files() to fix rcu_dereference_check_fdtable(), kill rcu_my_thread_group_empty()
rcu_dereference_check_fdtable() looks very wrong,
1. rcu_my_thread_group_empty() was added by 844b9a8707
"vfs: fix
RCU-lockdep false positive due to /proc" but it doesn't really
fix the problem. A CLONE_THREAD (without CLONE_FILES) task can
hit the same race with get_files_struct().
And otoh rcu_my_thread_group_empty() can suppress the correct
warning if the caller is the CLONE_FILES (without CLONE_THREAD)
task.
2. files->count == 1 check is not really right too. Even if this
files_struct is not shared it is not safe to access it lockless
unless the caller is the owner.
Otoh, this check is sub-optimal. files->count == 0 always means
it is safe to use it lockless even if files != current->files,
but put_files_struct() has to take rcu_read_lock(). See the next
patch.
This patch removes the buggy checks and turns fcheck_files() into
__fcheck_files() which uses rcu_dereference_raw(), the "unshared"
callers, fget_light() and fget_raw_light(), can use it to avoid
the warning from RCU-lockdep.
fcheck_files() is trivially reimplemented as rcu_lockdep_assert()
plus __fcheck_files().
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
parent
2ccdc41319
commit
a8d4b8345e
4 changed files with 23 additions and 29 deletions
|
@ -707,7 +707,7 @@ struct file *fget_light(unsigned int fd, int *fput_needed)
|
||||||
|
|
||||||
*fput_needed = 0;
|
*fput_needed = 0;
|
||||||
if (atomic_read(&files->count) == 1) {
|
if (atomic_read(&files->count) == 1) {
|
||||||
file = fcheck_files(files, fd);
|
file = __fcheck_files(files, fd);
|
||||||
if (file && (file->f_mode & FMODE_PATH))
|
if (file && (file->f_mode & FMODE_PATH))
|
||||||
file = NULL;
|
file = NULL;
|
||||||
} else {
|
} else {
|
||||||
|
@ -735,7 +735,7 @@ struct file *fget_raw_light(unsigned int fd, int *fput_needed)
|
||||||
|
|
||||||
*fput_needed = 0;
|
*fput_needed = 0;
|
||||||
if (atomic_read(&files->count) == 1) {
|
if (atomic_read(&files->count) == 1) {
|
||||||
file = fcheck_files(files, fd);
|
file = __fcheck_files(files, fd);
|
||||||
} else {
|
} else {
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
file = fcheck_files(files, fd);
|
file = fcheck_files(files, fd);
|
||||||
|
|
|
@ -59,29 +59,36 @@ struct files_struct {
|
||||||
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
|
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
|
||||||
};
|
};
|
||||||
|
|
||||||
#define rcu_dereference_check_fdtable(files, fdtfd) \
|
|
||||||
(rcu_dereference_check((fdtfd), \
|
|
||||||
lockdep_is_held(&(files)->file_lock) || \
|
|
||||||
atomic_read(&(files)->count) == 1 || \
|
|
||||||
rcu_my_thread_group_empty()))
|
|
||||||
|
|
||||||
#define files_fdtable(files) \
|
|
||||||
(rcu_dereference_check_fdtable((files), (files)->fdt))
|
|
||||||
|
|
||||||
struct file_operations;
|
struct file_operations;
|
||||||
struct vfsmount;
|
struct vfsmount;
|
||||||
struct dentry;
|
struct dentry;
|
||||||
|
|
||||||
extern void __init files_defer_init(void);
|
extern void __init files_defer_init(void);
|
||||||
|
|
||||||
static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
|
#define rcu_dereference_check_fdtable(files, fdtfd) \
|
||||||
|
rcu_dereference_check((fdtfd), lockdep_is_held(&(files)->file_lock))
|
||||||
|
|
||||||
|
#define files_fdtable(files) \
|
||||||
|
rcu_dereference_check_fdtable((files), (files)->fdt)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The caller must ensure that fd table isn't shared or hold rcu or file lock
|
||||||
|
*/
|
||||||
|
static inline struct file *__fcheck_files(struct files_struct *files, unsigned int fd)
|
||||||
{
|
{
|
||||||
struct file * file = NULL;
|
struct fdtable *fdt = rcu_dereference_raw(files->fdt);
|
||||||
struct fdtable *fdt = files_fdtable(files);
|
|
||||||
|
|
||||||
if (fd < fdt->max_fds)
|
if (fd < fdt->max_fds)
|
||||||
file = rcu_dereference_check_fdtable(files, fdt->fd[fd]);
|
return rcu_dereference_raw(fdt->fd[fd]);
|
||||||
return file;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct file *fcheck_files(struct files_struct *files, unsigned int fd)
|
||||||
|
{
|
||||||
|
rcu_lockdep_assert(rcu_read_lock_held() ||
|
||||||
|
lockdep_is_held(&files->file_lock),
|
||||||
|
"suspicious rcu_dereference_check() usage");
|
||||||
|
return __fcheck_files(files, fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -448,8 +448,6 @@ static inline int rcu_read_lock_sched_held(void)
|
||||||
|
|
||||||
#ifdef CONFIG_PROVE_RCU
|
#ifdef CONFIG_PROVE_RCU
|
||||||
|
|
||||||
extern int rcu_my_thread_group_empty(void);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rcu_lockdep_assert - emit lockdep splat if specified condition not met
|
* rcu_lockdep_assert - emit lockdep splat if specified condition not met
|
||||||
* @c: condition to check
|
* @c: condition to check
|
||||||
|
|
|
@ -195,17 +195,6 @@ void wait_rcu_gp(call_rcu_func_t crf)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(wait_rcu_gp);
|
EXPORT_SYMBOL_GPL(wait_rcu_gp);
|
||||||
|
|
||||||
#ifdef CONFIG_PROVE_RCU
|
|
||||||
/*
|
|
||||||
* wrapper function to avoid #include problems.
|
|
||||||
*/
|
|
||||||
int rcu_my_thread_group_empty(void)
|
|
||||||
{
|
|
||||||
return thread_group_empty(current);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(rcu_my_thread_group_empty);
|
|
||||||
#endif /* #ifdef CONFIG_PROVE_RCU */
|
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD
|
#ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD
|
||||||
static inline void debug_init_rcu_head(struct rcu_head *head)
|
static inline void debug_init_rcu_head(struct rcu_head *head)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue