diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2013-04-03 19:57:00 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2013-04-09 15:16:51 -0400 |
commit | ca469f35a8e9ef12571a4b80ac6d7fdc0260fb44 (patch) | |
tree | 228daeec1f54db72c32d64bf8b54413c65d0ab30 /fs/proc | |
parent | 866ad9a747bbf5461739fcae6d0a41c8971bbe1d (diff) |
deal with races between remove_proc_entry() and proc_reg_release()
* serialize the call of ->release() on per-pdeo mutex
* don't remove pdeo from per-pde list until we are through with it
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/proc')
-rw-r--r-- | fs/proc/inode.c | 85 | ||||
-rw-r--r-- | fs/proc/internal.h | 2 |
2 files changed, 34 insertions, 53 deletions
diff --git a/fs/proc/inode.c b/fs/proc/inode.c index 0cd9d80f28e8..b5b204d6b07f 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -156,6 +156,29 @@ static void unuse_pde(struct proc_dir_entry *pde) spin_unlock(&pde->pde_unload_lock); } +/* pde is locked */ +static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo) +{ + pdeo->count++; + if (!mutex_trylock(&pdeo->mutex)) { + /* somebody else is doing that, just wait */ + spin_unlock(&pde->pde_unload_lock); + mutex_lock(&pdeo->mutex); + spin_lock(&pde->pde_unload_lock); + WARN_ON(!list_empty(&pdeo->lh)); + } else { + struct file *file; + spin_unlock(&pde->pde_unload_lock); + file = pdeo->file; + pde->proc_fops->release(file_inode(file), file); + spin_lock(&pde->pde_unload_lock); + list_del_init(&pdeo->lh); + } + mutex_unlock(&pdeo->mutex); + if (!--pdeo->count) + kfree(pdeo); +} + void proc_entry_rundown(struct proc_dir_entry *de) { spin_lock(&de->pde_unload_lock); @@ -173,15 +196,8 @@ void proc_entry_rundown(struct proc_dir_entry *de) while (!list_empty(&de->pde_openers)) { struct pde_opener *pdeo; - struct file *file; - pdeo = list_first_entry(&de->pde_openers, struct pde_opener, lh); - list_del(&pdeo->lh); - spin_unlock(&de->pde_unload_lock); - file = pdeo->file; - de->proc_fops->release(file_inode(file), file); - kfree(pdeo); - spin_lock(&de->pde_unload_lock); + close_pdeo(de, pdeo); } spin_unlock(&de->pde_unload_lock); } @@ -357,6 +373,8 @@ static int proc_reg_open(struct inode *inode, struct file *file) spin_lock(&pde->pde_unload_lock); if (rv == 0 && release) { /* To know what to release. */ + mutex_init(&pdeo->mutex); + pdeo->count = 0; pdeo->file = file; /* Strictly for "too late" ->release in proc_reg_release(). */ list_add(&pdeo->lh, &pde->pde_openers); @@ -367,58 +385,19 @@ static int proc_reg_open(struct inode *inode, struct file *file) return rv; } -static struct pde_opener *find_pde_opener(struct proc_dir_entry *pde, - struct file *file) -{ - struct pde_opener *pdeo; - - list_for_each_entry(pdeo, &pde->pde_openers, lh) { - if (pdeo->file == file) - return pdeo; - } - return NULL; -} - static int proc_reg_release(struct inode *inode, struct file *file) { struct proc_dir_entry *pde = PDE(inode); - int rv = 0; - int (*release)(struct inode *, struct file *); struct pde_opener *pdeo; - spin_lock(&pde->pde_unload_lock); - pdeo = find_pde_opener(pde, file); - if (pde->pde_users < 0) { - /* - * Can't simply exit, __fput() will think that everything is OK, - * and move on to freeing struct file. remove_proc_entry() will - * find slacker in opener's list and will try to do non-trivial - * things with struct file. Therefore, remove opener from list. - * - * But if opener is removed from list, who will ->release it? - */ - if (pdeo) { - list_del(&pdeo->lh); - spin_unlock(&pde->pde_unload_lock); - rv = pde->proc_fops->release(inode, file); - kfree(pdeo); - } else - spin_unlock(&pde->pde_unload_lock); - return rv; - } - pde->pde_users++; - release = pde->proc_fops->release; - if (pdeo) { - list_del(&pdeo->lh); - kfree(pdeo); + list_for_each_entry(pdeo, &pde->pde_openers, lh) { + if (pdeo->file == file) { + close_pdeo(pde, pdeo); + break; + } } spin_unlock(&pde->pde_unload_lock); - - if (release) - rv = release(inode, file); - - unuse_pde(pde); - return rv; + return 0; } static const struct file_operations proc_reg_file_ops = { diff --git a/fs/proc/internal.h b/fs/proc/internal.h index c43d536f93b9..e2fa9345a9a8 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -153,6 +153,8 @@ int proc_readdir_de(struct proc_dir_entry *de, struct file *filp, void *dirent, struct pde_opener { struct file *file; struct list_head lh; + int count; /* number of threads in close_pdeo() */ + struct mutex mutex; }; ssize_t __proc_file_read(struct file *, char __user *, size_t, loff_t *); |