diff options
Diffstat (limited to 'fs/signalfd.c')
| -rw-r--r-- | fs/signalfd.c | 423 |
1 files changed, 209 insertions, 214 deletions
diff --git a/fs/signalfd.c b/fs/signalfd.c index 7cfeab412b4..424b7b65321 100644 --- a/fs/signalfd.c +++ b/fs/signalfd.c @@ -11,6 +11,10 @@ * Now using anonymous inode source. * Thanks to Oleg Nesterov for useful code review and suggestions. * More comments and suggestions from Arnd Bergmann. + * Sat May 19, 2007: Davi E. M. Arnaut <davi@haxent.com.br> + * 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> @@ -18,103 +22,38 @@ #include <linux/init.h> #include <linux/fs.h> #include <linux/sched.h> +#include <linux/slab.h> #include <linux/kernel.h> #include <linux/signal.h> #include <linux/list.h> #include <linux/anon_inodes.h> #include <linux/signalfd.h> +#include <linux/syscalls.h> +#include <linux/proc_fs.h> +#include <linux/compat.h> -struct signalfd_ctx { - struct list_head lnk; - wait_queue_head_t wqh; - 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) +void signalfd_cleanup(struct sighand_struct *sighand) { - 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 && !ctx->tsk) { - unlock_task_sighand(lk->tsk, &lk->flags); - sighand = NULL; - } - - return sighand != NULL; -} - -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; - + wait_queue_head_t *wqh = &sighand->signalfd_wqh; /* - * 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. + * The lockless check can race with remove_wait_queue() in progress, + * but in this case its caller should run under rcu_read_lock() and + * sighand_cachep is SLAB_DESTROY_BY_RCU, we can safely return. */ - if (signalfd_lock(ctx, &lk)) { - list_del(&ctx->lnk); - signalfd_unlock(&lk); - } - kfree(ctx); + if (likely(!waitqueue_active(wqh))) + return; + + /* wait_queue_t->func(POLLFREE) should do remove_wait_queue() */ + wake_up_poll(wqh, POLLHUP | POLLFREE); } +struct signalfd_ctx { + sigset_t sigmask; +}; + static int signalfd_release(struct inode *inode, struct file *file) { - signalfd_cleanup(file->private_data); + kfree(file->private_data); return 0; } @@ -122,22 +61,15 @@ static unsigned int signalfd_poll(struct file *file, poll_table *wait) { struct signalfd_ctx *ctx = file->private_data; 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 (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; + spin_unlock_irq(¤t->sighand->siglock); return events; } @@ -153,7 +85,7 @@ static int signalfd_copyinfo(struct signalfd_siginfo __user *uinfo, BUILD_BUG_ON(sizeof(struct signalfd_siginfo) != 128); /* - * Unused memebers should be zero ... + * Unused members should be zero ... */ err = __clear_user(uinfo, sizeof(*uinfo)); @@ -161,135 +93,182 @@ static int signalfd_copyinfo(struct signalfd_siginfo __user *uinfo, * If you change siginfo_t structure, please be sure * this code is fixed accordingly. */ - err |= __put_user(kinfo->si_signo, &uinfo->signo); - err |= __put_user(kinfo->si_errno, &uinfo->err); - err |= __put_user((short)kinfo->si_code, &uinfo->code); + err |= __put_user(kinfo->si_signo, &uinfo->ssi_signo); + err |= __put_user(kinfo->si_errno, &uinfo->ssi_errno); + err |= __put_user((short) kinfo->si_code, &uinfo->ssi_code); switch (kinfo->si_code & __SI_MASK) { case __SI_KILL: - err |= __put_user(kinfo->si_pid, &uinfo->pid); - err |= __put_user(kinfo->si_uid, &uinfo->uid); + err |= __put_user(kinfo->si_pid, &uinfo->ssi_pid); + err |= __put_user(kinfo->si_uid, &uinfo->ssi_uid); break; case __SI_TIMER: - err |= __put_user(kinfo->si_tid, &uinfo->tid); - err |= __put_user(kinfo->si_overrun, &uinfo->overrun); - err |= __put_user((long)kinfo->si_ptr, &uinfo->svptr); + err |= __put_user(kinfo->si_tid, &uinfo->ssi_tid); + err |= __put_user(kinfo->si_overrun, &uinfo->ssi_overrun); + err |= __put_user((long) kinfo->si_ptr, &uinfo->ssi_ptr); + err |= __put_user(kinfo->si_int, &uinfo->ssi_int); break; case __SI_POLL: - err |= __put_user(kinfo->si_band, &uinfo->band); - err |= __put_user(kinfo->si_fd, &uinfo->fd); + err |= __put_user(kinfo->si_band, &uinfo->ssi_band); + err |= __put_user(kinfo->si_fd, &uinfo->ssi_fd); break; case __SI_FAULT: - err |= __put_user((long)kinfo->si_addr, &uinfo->addr); + err |= __put_user((long) kinfo->si_addr, &uinfo->ssi_addr); #ifdef __ARCH_SI_TRAPNO - err |= __put_user(kinfo->si_trapno, &uinfo->trapno); + err |= __put_user(kinfo->si_trapno, &uinfo->ssi_trapno); +#endif +#ifdef BUS_MCEERR_AO + /* + * Other callers might not initialize the si_lsb field, + * so check explicitly for the right codes here. + */ + if (kinfo->si_code == BUS_MCEERR_AR || + kinfo->si_code == BUS_MCEERR_AO) + err |= __put_user((short) kinfo->si_addr_lsb, + &uinfo->ssi_addr_lsb); #endif break; case __SI_CHLD: - err |= __put_user(kinfo->si_pid, &uinfo->pid); - err |= __put_user(kinfo->si_uid, &uinfo->uid); - err |= __put_user(kinfo->si_status, &uinfo->status); - err |= __put_user(kinfo->si_utime, &uinfo->utime); - err |= __put_user(kinfo->si_stime, &uinfo->stime); + err |= __put_user(kinfo->si_pid, &uinfo->ssi_pid); + err |= __put_user(kinfo->si_uid, &uinfo->ssi_uid); + err |= __put_user(kinfo->si_status, &uinfo->ssi_status); + err |= __put_user(kinfo->si_utime, &uinfo->ssi_utime); + err |= __put_user(kinfo->si_stime, &uinfo->ssi_stime); break; case __SI_RT: /* This is not generated by the kernel as of now. */ case __SI_MESGQ: /* But this is */ - err |= __put_user(kinfo->si_pid, &uinfo->pid); - err |= __put_user(kinfo->si_uid, &uinfo->uid); - err |= __put_user((long)kinfo->si_ptr, &uinfo->svptr); + err |= __put_user(kinfo->si_pid, &uinfo->ssi_pid); + err |= __put_user(kinfo->si_uid, &uinfo->ssi_uid); + err |= __put_user((long) kinfo->si_ptr, &uinfo->ssi_ptr); + err |= __put_user(kinfo->si_int, &uinfo->ssi_int); break; - default: /* this is just in case for now ... */ - err |= __put_user(kinfo->si_pid, &uinfo->pid); - err |= __put_user(kinfo->si_uid, &uinfo->uid); + default: + /* + * This case catches also the signals queued by sigqueue(). + */ + err |= __put_user(kinfo->si_pid, &uinfo->ssi_pid); + err |= __put_user(kinfo->si_uid, &uinfo->ssi_uid); + err |= __put_user((long) kinfo->si_ptr, &uinfo->ssi_ptr); + err |= __put_user(kinfo->si_int, &uinfo->ssi_int); break; } return err ? -EFAULT: sizeof(*uinfo); } +static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, siginfo_t *info, + int nonblock) +{ + ssize_t ret; + DECLARE_WAITQUEUE(wait, current); + + spin_lock_irq(¤t->sighand->siglock); + ret = dequeue_signal(current, &ctx->sigmask, info); + switch (ret) { + case 0: + if (!nonblock) + break; + ret = -EAGAIN; + default: + spin_unlock_irq(¤t->sighand->siglock); + return ret; + } + + add_wait_queue(¤t->sighand->signalfd_wqh, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + ret = dequeue_signal(current, &ctx->sigmask, info); + if (ret != 0) + break; + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + spin_unlock_irq(¤t->sighand->siglock); + schedule(); + spin_lock_irq(¤t->sighand->siglock); + } + spin_unlock_irq(¤t->sighand->siglock); + + remove_wait_queue(¤t->sighand->signalfd_wqh, &wait); + __set_current_state(TASK_RUNNING); + + 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, loff_t *ppos) { struct signalfd_ctx *ctx = file->private_data; - ssize_t res = 0; - int locked, signo; + struct signalfd_siginfo __user *siginfo; + int nonblock = file->f_flags & O_NONBLOCK; + ssize_t ret, total = 0; siginfo_t info; - struct signalfd_lockctx lk; - DECLARE_WAITQUEUE(wait, current); - if (count < sizeof(struct signalfd_siginfo)) + count /= sizeof(struct signalfd_siginfo); + if (!count) return -EINVAL; - locked = signalfd_lock(ctx, &lk); - if (!locked) - return 0; - res = -EAGAIN; - signo = dequeue_signal(lk.tsk, &ctx->sigmask, &info); - if (signo == 0 && !(file->f_flags & O_NONBLOCK)) { - add_wait_queue(&ctx->wqh, &wait); - for (;;) { - set_current_state(TASK_INTERRUPTIBLE); - signo = dequeue_signal(lk.tsk, &ctx->sigmask, &info); - if (signo != 0) - break; - if (signal_pending(current)) { - res = -ERESTARTSYS; - break; - } - signalfd_unlock(&lk); - schedule(); - locked = signalfd_lock(ctx, &lk); - if (unlikely(!locked)) { - /* - * 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. - */ - res = 0; - break; - } - } - remove_wait_queue(&ctx->wqh, &wait); - __set_current_state(TASK_RUNNING); - } - if (likely(locked)) - signalfd_unlock(&lk); - if (likely(signo)) - res = signalfd_copyinfo((struct signalfd_siginfo __user *) buf, - &info); - return res; + siginfo = (struct signalfd_siginfo __user *) buf; + do { + ret = signalfd_dequeue(ctx, &info, nonblock); + if (unlikely(ret <= 0)) + break; + ret = signalfd_copyinfo(siginfo, &info); + if (ret < 0) + break; + siginfo++; + total += ret; + nonblock = 1; + } while (--count); + + return total ? total: ret; +} + +#ifdef CONFIG_PROC_FS +static int signalfd_show_fdinfo(struct seq_file *m, struct file *f) +{ + struct signalfd_ctx *ctx = f->private_data; + sigset_t sigmask; + + sigmask = ctx->sigmask; + signotset(&sigmask); + render_sigset_t(m, "sigmask:\t", &sigmask); + + return 0; } +#endif static const struct file_operations signalfd_fops = { +#ifdef CONFIG_PROC_FS + .show_fdinfo = signalfd_show_fdinfo, +#endif .release = signalfd_release, .poll = signalfd_poll, .read = signalfd_read, + .llseek = noop_llseek, }; -/* - * 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) +SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask, + size_t, sizemask, int, flags) { - int error; sigset_t sigmask; struct signalfd_ctx *ctx; - struct sighand_struct *sighand; - struct file *file; - struct inode *inode; - struct signalfd_lockctx lk; + + /* Check the SFD_* constants for consistency. */ + BUILD_BUG_ON(SFD_CLOEXEC != O_CLOEXEC); + BUILD_BUG_ON(SFD_NONBLOCK != O_NONBLOCK); + + if (flags & ~(SFD_CLOEXEC | SFD_NONBLOCK)) + return -EINVAL; if (sizemask != sizeof(sigset_t) || copy_from_user(&sigmask, user_mask, sizeof(sigmask))) - return error = -EINVAL; + return -EINVAL; sigdelsetmask(&sigmask, sigmask(SIGKILL) | sigmask(SIGSTOP)); signotset(&sigmask); @@ -298,52 +277,68 @@ asmlinkage long sys_signalfd(int ufd, sigset_t __user *user_mask, size_t sizemas if (!ctx) return -ENOMEM; - init_waitqueue_head(&ctx->wqh); ctx->sigmask = sigmask; - ctx->tsk = current; - - 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 * anon_inode_getfd() will install the fd. */ - error = anon_inode_getfd(&ufd, &inode, &file, "[signalfd]", - &signalfd_fops, ctx); - if (error) - goto err_fdalloc; + ufd = anon_inode_getfd("[signalfd]", &signalfd_fops, ctx, + O_RDWR | (flags & (O_CLOEXEC | O_NONBLOCK))); + if (ufd < 0) + kfree(ctx); } else { - file = fget(ufd); - if (!file) + struct fd f = fdget(ufd); + if (!f.file) return -EBADF; - ctx = file->private_data; - if (file->f_op != &signalfd_fops) { - fput(file); + ctx = f.file->private_data; + if (f.file->f_op != &signalfd_fops) { + fdput(f); 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); - fput(file); + spin_lock_irq(¤t->sighand->siglock); + ctx->sigmask = sigmask; + spin_unlock_irq(¤t->sighand->siglock); + + wake_up(¤t->sighand->signalfd_wqh); + fdput(f); } return ufd; +} -err_fdalloc: - signalfd_cleanup(ctx); - return error; +SYSCALL_DEFINE3(signalfd, int, ufd, sigset_t __user *, user_mask, + size_t, sizemask) +{ + return sys_signalfd4(ufd, user_mask, sizemask, 0); } +#ifdef CONFIG_COMPAT +COMPAT_SYSCALL_DEFINE4(signalfd4, int, ufd, + const compat_sigset_t __user *,sigmask, + compat_size_t, sigsetsize, + int, flags) +{ + compat_sigset_t ss32; + sigset_t tmp; + sigset_t __user *ksigmask; + + if (sigsetsize != sizeof(compat_sigset_t)) + return -EINVAL; + if (copy_from_user(&ss32, sigmask, sizeof(ss32))) + return -EFAULT; + sigset_from_compat(&tmp, &ss32); + ksigmask = compat_alloc_user_space(sizeof(sigset_t)); + if (copy_to_user(ksigmask, &tmp, sizeof(sigset_t))) + return -EFAULT; + + return sys_signalfd4(ufd, ksigmask, sizeof(sigset_t), flags); +} + +COMPAT_SYSCALL_DEFINE3(signalfd, int, ufd, + const compat_sigset_t __user *,sigmask, + compat_size_t, sigsetsize) +{ + return compat_sys_signalfd4(ufd, sigmask, sigsetsize, 0); +} +#endif |
