Commit 76398425 authored by Jonathan Corbet's avatar Jonathan Corbet

Move FASYNC bit handling to f_op->fasync()

Removing the BKL from FASYNC handling ran into the challenge of keeping the
setting of the FASYNC bit in filp->f_flags atomic with regard to calls to
the underlying fasync() function.  Andi Kleen suggested moving the handling
of that bit into fasync(); this patch does exactly that.  As a result, we
have a couple of internal API changes: fasync() must now manage the FASYNC
bit, and it will be called without the BKL held.

As it happens, every fasync() implementation in the kernel with one
exception calls fasync_helper().  So, if we make fasync_helper() set the
FASYNC bit, we can avoid making any changes to the other fasync()
functions - as long as those functions, themselves, have proper locking.
Most fasync() implementations do nothing but call fasync_helper() - which
has its own lock - so they are easily verified as correct.  The BKL had
already been pushed down into the rest.

The networking code has its own version of fasync_helper(), so that code
has been augmented with explicit FASYNC bit handling.

Cc: Al Viro <viro@ZenIV.linux.org.uk>
Cc: David Miller <davem@davemloft.net>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarJonathan Corbet <corbet@lwn.net>
parent db1dd4d3
...@@ -437,8 +437,11 @@ grab BKL for cases when we close a file that had been opened r/w, but that ...@@ -437,8 +437,11 @@ grab BKL for cases when we close a file that had been opened r/w, but that
can and should be done using the internal locking with smaller critical areas). can and should be done using the internal locking with smaller critical areas).
Current worst offender is ext2_get_block()... Current worst offender is ext2_get_block()...
->fasync() is a mess. This area needs a big cleanup and that will probably ->fasync() is called without BKL protection, and is responsible for
affect locking. maintaining the FASYNC bit in filp->f_flags. Most instances call
fasync_helper(), which does that maintenance, so it's not normally
something one needs to worry about. Return values > 0 will be mapped to
zero in the VFS layer.
->readdir() and ->ioctl() on directories must be changed. Ideally we would ->readdir() and ->ioctl() on directories must be changed. Ideally we would
move ->readdir() to inode_operations and use a separate method for directory move ->readdir() to inode_operations and use a separate method for directory
......
...@@ -141,7 +141,7 @@ SYSCALL_DEFINE1(dup, unsigned int, fildes) ...@@ -141,7 +141,7 @@ SYSCALL_DEFINE1(dup, unsigned int, fildes)
return ret; return ret;
} }
#define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | FASYNC | O_DIRECT | O_NOATIME) #define SETFL_MASK (O_APPEND | O_NONBLOCK | O_NDELAY | O_DIRECT | O_NOATIME)
static int setfl(int fd, struct file * filp, unsigned long arg) static int setfl(int fd, struct file * filp, unsigned long arg)
{ {
...@@ -177,23 +177,19 @@ static int setfl(int fd, struct file * filp, unsigned long arg) ...@@ -177,23 +177,19 @@ static int setfl(int fd, struct file * filp, unsigned long arg)
return error; return error;
/* /*
* We still need a lock here for now to keep multiple FASYNC calls * ->fasync() is responsible for setting the FASYNC bit.
* from racing with each other.
*/ */
lock_kernel(); if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op &&
if ((arg ^ filp->f_flags) & FASYNC) { filp->f_op->fasync) {
if (filp->f_op && filp->f_op->fasync) { error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); if (error < 0)
if (error < 0) goto out;
goto out;
}
} }
spin_lock(&filp->f_lock); spin_lock(&filp->f_lock);
filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK); filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
spin_unlock(&filp->f_lock); spin_unlock(&filp->f_lock);
out: out:
unlock_kernel();
return error; return error;
} }
...@@ -518,7 +514,7 @@ static DEFINE_RWLOCK(fasync_lock); ...@@ -518,7 +514,7 @@ static DEFINE_RWLOCK(fasync_lock);
static struct kmem_cache *fasync_cache __read_mostly; static struct kmem_cache *fasync_cache __read_mostly;
/* /*
* fasync_helper() is used by some character device drivers (mainly mice) * fasync_helper() is used by almost all character device drivers
* to set up the fasync queue. It returns negative on error, 0 if it did * to set up the fasync queue. It returns negative on error, 0 if it did
* no changes and positive if it added/deleted the entry. * no changes and positive if it added/deleted the entry.
*/ */
...@@ -557,6 +553,13 @@ int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fap ...@@ -557,6 +553,13 @@ int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fap
result = 1; result = 1;
} }
out: out:
/* Fix up FASYNC bit while still holding fasync_lock */
spin_lock(&filp->f_lock);
if (on)
filp->f_flags |= FASYNC;
else
filp->f_flags &= ~FASYNC;
spin_unlock(&filp->f_lock);
write_unlock_irq(&fasync_lock); write_unlock_irq(&fasync_lock);
return result; return result;
} }
......
...@@ -427,19 +427,11 @@ static int ioctl_fioasync(unsigned int fd, struct file *filp, ...@@ -427,19 +427,11 @@ static int ioctl_fioasync(unsigned int fd, struct file *filp,
/* Did FASYNC state change ? */ /* Did FASYNC state change ? */
if ((flag ^ filp->f_flags) & FASYNC) { if ((flag ^ filp->f_flags) & FASYNC) {
if (filp->f_op && filp->f_op->fasync) if (filp->f_op && filp->f_op->fasync)
/* fasync() adjusts filp->f_flags */
error = filp->f_op->fasync(fd, filp, on); error = filp->f_op->fasync(fd, filp, on);
else else
error = -ENOTTY; error = -ENOTTY;
} }
if (error)
return error;
spin_lock(&filp->f_lock);
if (on)
filp->f_flags |= FASYNC;
else
filp->f_flags &= ~FASYNC;
spin_unlock(&filp->f_lock);
return error; return error;
} }
...@@ -507,10 +499,7 @@ int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, ...@@ -507,10 +499,7 @@ int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
break; break;
case FIOASYNC: case FIOASYNC:
/* BKL needed to avoid races tweaking f_flags */
lock_kernel();
error = ioctl_fioasync(fd, filp, argp); error = ioctl_fioasync(fd, filp, argp);
unlock_kernel();
break; break;
case FIOQSIZE: case FIOQSIZE:
......
...@@ -1030,6 +1030,13 @@ static int sock_fasync(int fd, struct file *filp, int on) ...@@ -1030,6 +1030,13 @@ static int sock_fasync(int fd, struct file *filp, int on)
lock_sock(sk); lock_sock(sk);
spin_lock(&filp->f_lock);
if (on)
filp->f_flags |= FASYNC;
else
filp->f_flags &= ~FASYNC;
spin_unlock(&filp->f_lock);
prev = &(sock->fasync_list); prev = &(sock->fasync_list);
for (fa = *prev; fa != NULL; prev = &fa->fa_next, fa = *prev) for (fa = *prev; fa != NULL; prev = &fa->fa_next, fa = *prev)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment