|
@@ -11,8 +11,10 @@
|
|
* Now using anonymous inode source.
|
|
* Now using anonymous inode source.
|
|
* Thanks to Oleg Nesterov for useful code review and suggestions.
|
|
* Thanks to Oleg Nesterov for useful code review and suggestions.
|
|
* More comments and suggestions from Arnd Bergmann.
|
|
* More comments and suggestions from Arnd Bergmann.
|
|
- * Sat May 19, 2007: Davi E. M. Arnaut <davi@haxent.com.br>
|
|
|
|
|
|
+ * Sat May 19, 2007: Davi E. M. Arnaut <davi@haxent.com.br>
|
|
* Retrieve multiple signals with one read() call
|
|
* Retrieve multiple signals with one read() call
|
|
|
|
+ * Sun Jul 15, 2007: Davide Libenzi <davidel@xmailserver.org>
|
|
|
|
+ * Attach to the sighand only during read() and poll().
|
|
*/
|
|
*/
|
|
|
|
|
|
#include <linux/file.h>
|
|
#include <linux/file.h>
|
|
@@ -27,102 +29,12 @@
|
|
#include <linux/signalfd.h>
|
|
#include <linux/signalfd.h>
|
|
|
|
|
|
struct signalfd_ctx {
|
|
struct signalfd_ctx {
|
|
- struct list_head lnk;
|
|
|
|
- wait_queue_head_t wqh;
|
|
|
|
sigset_t sigmask;
|
|
sigset_t sigmask;
|
|
- struct task_struct *tsk;
|
|
|
|
};
|
|
};
|
|
|
|
|
|
-struct signalfd_lockctx {
|
|
|
|
- struct task_struct *tsk;
|
|
|
|
- unsigned long flags;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * Tries to acquire the sighand lock. We do not increment the sighand
|
|
|
|
- * use count, and we do not even pin the task struct, so we need to
|
|
|
|
- * do it inside an RCU read lock, and we must be prepared for the
|
|
|
|
- * ctx->tsk going to NULL (in signalfd_deliver()), and for the sighand
|
|
|
|
- * being detached. We return 0 if the sighand has been detached, or
|
|
|
|
- * 1 if we were able to pin the sighand lock.
|
|
|
|
- */
|
|
|
|
-static int signalfd_lock(struct signalfd_ctx *ctx, struct signalfd_lockctx *lk)
|
|
|
|
-{
|
|
|
|
- struct sighand_struct *sighand = NULL;
|
|
|
|
-
|
|
|
|
- rcu_read_lock();
|
|
|
|
- lk->tsk = rcu_dereference(ctx->tsk);
|
|
|
|
- if (likely(lk->tsk != NULL))
|
|
|
|
- sighand = lock_task_sighand(lk->tsk, &lk->flags);
|
|
|
|
- rcu_read_unlock();
|
|
|
|
-
|
|
|
|
- if (!sighand)
|
|
|
|
- return 0;
|
|
|
|
-
|
|
|
|
- if (!ctx->tsk) {
|
|
|
|
- unlock_task_sighand(lk->tsk, &lk->flags);
|
|
|
|
- return 0;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (lk->tsk->tgid == current->tgid)
|
|
|
|
- lk->tsk = current;
|
|
|
|
-
|
|
|
|
- return 1;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static void signalfd_unlock(struct signalfd_lockctx *lk)
|
|
|
|
-{
|
|
|
|
- unlock_task_sighand(lk->tsk, &lk->flags);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * This must be called with the sighand lock held.
|
|
|
|
- */
|
|
|
|
-void signalfd_deliver(struct task_struct *tsk, int sig)
|
|
|
|
-{
|
|
|
|
- struct sighand_struct *sighand = tsk->sighand;
|
|
|
|
- struct signalfd_ctx *ctx, *tmp;
|
|
|
|
-
|
|
|
|
- BUG_ON(!sig);
|
|
|
|
- list_for_each_entry_safe(ctx, tmp, &sighand->signalfd_list, lnk) {
|
|
|
|
- /*
|
|
|
|
- * We use a negative signal value as a way to broadcast that the
|
|
|
|
- * sighand has been orphaned, so that we can notify all the
|
|
|
|
- * listeners about this. Remember the ctx->sigmask is inverted,
|
|
|
|
- * so if the user is interested in a signal, that corresponding
|
|
|
|
- * bit will be zero.
|
|
|
|
- */
|
|
|
|
- if (sig < 0) {
|
|
|
|
- if (ctx->tsk == tsk) {
|
|
|
|
- ctx->tsk = NULL;
|
|
|
|
- list_del_init(&ctx->lnk);
|
|
|
|
- wake_up(&ctx->wqh);
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- if (!sigismember(&ctx->sigmask, sig))
|
|
|
|
- wake_up(&ctx->wqh);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-static void signalfd_cleanup(struct signalfd_ctx *ctx)
|
|
|
|
-{
|
|
|
|
- struct signalfd_lockctx lk;
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
- * This is tricky. If the sighand is gone, we do not need to remove
|
|
|
|
- * context from the list, the list itself won't be there anymore.
|
|
|
|
- */
|
|
|
|
- if (signalfd_lock(ctx, &lk)) {
|
|
|
|
- list_del(&ctx->lnk);
|
|
|
|
- signalfd_unlock(&lk);
|
|
|
|
- }
|
|
|
|
- kfree(ctx);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
static int signalfd_release(struct inode *inode, struct file *file)
|
|
static int signalfd_release(struct inode *inode, struct file *file)
|
|
{
|
|
{
|
|
- signalfd_cleanup(file->private_data);
|
|
|
|
|
|
+ kfree(file->private_data);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -130,23 +42,15 @@ static unsigned int signalfd_poll(struct file *file, poll_table *wait)
|
|
{
|
|
{
|
|
struct signalfd_ctx *ctx = file->private_data;
|
|
struct signalfd_ctx *ctx = file->private_data;
|
|
unsigned int events = 0;
|
|
unsigned int events = 0;
|
|
- struct signalfd_lockctx lk;
|
|
|
|
|
|
|
|
- poll_wait(file, &ctx->wqh, wait);
|
|
|
|
|
|
+ poll_wait(file, ¤t->sighand->signalfd_wqh, wait);
|
|
|
|
|
|
- /*
|
|
|
|
- * Let the caller get a POLLIN in this case, ala socket recv() when
|
|
|
|
- * the peer disconnects.
|
|
|
|
- */
|
|
|
|
- if (signalfd_lock(ctx, &lk)) {
|
|
|
|
- if ((lk.tsk == current &&
|
|
|
|
- next_signal(&lk.tsk->pending, &ctx->sigmask) > 0) ||
|
|
|
|
- next_signal(&lk.tsk->signal->shared_pending,
|
|
|
|
- &ctx->sigmask) > 0)
|
|
|
|
- events |= POLLIN;
|
|
|
|
- signalfd_unlock(&lk);
|
|
|
|
- } else
|
|
|
|
|
|
+ spin_lock_irq(¤t->sighand->siglock);
|
|
|
|
+ if (next_signal(¤t->pending, &ctx->sigmask) ||
|
|
|
|
+ next_signal(¤t->signal->shared_pending,
|
|
|
|
+ &ctx->sigmask))
|
|
events |= POLLIN;
|
|
events |= POLLIN;
|
|
|
|
+ spin_unlock_irq(¤t->sighand->siglock);
|
|
|
|
|
|
return events;
|
|
return events;
|
|
}
|
|
}
|
|
@@ -219,59 +123,46 @@ static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, siginfo_t *info,
|
|
int nonblock)
|
|
int nonblock)
|
|
{
|
|
{
|
|
ssize_t ret;
|
|
ssize_t ret;
|
|
- struct signalfd_lockctx lk;
|
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
|
|
- if (!signalfd_lock(ctx, &lk))
|
|
|
|
- return 0;
|
|
|
|
-
|
|
|
|
- ret = dequeue_signal(lk.tsk, &ctx->sigmask, info);
|
|
|
|
|
|
+ spin_lock_irq(¤t->sighand->siglock);
|
|
|
|
+ ret = dequeue_signal(current, &ctx->sigmask, info);
|
|
switch (ret) {
|
|
switch (ret) {
|
|
case 0:
|
|
case 0:
|
|
if (!nonblock)
|
|
if (!nonblock)
|
|
break;
|
|
break;
|
|
ret = -EAGAIN;
|
|
ret = -EAGAIN;
|
|
default:
|
|
default:
|
|
- signalfd_unlock(&lk);
|
|
|
|
|
|
+ spin_unlock_irq(¤t->sighand->siglock);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
- add_wait_queue(&ctx->wqh, &wait);
|
|
|
|
|
|
+ add_wait_queue(¤t->sighand->signalfd_wqh, &wait);
|
|
for (;;) {
|
|
for (;;) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
- ret = dequeue_signal(lk.tsk, &ctx->sigmask, info);
|
|
|
|
- signalfd_unlock(&lk);
|
|
|
|
|
|
+ ret = dequeue_signal(current, &ctx->sigmask, info);
|
|
if (ret != 0)
|
|
if (ret != 0)
|
|
break;
|
|
break;
|
|
if (signal_pending(current)) {
|
|
if (signal_pending(current)) {
|
|
ret = -ERESTARTSYS;
|
|
ret = -ERESTARTSYS;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+ spin_unlock_irq(¤t->sighand->siglock);
|
|
schedule();
|
|
schedule();
|
|
- ret = signalfd_lock(ctx, &lk);
|
|
|
|
- if (unlikely(!ret)) {
|
|
|
|
- /*
|
|
|
|
- * Let the caller read zero byte, ala socket
|
|
|
|
- * recv() when the peer disconnect. This test
|
|
|
|
- * must be done before doing a dequeue_signal(),
|
|
|
|
- * because if the sighand has been orphaned,
|
|
|
|
- * the dequeue_signal() call is going to crash
|
|
|
|
- * because ->sighand will be long gone.
|
|
|
|
- */
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ spin_lock_irq(¤t->sighand->siglock);
|
|
}
|
|
}
|
|
|
|
+ spin_unlock_irq(¤t->sighand->siglock);
|
|
|
|
|
|
- remove_wait_queue(&ctx->wqh, &wait);
|
|
|
|
|
|
+ remove_wait_queue(¤t->sighand->signalfd_wqh, &wait);
|
|
__set_current_state(TASK_RUNNING);
|
|
__set_current_state(TASK_RUNNING);
|
|
|
|
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
- * Returns either the size of a "struct signalfd_siginfo", or zero if the
|
|
|
|
- * sighand we are attached to, has been orphaned. The "count" parameter
|
|
|
|
- * must be at least the size of a "struct signalfd_siginfo".
|
|
|
|
|
|
+ * Returns a multiple of the size of a "struct signalfd_siginfo", or a negative
|
|
|
|
+ * error code. The "count" parameter must be at least the size of a
|
|
|
|
+ * "struct signalfd_siginfo".
|
|
*/
|
|
*/
|
|
static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count,
|
|
static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
loff_t *ppos)
|
|
@@ -287,7 +178,6 @@ static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count,
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
|
|
siginfo = (struct signalfd_siginfo __user *) buf;
|
|
siginfo = (struct signalfd_siginfo __user *) buf;
|
|
-
|
|
|
|
do {
|
|
do {
|
|
ret = signalfd_dequeue(ctx, &info, nonblock);
|
|
ret = signalfd_dequeue(ctx, &info, nonblock);
|
|
if (unlikely(ret <= 0))
|
|
if (unlikely(ret <= 0))
|
|
@@ -300,7 +190,7 @@ static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count,
|
|
nonblock = 1;
|
|
nonblock = 1;
|
|
} while (--count);
|
|
} while (--count);
|
|
|
|
|
|
- return total ? total : ret;
|
|
|
|
|
|
+ return total ? total: ret;
|
|
}
|
|
}
|
|
|
|
|
|
static const struct file_operations signalfd_fops = {
|
|
static const struct file_operations signalfd_fops = {
|
|
@@ -309,20 +199,13 @@ static const struct file_operations signalfd_fops = {
|
|
.read = signalfd_read,
|
|
.read = signalfd_read,
|
|
};
|
|
};
|
|
|
|
|
|
-/*
|
|
|
|
- * Create a file descriptor that is associated with our signal
|
|
|
|
- * state. We can pass it around to others if we want to, but
|
|
|
|
- * it will always be _our_ signal state.
|
|
|
|
- */
|
|
|
|
asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask)
|
|
asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemask)
|
|
{
|
|
{
|
|
int error;
|
|
int error;
|
|
sigset_t sigmask;
|
|
sigset_t sigmask;
|
|
struct signalfd_ctx *ctx;
|
|
struct signalfd_ctx *ctx;
|
|
- struct sighand_struct *sighand;
|
|
|
|
struct file *file;
|
|
struct file *file;
|
|
struct inode *inode;
|
|
struct inode *inode;
|
|
- struct signalfd_lockctx lk;
|
|
|
|
|
|
|
|
if (sizemask != sizeof(sigset_t) ||
|
|
if (sizemask != sizeof(sigset_t) ||
|
|
copy_from_user(&sigmask, user_mask, sizeof(sigmask)))
|
|
copy_from_user(&sigmask, user_mask, sizeof(sigmask)))
|
|
@@ -335,17 +218,7 @@ asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemas
|
|
if (!ctx)
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
- init_waitqueue_head(&ctx->wqh);
|
|
|
|
ctx->sigmask = sigmask;
|
|
ctx->sigmask = sigmask;
|
|
- ctx->tsk = current->group_leader;
|
|
|
|
-
|
|
|
|
- sighand = current->sighand;
|
|
|
|
- /*
|
|
|
|
- * Add this fd to the list of signal listeners.
|
|
|
|
- */
|
|
|
|
- spin_lock_irq(&sighand->siglock);
|
|
|
|
- list_add_tail(&ctx->lnk, &sighand->signalfd_list);
|
|
|
|
- spin_unlock_irq(&sighand->siglock);
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
* When we call this, the initialization must be complete, since
|
|
* When we call this, the initialization must be complete, since
|
|
@@ -364,23 +237,18 @@ asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemas
|
|
fput(file);
|
|
fput(file);
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
- /*
|
|
|
|
- * We need to be prepared of the fact that the sighand this fd
|
|
|
|
- * is attached to, has been detched. In that case signalfd_lock()
|
|
|
|
- * will return 0, and we'll just skip setting the new mask.
|
|
|
|
- */
|
|
|
|
- if (signalfd_lock(ctx, &lk)) {
|
|
|
|
- ctx->sigmask = sigmask;
|
|
|
|
- signalfd_unlock(&lk);
|
|
|
|
- }
|
|
|
|
- wake_up(&ctx->wqh);
|
|
|
|
|
|
+ spin_lock_irq(¤t->sighand->siglock);
|
|
|
|
+ ctx->sigmask = sigmask;
|
|
|
|
+ spin_unlock_irq(¤t->sighand->siglock);
|
|
|
|
+
|
|
|
|
+ wake_up(¤t->sighand->signalfd_wqh);
|
|
fput(file);
|
|
fput(file);
|
|
}
|
|
}
|
|
|
|
|
|
return ufd;
|
|
return ufd;
|
|
|
|
|
|
err_fdalloc:
|
|
err_fdalloc:
|
|
- signalfd_cleanup(ctx);
|
|
|
|
|
|
+ kfree(ctx);
|
|
return error;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
|