diff options
author | Alexey Dobriyan <adobriyan@gmail.com> | 2008-07-25 04:48:29 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-07-25 13:53:44 -0400 |
commit | 881adb85358309ea9c6f707394002719982ec607 (patch) | |
tree | e4ffc2f6ca6013bab97bdb77b80e98b46a8d01e1 /fs/proc | |
parent | 6e644c3126149b65460610fe5a00d8a162092abe (diff) |
proc: always do ->release
Current two-stage scheme of removing PDE emphasizes one bug in proc:
open
rmmod
remove_proc_entry
close
->release won't be called because ->proc_fops were cleared. In simple
cases it's small memory leak.
For every ->open, ->release has to be done. List of openers is introduced
which is traversed at remove_proc_entry() if neeeded.
Discussions with Al long ago (sigh).
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/proc')
-rw-r--r-- | fs/proc/generic.c | 14 | ||||
-rw-r--r-- | fs/proc/inode.c | 74 | ||||
-rw-r--r-- | fs/proc/internal.h | 7 |
3 files changed, 91 insertions, 4 deletions
diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 43e54e86cefd..bc0a0dd2d844 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c | |||
@@ -597,6 +597,7 @@ static struct proc_dir_entry *__proc_create(struct proc_dir_entry **parent, | |||
597 | ent->pde_users = 0; | 597 | ent->pde_users = 0; |
598 | spin_lock_init(&ent->pde_unload_lock); | 598 | spin_lock_init(&ent->pde_unload_lock); |
599 | ent->pde_unload_completion = NULL; | 599 | ent->pde_unload_completion = NULL; |
600 | INIT_LIST_HEAD(&ent->pde_openers); | ||
600 | out: | 601 | out: |
601 | return ent; | 602 | return ent; |
602 | } | 603 | } |
@@ -789,6 +790,19 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent) | |||
789 | spin_unlock(&de->pde_unload_lock); | 790 | spin_unlock(&de->pde_unload_lock); |
790 | 791 | ||
791 | continue_removing: | 792 | continue_removing: |
793 | spin_lock(&de->pde_unload_lock); | ||
794 | while (!list_empty(&de->pde_openers)) { | ||
795 | struct pde_opener *pdeo; | ||
796 | |||
797 | pdeo = list_first_entry(&de->pde_openers, struct pde_opener, lh); | ||
798 | list_del(&pdeo->lh); | ||
799 | spin_unlock(&de->pde_unload_lock); | ||
800 | pdeo->release(pdeo->inode, pdeo->file); | ||
801 | kfree(pdeo); | ||
802 | spin_lock(&de->pde_unload_lock); | ||
803 | } | ||
804 | spin_unlock(&de->pde_unload_lock); | ||
805 | |||
792 | if (S_ISDIR(de->mode)) | 806 | if (S_ISDIR(de->mode)) |
793 | parent->nlink--; | 807 | parent->nlink--; |
794 | de->nlink = 0; | 808 | de->nlink = 0; |
diff --git a/fs/proc/inode.c b/fs/proc/inode.c index b08d10017911..354c08485825 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c | |||
@@ -126,12 +126,17 @@ static const struct super_operations proc_sops = { | |||
126 | .remount_fs = proc_remount, | 126 | .remount_fs = proc_remount, |
127 | }; | 127 | }; |
128 | 128 | ||
129 | static void pde_users_dec(struct proc_dir_entry *pde) | 129 | static void __pde_users_dec(struct proc_dir_entry *pde) |
130 | { | 130 | { |
131 | spin_lock(&pde->pde_unload_lock); | ||
132 | pde->pde_users--; | 131 | pde->pde_users--; |
133 | if (pde->pde_unload_completion && pde->pde_users == 0) | 132 | if (pde->pde_unload_completion && pde->pde_users == 0) |
134 | complete(pde->pde_unload_completion); | 133 | complete(pde->pde_unload_completion); |
134 | } | ||
135 | |||
136 | static void pde_users_dec(struct proc_dir_entry *pde) | ||
137 | { | ||
138 | spin_lock(&pde->pde_unload_lock); | ||
139 | __pde_users_dec(pde); | ||
135 | spin_unlock(&pde->pde_unload_lock); | 140 | spin_unlock(&pde->pde_unload_lock); |
136 | } | 141 | } |
137 | 142 | ||
@@ -318,36 +323,97 @@ static int proc_reg_open(struct inode *inode, struct file *file) | |||
318 | struct proc_dir_entry *pde = PDE(inode); | 323 | struct proc_dir_entry *pde = PDE(inode); |
319 | int rv = 0; | 324 | int rv = 0; |
320 | int (*open)(struct inode *, struct file *); | 325 | int (*open)(struct inode *, struct file *); |
326 | int (*release)(struct inode *, struct file *); | ||
327 | struct pde_opener *pdeo; | ||
328 | |||
329 | /* | ||
330 | * What for, you ask? Well, we can have open, rmmod, remove_proc_entry | ||
331 | * sequence. ->release won't be called because ->proc_fops will be | ||
332 | * cleared. Depending on complexity of ->release, consequences vary. | ||
333 | * | ||
334 | * We can't wait for mercy when close will be done for real, it's | ||
335 | * deadlockable: rmmod foo </proc/foo . So, we're going to do ->release | ||
336 | * by hand in remove_proc_entry(). For this, save opener's credentials | ||
337 | * for later. | ||
338 | */ | ||
339 | pdeo = kmalloc(sizeof(struct pde_opener), GFP_KERNEL); | ||
340 | if (!pdeo) | ||
341 | return -ENOMEM; | ||
321 | 342 | ||
322 | spin_lock(&pde->pde_unload_lock); | 343 | spin_lock(&pde->pde_unload_lock); |
323 | if (!pde->proc_fops) { | 344 | if (!pde->proc_fops) { |
324 | spin_unlock(&pde->pde_unload_lock); | 345 | spin_unlock(&pde->pde_unload_lock); |
346 | kfree(pdeo); | ||
325 | return rv; | 347 | return rv; |
326 | } | 348 | } |
327 | pde->pde_users++; | 349 | pde->pde_users++; |
328 | open = pde->proc_fops->open; | 350 | open = pde->proc_fops->open; |
351 | release = pde->proc_fops->release; | ||
329 | spin_unlock(&pde->pde_unload_lock); | 352 | spin_unlock(&pde->pde_unload_lock); |
330 | 353 | ||
331 | if (open) | 354 | if (open) |
332 | rv = open(inode, file); | 355 | rv = open(inode, file); |
333 | 356 | ||
334 | pde_users_dec(pde); | 357 | spin_lock(&pde->pde_unload_lock); |
358 | if (rv == 0 && release) { | ||
359 | /* To know what to release. */ | ||
360 | pdeo->inode = inode; | ||
361 | pdeo->file = file; | ||
362 | /* Strictly for "too late" ->release in proc_reg_release(). */ | ||
363 | pdeo->release = release; | ||
364 | list_add(&pdeo->lh, &pde->pde_openers); | ||
365 | } else | ||
366 | kfree(pdeo); | ||
367 | __pde_users_dec(pde); | ||
368 | spin_unlock(&pde->pde_unload_lock); | ||
335 | return rv; | 369 | return rv; |
336 | } | 370 | } |
337 | 371 | ||
372 | static struct pde_opener *find_pde_opener(struct proc_dir_entry *pde, | ||
373 | struct inode *inode, struct file *file) | ||
374 | { | ||
375 | struct pde_opener *pdeo; | ||
376 | |||
377 | list_for_each_entry(pdeo, &pde->pde_openers, lh) { | ||
378 | if (pdeo->inode == inode && pdeo->file == file) | ||
379 | return pdeo; | ||
380 | } | ||
381 | return NULL; | ||
382 | } | ||
383 | |||
338 | static int proc_reg_release(struct inode *inode, struct file *file) | 384 | static int proc_reg_release(struct inode *inode, struct file *file) |
339 | { | 385 | { |
340 | struct proc_dir_entry *pde = PDE(inode); | 386 | struct proc_dir_entry *pde = PDE(inode); |
341 | int rv = 0; | 387 | int rv = 0; |
342 | int (*release)(struct inode *, struct file *); | 388 | int (*release)(struct inode *, struct file *); |
389 | struct pde_opener *pdeo; | ||
343 | 390 | ||
344 | spin_lock(&pde->pde_unload_lock); | 391 | spin_lock(&pde->pde_unload_lock); |
392 | pdeo = find_pde_opener(pde, inode, file); | ||
345 | if (!pde->proc_fops) { | 393 | if (!pde->proc_fops) { |
346 | spin_unlock(&pde->pde_unload_lock); | 394 | /* |
395 | * Can't simply exit, __fput() will think that everything is OK, | ||
396 | * and move on to freeing struct file. remove_proc_entry() will | ||
397 | * find slacker in opener's list and will try to do non-trivial | ||
398 | * things with struct file. Therefore, remove opener from list. | ||
399 | * | ||
400 | * But if opener is removed from list, who will ->release it? | ||
401 | */ | ||
402 | if (pdeo) { | ||
403 | list_del(&pdeo->lh); | ||
404 | spin_unlock(&pde->pde_unload_lock); | ||
405 | rv = pdeo->release(inode, file); | ||
406 | kfree(pdeo); | ||
407 | } else | ||
408 | spin_unlock(&pde->pde_unload_lock); | ||
347 | return rv; | 409 | return rv; |
348 | } | 410 | } |
349 | pde->pde_users++; | 411 | pde->pde_users++; |
350 | release = pde->proc_fops->release; | 412 | release = pde->proc_fops->release; |
413 | if (pdeo) { | ||
414 | list_del(&pdeo->lh); | ||
415 | kfree(pdeo); | ||
416 | } | ||
351 | spin_unlock(&pde->pde_unload_lock); | 417 | spin_unlock(&pde->pde_unload_lock); |
352 | 418 | ||
353 | if (release) | 419 | if (release) |
diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 8d67616e7bb0..442202314d53 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h | |||
@@ -89,3 +89,10 @@ struct dentry *proc_lookup_de(struct proc_dir_entry *de, struct inode *ino, | |||
89 | struct dentry *dentry); | 89 | struct dentry *dentry); |
90 | int proc_readdir_de(struct proc_dir_entry *de, struct file *filp, void *dirent, | 90 | int proc_readdir_de(struct proc_dir_entry *de, struct file *filp, void *dirent, |
91 | filldir_t filldir); | 91 | filldir_t filldir); |
92 | |||
93 | struct pde_opener { | ||
94 | struct inode *inode; | ||
95 | struct file *file; | ||
96 | int (*release)(struct inode *, struct file *); | ||
97 | struct list_head lh; | ||
98 | }; | ||