summaryrefslogtreecommitdiff
path: root/fs/signalfd.c
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2013-02-08 14:58:03 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2013-02-08 14:58:03 +1100
commitdf36655e17d85aeab6efbb36096b4120d8d3ad73 (patch)
tree6f0f80d42a96f03f9ff39e9b50b3344c5d6cf80e /fs/signalfd.c
parent61e5e6a7272e995083e4a23972aa9e0dc6e7d9ef (diff)
parent830ddd56fdb4a32e7e7227cec0ee8ac1721fba45 (diff)
Merge branch 'akpm/master'
Diffstat (limited to 'fs/signalfd.c')
-rw-r--r--fs/signalfd.c65
1 files changed, 60 insertions, 5 deletions
diff --git a/fs/signalfd.c b/fs/signalfd.c
index b53486961735..c723b5d56c8f 100644
--- a/fs/signalfd.c
+++ b/fs/signalfd.c
@@ -30,6 +30,7 @@
#include <linux/signalfd.h>
#include <linux/syscalls.h>
#include <linux/proc_fs.h>
+#include <linux/compat.h>
void signalfd_cleanup(struct sighand_struct *sighand)
{
@@ -74,6 +75,39 @@ static unsigned int signalfd_poll(struct file *file, poll_table *wait)
}
/*
+ * Copy a whole siginfo into userspace.
+ * The main idea of this format is that it should be enough
+ * for restoring siginfo back into the kernel.
+ */
+static int signalfd_copy_raw_info(struct signalfd_siginfo __user *siginfo,
+ siginfo_t *kinfo)
+{
+ siginfo_t __user *uinfo = (siginfo_t __user *)siginfo;
+ int err;
+
+ BUILD_BUG_ON(sizeof(siginfo_t) != sizeof(struct signalfd_siginfo));
+
+ err = __clear_user(uinfo, sizeof(*uinfo));
+
+#ifdef CONFIG_COMPAT
+ if (unlikely(is_compat_task())) {
+ compat_siginfo_t __user *compat_uinfo;
+
+ compat_uinfo = (compat_siginfo_t __user *)siginfo;
+ err |= copy_siginfo_to_user32(compat_uinfo, kinfo);
+ err |= put_user(kinfo->si_code, &compat_uinfo->si_code);
+
+ return err ? -EFAULT : sizeof(*compat_uinfo);
+ }
+#endif
+
+ err |= copy_siginfo_to_user(uinfo, kinfo);
+ err |= put_user(kinfo->si_code, &uinfo->si_code);
+
+ return err ? -EFAULT : sizeof(*uinfo);
+}
+
+/*
* Copied from copy_siginfo_to_user() in kernel/signal.c
*/
static int signalfd_copyinfo(struct signalfd_siginfo __user *uinfo,
@@ -205,6 +239,7 @@ static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count,
struct signalfd_ctx *ctx = file->private_data;
struct signalfd_siginfo __user *siginfo;
int nonblock = file->f_flags & O_NONBLOCK;
+ bool raw = file->f_flags & SFD_RAW;
ssize_t ret, total = 0;
siginfo_t info;
@@ -217,7 +252,12 @@ static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count,
ret = signalfd_dequeue(ctx, &info, nonblock);
if (unlikely(ret <= 0))
break;
- ret = signalfd_copyinfo(siginfo, &info);
+
+ if (raw)
+ ret = signalfd_copy_raw_info(siginfo, &info);
+ else
+ ret = signalfd_copyinfo(siginfo, &info);
+
if (ret < 0)
break;
siginfo++;
@@ -262,7 +302,7 @@ SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask,
BUILD_BUG_ON(SFD_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON(SFD_NONBLOCK != O_NONBLOCK);
- if (flags & ~(SFD_CLOEXEC | SFD_NONBLOCK))
+ if (flags & ~(SFD_CLOEXEC | SFD_NONBLOCK | SFD_RAW))
return -EINVAL;
if (sizemask != sizeof(sigset_t) ||
@@ -272,20 +312,35 @@ SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask,
signotset(&sigmask);
if (ufd == -1) {
+ struct file *file;
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->sigmask = sigmask;
+ ufd = get_unused_fd_flags(flags);
+ if (ufd < 0) {
+ kfree(ctx);
+ goto out;
+ }
+
/*
* When we call this, the initialization must be complete, since
* anon_inode_getfd() will install the fd.
*/
- ufd = anon_inode_getfd("[signalfd]", &signalfd_fops, ctx,
+ file = anon_inode_getfile("[signalfd]", &signalfd_fops, ctx,
O_RDWR | (flags & (O_CLOEXEC | O_NONBLOCK)));
- if (ufd < 0)
+ if (IS_ERR(file)) {
+ put_unused_fd(ufd);
+ ufd = PTR_ERR(file);
kfree(ctx);
+ goto out;
+ }
+
+ file->f_flags |= flags & SFD_RAW;
+
+ fd_install(ufd, file);
} else {
struct fd f = fdget(ufd);
if (!f.file)
@@ -302,7 +357,7 @@ SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask,
wake_up(&current->sighand->signalfd_wqh);
fdput(f);
}
-
+out:
return ufd;
}