diff options
author | Andrey Vagin <avagin@openvz.org> | 2013-01-24 13:14:29 +1100 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2013-02-04 18:00:55 +1100 |
commit | f0607bbee88d001d72b4ed576c7e3ab50f7b5f3e (patch) | |
tree | c6869868145b690ee642120e59db93e5f01357d3 /fs | |
parent | b7db8ae5ad2344f8a5d9bf5153cad36e88e92e64 (diff) |
signalfd: add ability to read siginfos without dequeuing signals
pread(fd, buf, size, pos) with non-zero pos returns siginfos
without dequeuing signals.
A sequence number and a queue are encoded in pos.
pos = seq + SFD_*_OFFSET
seq is a sequence number of a signal in a queue.
SFD_PER_THREAD_QUEUE_OFFSET - read signals from a per-thread queue.
SFD_SHARED_QUEUE_OFFSET - read signals from a shared (process wide) queue.
This functionality is required for checkpointing pending signals.
v2: llseek() can't be used here, because peek_offset/f_pos/whatever
has to be shared with all processes which have this file opened.
Suppose that the task forks after sys_signalfd(). Now if parent or child
do llseek this affects them both. This is insane because signalfd is
"strange" to say at least, fork/dup/etc inherits signalfd_ctx but not
the" source" of the data. // Oleg Nesterov
v3,v4: minor cleanups
Signed-off-by: Andrey Vagin <avagin@openvz.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
Cc: David Howells <dhowells@redhat.com>
Cc: Dave Jones <davej@redhat.com>
Cc: Andrey Vagin <avagin@openvz.org>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Pavel Emelyanov <xemul@parallels.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/signalfd.c | 45 |
1 files changed, 44 insertions, 1 deletions
diff --git a/fs/signalfd.c b/fs/signalfd.c index c723b5d56c8f..229e480aac28 100644 --- a/fs/signalfd.c +++ b/fs/signalfd.c @@ -51,6 +51,44 @@ struct signalfd_ctx { sigset_t sigmask; }; +static ssize_t signalfd_peek(struct signalfd_ctx *ctx, + siginfo_t *info, loff_t *ppos) +{ + struct sigpending *pending; + struct sigqueue *q; + loff_t seq; + int ret = 0; + + if (*ppos >= SFD_SHARED_QUEUE_OFFSET) { + pending = ¤t->signal->shared_pending; + seq = *ppos - SFD_SHARED_QUEUE_OFFSET; + } else if (*ppos >= SFD_PER_THREAD_QUEUE_OFFSET) { + pending = ¤t->pending; + seq = *ppos - SFD_PER_THREAD_QUEUE_OFFSET; + } else + return -EINVAL; + + spin_lock_irq(¤t->sighand->siglock); + + list_for_each_entry(q, &pending->list, list) { + if (sigismember(&ctx->sigmask, q->info.si_signo)) + continue; + + if (seq-- == 0) { + copy_siginfo(info, &q->info); + ret = info->si_signo; + break; + } + } + + spin_unlock_irq(¤t->sighand->siglock); + + if (ret) + (*ppos)++; + + return ret; +} + static int signalfd_release(struct inode *inode, struct file *file) { kfree(file->private_data); @@ -249,7 +287,11 @@ static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count, siginfo = (struct signalfd_siginfo __user *) buf; do { - ret = signalfd_dequeue(ctx, &info, nonblock); + if (*ppos == 0) + ret = signalfd_dequeue(ctx, &info, nonblock); + else + ret = signalfd_peek(ctx, &info, ppos); + if (unlikely(ret <= 0)) break; @@ -339,6 +381,7 @@ SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask, } file->f_flags |= flags & SFD_RAW; + file->f_mode |= FMODE_PREAD; fd_install(ufd, file); } else { |