diff options
Diffstat (limited to 'fs/aio.c')
| -rw-r--r-- | fs/aio.c | 52 | 
1 files changed, 41 insertions, 11 deletions
| @@ -239,15 +239,23 @@ static void __put_ioctx(struct kioctx *ctx)  	call_rcu(&ctx->rcu_head, ctx_rcu_free);  } -#define get_ioctx(kioctx) do {						\ -	BUG_ON(atomic_read(&(kioctx)->users) <= 0);			\ -	atomic_inc(&(kioctx)->users);					\ -} while (0) -#define put_ioctx(kioctx) do {						\ -	BUG_ON(atomic_read(&(kioctx)->users) <= 0);			\ -	if (unlikely(atomic_dec_and_test(&(kioctx)->users))) 		\ -		__put_ioctx(kioctx);					\ -} while (0) +static inline void get_ioctx(struct kioctx *kioctx) +{ +	BUG_ON(atomic_read(&kioctx->users) <= 0); +	atomic_inc(&kioctx->users); +} + +static inline int try_get_ioctx(struct kioctx *kioctx) +{ +	return atomic_inc_not_zero(&kioctx->users); +} + +static inline void put_ioctx(struct kioctx *kioctx) +{ +	BUG_ON(atomic_read(&kioctx->users) <= 0); +	if (unlikely(atomic_dec_and_test(&kioctx->users))) +		__put_ioctx(kioctx); +}  /* ioctx_alloc   *	Allocates and initializes an ioctx.  Returns an ERR_PTR if it failed. @@ -601,8 +609,13 @@ static struct kioctx *lookup_ioctx(unsigned long ctx_id)  	rcu_read_lock();  	hlist_for_each_entry_rcu(ctx, n, &mm->ioctx_list, list) { -		if (ctx->user_id == ctx_id && !ctx->dead) { -			get_ioctx(ctx); +		/* +		 * RCU protects us against accessing freed memory but +		 * we have to be careful not to get a reference when the +		 * reference count already dropped to 0 (ctx->dead test +		 * is unreliable because of races). +		 */ +		if (ctx->user_id == ctx_id && !ctx->dead && try_get_ioctx(ctx)){  			ret = ctx;  			break;  		} @@ -1629,6 +1642,23 @@ static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb,  		goto out_put_req;  	spin_lock_irq(&ctx->ctx_lock); +	/* +	 * We could have raced with io_destroy() and are currently holding a +	 * reference to ctx which should be destroyed. We cannot submit IO +	 * since ctx gets freed as soon as io_submit() puts its reference.  The +	 * check here is reliable: io_destroy() sets ctx->dead before waiting +	 * for outstanding IO and the barrier between these two is realized by +	 * unlock of mm->ioctx_lock and lock of ctx->ctx_lock.  Analogously we +	 * increment ctx->reqs_active before checking for ctx->dead and the +	 * barrier is realized by unlock and lock of ctx->ctx_lock. Thus if we +	 * don't see ctx->dead set here, io_destroy() waits for our IO to +	 * finish. +	 */ +	if (ctx->dead) { +		spin_unlock_irq(&ctx->ctx_lock); +		ret = -EINVAL; +		goto out_put_req; +	}  	aio_run_iocb(req);  	if (!list_empty(&ctx->run_list)) {  		/* drain the run list */ | 
