diff options
Diffstat (limited to 'fs/fcntl.c')
-rw-r--r-- | fs/fcntl.c | 102 |
1 files changed, 66 insertions, 36 deletions
diff --git a/fs/fcntl.c b/fs/fcntl.c index 2cf93ec40a67..97e01dc0d95f 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c | |||
@@ -618,60 +618,90 @@ static DEFINE_RWLOCK(fasync_lock); | |||
618 | static struct kmem_cache *fasync_cache __read_mostly; | 618 | static struct kmem_cache *fasync_cache __read_mostly; |
619 | 619 | ||
620 | /* | 620 | /* |
621 | * fasync_helper() is used by almost all character device drivers | 621 | * Remove a fasync entry. If successfully removed, return |
622 | * to set up the fasync queue. It returns negative on error, 0 if it did | 622 | * positive and clear the FASYNC flag. If no entry exists, |
623 | * no changes and positive if it added/deleted the entry. | 623 | * do nothing and return 0. |
624 | * | ||
625 | * NOTE! It is very important that the FASYNC flag always | ||
626 | * match the state "is the filp on a fasync list". | ||
627 | * | ||
628 | * We always take the 'filp->f_lock', in since fasync_lock | ||
629 | * needs to be irq-safe. | ||
624 | */ | 630 | */ |
625 | int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) | 631 | static int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp) |
626 | { | 632 | { |
627 | struct fasync_struct *fa, **fp; | 633 | struct fasync_struct *fa, **fp; |
628 | struct fasync_struct *new = NULL; | ||
629 | int result = 0; | 634 | int result = 0; |
630 | 635 | ||
631 | if (on) { | 636 | spin_lock(&filp->f_lock); |
632 | new = kmem_cache_alloc(fasync_cache, GFP_KERNEL); | 637 | write_lock_irq(&fasync_lock); |
633 | if (!new) | 638 | for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { |
634 | return -ENOMEM; | 639 | if (fa->fa_file != filp) |
640 | continue; | ||
641 | *fp = fa->fa_next; | ||
642 | kmem_cache_free(fasync_cache, fa); | ||
643 | filp->f_flags &= ~FASYNC; | ||
644 | result = 1; | ||
645 | break; | ||
635 | } | 646 | } |
647 | write_unlock_irq(&fasync_lock); | ||
648 | spin_unlock(&filp->f_lock); | ||
649 | return result; | ||
650 | } | ||
651 | |||
652 | /* | ||
653 | * Add a fasync entry. Return negative on error, positive if | ||
654 | * added, and zero if did nothing but change an existing one. | ||
655 | * | ||
656 | * NOTE! It is very important that the FASYNC flag always | ||
657 | * match the state "is the filp on a fasync list". | ||
658 | */ | ||
659 | static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp) | ||
660 | { | ||
661 | struct fasync_struct *new, *fa, **fp; | ||
662 | int result = 0; | ||
663 | |||
664 | new = kmem_cache_alloc(fasync_cache, GFP_KERNEL); | ||
665 | if (!new) | ||
666 | return -ENOMEM; | ||
636 | 667 | ||
637 | /* | ||
638 | * We need to take f_lock first since it's not an IRQ-safe | ||
639 | * lock. | ||
640 | */ | ||
641 | spin_lock(&filp->f_lock); | 668 | spin_lock(&filp->f_lock); |
642 | write_lock_irq(&fasync_lock); | 669 | write_lock_irq(&fasync_lock); |
643 | for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { | 670 | for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) { |
644 | if (fa->fa_file == filp) { | 671 | if (fa->fa_file != filp) |
645 | if(on) { | 672 | continue; |
646 | fa->fa_fd = fd; | 673 | fa->fa_fd = fd; |
647 | kmem_cache_free(fasync_cache, new); | 674 | kmem_cache_free(fasync_cache, new); |
648 | } else { | 675 | goto out; |
649 | *fp = fa->fa_next; | ||
650 | kmem_cache_free(fasync_cache, fa); | ||
651 | result = 1; | ||
652 | } | ||
653 | goto out; | ||
654 | } | ||
655 | } | 676 | } |
656 | 677 | ||
657 | if (on) { | 678 | new->magic = FASYNC_MAGIC; |
658 | new->magic = FASYNC_MAGIC; | 679 | new->fa_file = filp; |
659 | new->fa_file = filp; | 680 | new->fa_fd = fd; |
660 | new->fa_fd = fd; | 681 | new->fa_next = *fapp; |
661 | new->fa_next = *fapp; | 682 | *fapp = new; |
662 | *fapp = new; | 683 | result = 1; |
663 | result = 1; | 684 | filp->f_flags |= FASYNC; |
664 | } | 685 | |
665 | out: | 686 | out: |
666 | if (on) | ||
667 | filp->f_flags |= FASYNC; | ||
668 | else | ||
669 | filp->f_flags &= ~FASYNC; | ||
670 | write_unlock_irq(&fasync_lock); | 687 | write_unlock_irq(&fasync_lock); |
671 | spin_unlock(&filp->f_lock); | 688 | spin_unlock(&filp->f_lock); |
672 | return result; | 689 | return result; |
673 | } | 690 | } |
674 | 691 | ||
692 | /* | ||
693 | * fasync_helper() is used by almost all character device drivers | ||
694 | * to set up the fasync queue, and for regular files by the file | ||
695 | * lease code. It returns negative on error, 0 if it did no changes | ||
696 | * and positive if it added/deleted the entry. | ||
697 | */ | ||
698 | int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp) | ||
699 | { | ||
700 | if (!on) | ||
701 | return fasync_remove_entry(filp, fapp); | ||
702 | return fasync_add_entry(fd, filp, fapp); | ||
703 | } | ||
704 | |||
675 | EXPORT_SYMBOL(fasync_helper); | 705 | EXPORT_SYMBOL(fasync_helper); |
676 | 706 | ||
677 | void __kill_fasync(struct fasync_struct *fa, int sig, int band) | 707 | void __kill_fasync(struct fasync_struct *fa, int sig, int band) |