diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-08-27 15:26:02 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-08-27 15:26:02 -0400 |
commit | 9c504cadc443a3d002fa581ec5109c0ef02d7b14 (patch) | |
tree | 0b44c60843062e5ee8d91a636dab67ada5286156 | |
parent | 4f8ee2c9cc0e885d2bb50ef26db66150ab25213e (diff) | |
parent | 0db501bd0610ee0c0aca84d927f90bcccd09e2bd (diff) |
Merge branch 'for-linus' of git://git.infradead.org/users/eparis/notify
* 'for-linus' of git://git.infradead.org/users/eparis/notify:
inotify: Ensure we alwasy write the terminating NULL.
inotify: fix locking around inotify watching in the idr
inotify: do not BUG on idr entries at inotify destruction
inotify: seperate new watch creation updating existing watches
-rw-r--r-- | fs/notify/inotify/inotify_fsnotify.c | 33 | ||||
-rw-r--r-- | fs/notify/inotify/inotify_user.c | 229 |
2 files changed, 177 insertions, 85 deletions
diff --git a/fs/notify/inotify/inotify_fsnotify.c b/fs/notify/inotify/inotify_fsnotify.c index 5dcbafe72d71..c9ee67b442e1 100644 --- a/fs/notify/inotify/inotify_fsnotify.c +++ b/fs/notify/inotify/inotify_fsnotify.c | |||
@@ -105,16 +105,45 @@ static bool inotify_should_send_event(struct fsnotify_group *group, struct inode | |||
105 | return send; | 105 | return send; |
106 | } | 106 | } |
107 | 107 | ||
108 | /* | ||
109 | * This is NEVER supposed to be called. Inotify marks should either have been | ||
110 | * removed from the idr when the watch was removed or in the | ||
111 | * fsnotify_destroy_mark_by_group() call when the inotify instance was being | ||
112 | * torn down. This is only called if the idr is about to be freed but there | ||
113 | * are still marks in it. | ||
114 | */ | ||
108 | static int idr_callback(int id, void *p, void *data) | 115 | static int idr_callback(int id, void *p, void *data) |
109 | { | 116 | { |
110 | BUG(); | 117 | struct fsnotify_mark_entry *entry; |
118 | struct inotify_inode_mark_entry *ientry; | ||
119 | static bool warned = false; | ||
120 | |||
121 | if (warned) | ||
122 | return 0; | ||
123 | |||
124 | warned = false; | ||
125 | entry = p; | ||
126 | ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry); | ||
127 | |||
128 | WARN(1, "inotify closing but id=%d for entry=%p in group=%p still in " | ||
129 | "idr. Probably leaking memory\n", id, p, data); | ||
130 | |||
131 | /* | ||
132 | * I'm taking the liberty of assuming that the mark in question is a | ||
133 | * valid address and I'm dereferencing it. This might help to figure | ||
134 | * out why we got here and the panic is no worse than the original | ||
135 | * BUG() that was here. | ||
136 | */ | ||
137 | if (entry) | ||
138 | printk(KERN_WARNING "entry->group=%p inode=%p wd=%d\n", | ||
139 | entry->group, entry->inode, ientry->wd); | ||
111 | return 0; | 140 | return 0; |
112 | } | 141 | } |
113 | 142 | ||
114 | static void inotify_free_group_priv(struct fsnotify_group *group) | 143 | static void inotify_free_group_priv(struct fsnotify_group *group) |
115 | { | 144 | { |
116 | /* ideally the idr is empty and we won't hit the BUG in teh callback */ | 145 | /* ideally the idr is empty and we won't hit the BUG in teh callback */ |
117 | idr_for_each(&group->inotify_data.idr, idr_callback, NULL); | 146 | idr_for_each(&group->inotify_data.idr, idr_callback, group); |
118 | idr_remove_all(&group->inotify_data.idr); | 147 | idr_remove_all(&group->inotify_data.idr); |
119 | idr_destroy(&group->inotify_data.idr); | 148 | idr_destroy(&group->inotify_data.idr); |
120 | } | 149 | } |
diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index dc32ed8323ba..0e781bc88d1e 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c | |||
@@ -47,9 +47,6 @@ | |||
47 | 47 | ||
48 | static struct vfsmount *inotify_mnt __read_mostly; | 48 | static struct vfsmount *inotify_mnt __read_mostly; |
49 | 49 | ||
50 | /* this just sits here and wastes global memory. used to just pad userspace messages with zeros */ | ||
51 | static struct inotify_event nul_inotify_event; | ||
52 | |||
53 | /* these are configurable via /proc/sys/fs/inotify/ */ | 50 | /* these are configurable via /proc/sys/fs/inotify/ */ |
54 | static int inotify_max_user_instances __read_mostly; | 51 | static int inotify_max_user_instances __read_mostly; |
55 | static int inotify_max_queued_events __read_mostly; | 52 | static int inotify_max_queued_events __read_mostly; |
@@ -199,8 +196,10 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, | |||
199 | inotify_free_event_priv(fsn_priv); | 196 | inotify_free_event_priv(fsn_priv); |
200 | } | 197 | } |
201 | 198 | ||
202 | /* round up event->name_len so it is a multiple of event_size */ | 199 | /* round up event->name_len so it is a multiple of event_size |
203 | name_len = roundup(event->name_len, event_size); | 200 | * plus an extra byte for the terminating '\0'. |
201 | */ | ||
202 | name_len = roundup(event->name_len + 1, event_size); | ||
204 | inotify_event.len = name_len; | 203 | inotify_event.len = name_len; |
205 | 204 | ||
206 | inotify_event.mask = inotify_mask_to_arg(event->mask); | 205 | inotify_event.mask = inotify_mask_to_arg(event->mask); |
@@ -224,8 +223,8 @@ static ssize_t copy_event_to_user(struct fsnotify_group *group, | |||
224 | return -EFAULT; | 223 | return -EFAULT; |
225 | buf += event->name_len; | 224 | buf += event->name_len; |
226 | 225 | ||
227 | /* fill userspace with 0's from nul_inotify_event */ | 226 | /* fill userspace with 0's */ |
228 | if (copy_to_user(buf, &nul_inotify_event, len_to_zero)) | 227 | if (clear_user(buf, len_to_zero)) |
229 | return -EFAULT; | 228 | return -EFAULT; |
230 | buf += len_to_zero; | 229 | buf += len_to_zero; |
231 | event_size += name_len; | 230 | event_size += name_len; |
@@ -364,20 +363,53 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, uns | |||
364 | return error; | 363 | return error; |
365 | } | 364 | } |
366 | 365 | ||
366 | /* | ||
367 | * Remove the mark from the idr (if present) and drop the reference | ||
368 | * on the mark because it was in the idr. | ||
369 | */ | ||
367 | static void inotify_remove_from_idr(struct fsnotify_group *group, | 370 | static void inotify_remove_from_idr(struct fsnotify_group *group, |
368 | struct inotify_inode_mark_entry *ientry) | 371 | struct inotify_inode_mark_entry *ientry) |
369 | { | 372 | { |
370 | struct idr *idr; | 373 | struct idr *idr; |
374 | struct fsnotify_mark_entry *entry; | ||
375 | struct inotify_inode_mark_entry *found_ientry; | ||
376 | int wd; | ||
371 | 377 | ||
372 | spin_lock(&group->inotify_data.idr_lock); | 378 | spin_lock(&group->inotify_data.idr_lock); |
373 | idr = &group->inotify_data.idr; | 379 | idr = &group->inotify_data.idr; |
374 | idr_remove(idr, ientry->wd); | 380 | wd = ientry->wd; |
375 | spin_unlock(&group->inotify_data.idr_lock); | 381 | |
382 | if (wd == -1) | ||
383 | goto out; | ||
384 | |||
385 | entry = idr_find(&group->inotify_data.idr, wd); | ||
386 | if (unlikely(!entry)) | ||
387 | goto out; | ||
388 | |||
389 | found_ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry); | ||
390 | if (unlikely(found_ientry != ientry)) { | ||
391 | /* We found an entry in the idr with the right wd, but it's | ||
392 | * not the entry we were told to remove. eparis seriously | ||
393 | * fucked up somewhere. */ | ||
394 | WARN_ON(1); | ||
395 | ientry->wd = -1; | ||
396 | goto out; | ||
397 | } | ||
398 | |||
399 | /* One ref for being in the idr, one ref held by the caller */ | ||
400 | BUG_ON(atomic_read(&entry->refcnt) < 2); | ||
401 | |||
402 | idr_remove(idr, wd); | ||
376 | ientry->wd = -1; | 403 | ientry->wd = -1; |
404 | |||
405 | /* removed from the idr, drop that ref */ | ||
406 | fsnotify_put_mark(entry); | ||
407 | out: | ||
408 | spin_unlock(&group->inotify_data.idr_lock); | ||
377 | } | 409 | } |
410 | |||
378 | /* | 411 | /* |
379 | * Send IN_IGNORED for this wd, remove this wd from the idr, and drop the | 412 | * Send IN_IGNORED for this wd, remove this wd from the idr. |
380 | * internal reference help on the mark because it is in the idr. | ||
381 | */ | 413 | */ |
382 | void inotify_ignored_and_remove_idr(struct fsnotify_mark_entry *entry, | 414 | void inotify_ignored_and_remove_idr(struct fsnotify_mark_entry *entry, |
383 | struct fsnotify_group *group) | 415 | struct fsnotify_group *group) |
@@ -417,9 +449,6 @@ skip_send_ignore: | |||
417 | /* remove this entry from the idr */ | 449 | /* remove this entry from the idr */ |
418 | inotify_remove_from_idr(group, ientry); | 450 | inotify_remove_from_idr(group, ientry); |
419 | 451 | ||
420 | /* removed from idr, drop that reference */ | ||
421 | fsnotify_put_mark(entry); | ||
422 | |||
423 | atomic_dec(&group->inotify_data.user->inotify_watches); | 452 | atomic_dec(&group->inotify_data.user->inotify_watches); |
424 | } | 453 | } |
425 | 454 | ||
@@ -431,80 +460,29 @@ static void inotify_free_mark(struct fsnotify_mark_entry *entry) | |||
431 | kmem_cache_free(inotify_inode_mark_cachep, ientry); | 460 | kmem_cache_free(inotify_inode_mark_cachep, ientry); |
432 | } | 461 | } |
433 | 462 | ||
434 | static int inotify_update_watch(struct fsnotify_group *group, struct inode *inode, u32 arg) | 463 | static int inotify_update_existing_watch(struct fsnotify_group *group, |
464 | struct inode *inode, | ||
465 | u32 arg) | ||
435 | { | 466 | { |
436 | struct fsnotify_mark_entry *entry = NULL; | 467 | struct fsnotify_mark_entry *entry; |
437 | struct inotify_inode_mark_entry *ientry; | 468 | struct inotify_inode_mark_entry *ientry; |
438 | struct inotify_inode_mark_entry *tmp_ientry; | ||
439 | int ret = 0; | ||
440 | int add = (arg & IN_MASK_ADD); | ||
441 | __u32 mask; | ||
442 | __u32 old_mask, new_mask; | 469 | __u32 old_mask, new_mask; |
470 | __u32 mask; | ||
471 | int add = (arg & IN_MASK_ADD); | ||
472 | int ret; | ||
443 | 473 | ||
444 | /* don't allow invalid bits: we don't want flags set */ | 474 | /* don't allow invalid bits: we don't want flags set */ |
445 | mask = inotify_arg_to_mask(arg); | 475 | mask = inotify_arg_to_mask(arg); |
446 | if (unlikely(!mask)) | 476 | if (unlikely(!mask)) |
447 | return -EINVAL; | 477 | return -EINVAL; |
448 | 478 | ||
449 | tmp_ientry = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL); | ||
450 | if (unlikely(!tmp_ientry)) | ||
451 | return -ENOMEM; | ||
452 | /* we set the mask at the end after attaching it */ | ||
453 | fsnotify_init_mark(&tmp_ientry->fsn_entry, inotify_free_mark); | ||
454 | tmp_ientry->wd = -1; | ||
455 | |||
456 | find_entry: | ||
457 | spin_lock(&inode->i_lock); | 479 | spin_lock(&inode->i_lock); |
458 | entry = fsnotify_find_mark_entry(group, inode); | 480 | entry = fsnotify_find_mark_entry(group, inode); |
459 | spin_unlock(&inode->i_lock); | 481 | spin_unlock(&inode->i_lock); |
460 | if (entry) { | 482 | if (!entry) |
461 | ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry); | 483 | return -ENOENT; |
462 | } else { | ||
463 | ret = -ENOSPC; | ||
464 | if (atomic_read(&group->inotify_data.user->inotify_watches) >= inotify_max_user_watches) | ||
465 | goto out_err; | ||
466 | retry: | ||
467 | ret = -ENOMEM; | ||
468 | if (unlikely(!idr_pre_get(&group->inotify_data.idr, GFP_KERNEL))) | ||
469 | goto out_err; | ||
470 | |||
471 | spin_lock(&group->inotify_data.idr_lock); | ||
472 | ret = idr_get_new_above(&group->inotify_data.idr, &tmp_ientry->fsn_entry, | ||
473 | group->inotify_data.last_wd, | ||
474 | &tmp_ientry->wd); | ||
475 | spin_unlock(&group->inotify_data.idr_lock); | ||
476 | if (ret) { | ||
477 | if (ret == -EAGAIN) | ||
478 | goto retry; | ||
479 | goto out_err; | ||
480 | } | ||
481 | |||
482 | ret = fsnotify_add_mark(&tmp_ientry->fsn_entry, group, inode); | ||
483 | if (ret) { | ||
484 | inotify_remove_from_idr(group, tmp_ientry); | ||
485 | if (ret == -EEXIST) | ||
486 | goto find_entry; | ||
487 | goto out_err; | ||
488 | } | ||
489 | |||
490 | /* tmp_ientry has been added to the inode, so we are all set up. | ||
491 | * now we just need to make sure tmp_ientry doesn't get freed and | ||
492 | * we need to set up entry and ientry so the generic code can | ||
493 | * do its thing. */ | ||
494 | ientry = tmp_ientry; | ||
495 | entry = &ientry->fsn_entry; | ||
496 | tmp_ientry = NULL; | ||
497 | |||
498 | atomic_inc(&group->inotify_data.user->inotify_watches); | ||
499 | |||
500 | /* update the idr hint */ | ||
501 | group->inotify_data.last_wd = ientry->wd; | ||
502 | |||
503 | /* we put the mark on the idr, take a reference */ | ||
504 | fsnotify_get_mark(entry); | ||
505 | } | ||
506 | 484 | ||
507 | ret = ientry->wd; | 485 | ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry); |
508 | 486 | ||
509 | spin_lock(&entry->lock); | 487 | spin_lock(&entry->lock); |
510 | 488 | ||
@@ -536,18 +514,103 @@ retry: | |||
536 | fsnotify_recalc_group_mask(group); | 514 | fsnotify_recalc_group_mask(group); |
537 | } | 515 | } |
538 | 516 | ||
539 | /* this either matches fsnotify_find_mark_entry, or init_mark_entry | 517 | /* return the wd */ |
540 | * depending on which path we took... */ | 518 | ret = ientry->wd; |
519 | |||
520 | /* match the get from fsnotify_find_mark_entry() */ | ||
541 | fsnotify_put_mark(entry); | 521 | fsnotify_put_mark(entry); |
542 | 522 | ||
523 | return ret; | ||
524 | } | ||
525 | |||
526 | static int inotify_new_watch(struct fsnotify_group *group, | ||
527 | struct inode *inode, | ||
528 | u32 arg) | ||
529 | { | ||
530 | struct inotify_inode_mark_entry *tmp_ientry; | ||
531 | __u32 mask; | ||
532 | int ret; | ||
533 | |||
534 | /* don't allow invalid bits: we don't want flags set */ | ||
535 | mask = inotify_arg_to_mask(arg); | ||
536 | if (unlikely(!mask)) | ||
537 | return -EINVAL; | ||
538 | |||
539 | tmp_ientry = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL); | ||
540 | if (unlikely(!tmp_ientry)) | ||
541 | return -ENOMEM; | ||
542 | |||
543 | fsnotify_init_mark(&tmp_ientry->fsn_entry, inotify_free_mark); | ||
544 | tmp_ientry->fsn_entry.mask = mask; | ||
545 | tmp_ientry->wd = -1; | ||
546 | |||
547 | ret = -ENOSPC; | ||
548 | if (atomic_read(&group->inotify_data.user->inotify_watches) >= inotify_max_user_watches) | ||
549 | goto out_err; | ||
550 | retry: | ||
551 | ret = -ENOMEM; | ||
552 | if (unlikely(!idr_pre_get(&group->inotify_data.idr, GFP_KERNEL))) | ||
553 | goto out_err; | ||
554 | |||
555 | spin_lock(&group->inotify_data.idr_lock); | ||
556 | ret = idr_get_new_above(&group->inotify_data.idr, &tmp_ientry->fsn_entry, | ||
557 | group->inotify_data.last_wd, | ||
558 | &tmp_ientry->wd); | ||
559 | spin_unlock(&group->inotify_data.idr_lock); | ||
560 | if (ret) { | ||
561 | /* idr was out of memory allocate and try again */ | ||
562 | if (ret == -EAGAIN) | ||
563 | goto retry; | ||
564 | goto out_err; | ||
565 | } | ||
566 | |||
567 | /* we put the mark on the idr, take a reference */ | ||
568 | fsnotify_get_mark(&tmp_ientry->fsn_entry); | ||
569 | |||
570 | /* we are on the idr, now get on the inode */ | ||
571 | ret = fsnotify_add_mark(&tmp_ientry->fsn_entry, group, inode); | ||
572 | if (ret) { | ||
573 | /* we failed to get on the inode, get off the idr */ | ||
574 | inotify_remove_from_idr(group, tmp_ientry); | ||
575 | goto out_err; | ||
576 | } | ||
577 | |||
578 | /* update the idr hint, who cares about races, it's just a hint */ | ||
579 | group->inotify_data.last_wd = tmp_ientry->wd; | ||
580 | |||
581 | /* increment the number of watches the user has */ | ||
582 | atomic_inc(&group->inotify_data.user->inotify_watches); | ||
583 | |||
584 | /* return the watch descriptor for this new entry */ | ||
585 | ret = tmp_ientry->wd; | ||
586 | |||
587 | /* match the ref from fsnotify_init_markentry() */ | ||
588 | fsnotify_put_mark(&tmp_ientry->fsn_entry); | ||
589 | |||
543 | out_err: | 590 | out_err: |
544 | /* could be an error, could be that we found an existing mark */ | 591 | if (ret < 0) |
545 | if (tmp_ientry) { | ||
546 | /* on the idr but didn't make it on the inode */ | ||
547 | if (tmp_ientry->wd != -1) | ||
548 | inotify_remove_from_idr(group, tmp_ientry); | ||
549 | kmem_cache_free(inotify_inode_mark_cachep, tmp_ientry); | 592 | kmem_cache_free(inotify_inode_mark_cachep, tmp_ientry); |
550 | } | 593 | |
594 | return ret; | ||
595 | } | ||
596 | |||
597 | static int inotify_update_watch(struct fsnotify_group *group, struct inode *inode, u32 arg) | ||
598 | { | ||
599 | int ret = 0; | ||
600 | |||
601 | retry: | ||
602 | /* try to update and existing watch with the new arg */ | ||
603 | ret = inotify_update_existing_watch(group, inode, arg); | ||
604 | /* no mark present, try to add a new one */ | ||
605 | if (ret == -ENOENT) | ||
606 | ret = inotify_new_watch(group, inode, arg); | ||
607 | /* | ||
608 | * inotify_new_watch could race with another thread which did an | ||
609 | * inotify_new_watch between the update_existing and the add watch | ||
610 | * here, go back and try to update an existing mark again. | ||
611 | */ | ||
612 | if (ret == -EEXIST) | ||
613 | goto retry; | ||
551 | 614 | ||
552 | return ret; | 615 | return ret; |
553 | } | 616 | } |