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/inode.c | |
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/inode.c')
-rw-r--r-- | fs/proc/inode.c | 74 |
1 files changed, 70 insertions, 4 deletions
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) |