Skip to content

Commit f7347ce

Browse files
torvaldsarndb
authored andcommitted
fasync: re-organize fasync entry insertion to allow it under a spinlock
You currently cannot use "fasync_helper()" in an atomic environment to insert a new fasync entry, because it will need to allocate the new "struct fasync_struct". Yet fcntl_setlease() wants to call this under lock_flocks(), which is in the process of being converted from the BKL to a spinlock. In order to fix this, this abstracts out the actual fasync list insertion and the fasync allocations into functions of their own, and teaches fs/locks.c to pre-allocate the fasync_struct entry. That way the actual list insertion can happen while holding the required spinlock. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> [bfields@redhat.com: rebase on top of my changes to Arnd's patch] Tested-by: J. Bruce Fields <bfields@redhat.com> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
1 parent c5b1f0d commit f7347ce

File tree

3 files changed

+72
-17
lines changed

3 files changed

+72
-17
lines changed

fs/fcntl.c

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ static void fasync_free_rcu(struct rcu_head *head)
640640
* match the state "is the filp on a fasync list".
641641
*
642642
*/
643-
static int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp)
643+
int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp)
644644
{
645645
struct fasync_struct *fa, **fp;
646646
int result = 0;
@@ -666,21 +666,28 @@ static int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp)
666666
return result;
667667
}
668668

669+
struct fasync_struct *fasync_alloc(void)
670+
{
671+
return kmem_cache_alloc(fasync_cache, GFP_KERNEL);
672+
}
673+
669674
/*
670-
* Add a fasync entry. Return negative on error, positive if
671-
* added, and zero if did nothing but change an existing one.
672-
*
673-
* NOTE! It is very important that the FASYNC flag always
674-
* match the state "is the filp on a fasync list".
675+
* NOTE! This can be used only for unused fasync entries:
676+
* entries that actually got inserted on the fasync list
677+
* need to be released by rcu - see fasync_remove_entry.
675678
*/
676-
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
679+
void fasync_free(struct fasync_struct *new)
677680
{
678-
struct fasync_struct *new, *fa, **fp;
679-
int result = 0;
681+
kmem_cache_free(fasync_cache, new);
682+
}
680683

681-
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
682-
if (!new)
683-
return -ENOMEM;
684+
/*
685+
* Insert a new entry into the fasync list. Return the pointer to the
686+
* old one if we didn't use the new one.
687+
*/
688+
struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
689+
{
690+
struct fasync_struct *fa, **fp;
684691

685692
spin_lock(&filp->f_lock);
686693
spin_lock(&fasync_lock);
@@ -691,8 +698,6 @@ static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fa
691698
spin_lock_irq(&fa->fa_lock);
692699
fa->fa_fd = fd;
693700
spin_unlock_irq(&fa->fa_lock);
694-
695-
kmem_cache_free(fasync_cache, new);
696701
goto out;
697702
}
698703

@@ -702,13 +707,42 @@ static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fa
702707
new->fa_fd = fd;
703708
new->fa_next = *fapp;
704709
rcu_assign_pointer(*fapp, new);
705-
result = 1;
706710
filp->f_flags |= FASYNC;
707711

708712
out:
709713
spin_unlock(&fasync_lock);
710714
spin_unlock(&filp->f_lock);
711-
return result;
715+
return fa;
716+
}
717+
718+
/*
719+
* Add a fasync entry. Return negative on error, positive if
720+
* added, and zero if did nothing but change an existing one.
721+
*
722+
* NOTE! It is very important that the FASYNC flag always
723+
* match the state "is the filp on a fasync list".
724+
*/
725+
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
726+
{
727+
struct fasync_struct *new;
728+
729+
new = fasync_alloc();
730+
if (!new)
731+
return -ENOMEM;
732+
733+
/*
734+
* fasync_insert_entry() returns the old (update) entry if
735+
* it existed.
736+
*
737+
* So free the (unused) new entry and return 0 to let the
738+
* caller know that we didn't add any new fasync entries.
739+
*/
740+
if (fasync_insert_entry(fd, filp, fapp, new)) {
741+
fasync_free(new);
742+
return 0;
743+
}
744+
745+
return 1;
712746
}
713747

714748
/*

fs/locks.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1505,19 +1505,33 @@ EXPORT_SYMBOL_GPL(vfs_setlease);
15051505
int fcntl_setlease(unsigned int fd, struct file *filp, long arg)
15061506
{
15071507
struct file_lock *fl;
1508+
struct fasync_struct *new;
15081509
struct inode *inode = filp->f_path.dentry->d_inode;
15091510
int error;
15101511

15111512
fl = lease_alloc(filp, arg);
15121513
if (IS_ERR(fl))
15131514
return PTR_ERR(fl);
15141515

1516+
new = fasync_alloc();
1517+
if (!new) {
1518+
locks_free_lock(fl);
1519+
return -ENOMEM;
1520+
}
15151521
lock_flocks();
15161522
error = __vfs_setlease(filp, arg, &fl);
15171523
if (error || arg == F_UNLCK)
15181524
goto out_unlock;
15191525

1520-
error = fasync_helper(fd, filp, 1, &fl->fl_fasync);
1526+
/*
1527+
* fasync_insert_entry() returns the old entry if any.
1528+
* If there was no old entry, then it used 'new' and
1529+
* inserted it into the fasync list. Clear new so that
1530+
* we don't release it here.
1531+
*/
1532+
if (!fasync_insert_entry(fd, filp, &fl->fl_fasync, new))
1533+
new = NULL;
1534+
15211535
if (error < 0) {
15221536
/* remove lease just inserted by setlease */
15231537
fl->fl_type = F_UNLCK | F_INPROGRESS;
@@ -1529,6 +1543,8 @@ int fcntl_setlease(unsigned int fd, struct file *filp, long arg)
15291543
error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0);
15301544
out_unlock:
15311545
unlock_flocks();
1546+
if (new)
1547+
fasync_free(new);
15321548
return error;
15331549
}
15341550

include/linux/fs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,11 @@ struct fasync_struct {
13021302

13031303
/* SMP safe fasync helpers: */
13041304
extern int fasync_helper(int, struct file *, int, struct fasync_struct **);
1305+
extern struct fasync_struct *fasync_insert_entry(int, struct file *, struct fasync_struct **, struct fasync_struct *);
1306+
extern int fasync_remove_entry(struct file *, struct fasync_struct **);
1307+
extern struct fasync_struct *fasync_alloc(void);
1308+
extern void fasync_free(struct fasync_struct *);
1309+
13051310
/* can be called from interrupts */
13061311
extern void kill_fasync(struct fasync_struct **, int, int);
13071312

0 commit comments

Comments
 (0)