diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2013-02-08 14:58:03 +1100 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2013-02-08 14:58:03 +1100 |
commit | df36655e17d85aeab6efbb36096b4120d8d3ad73 (patch) | |
tree | 6f0f80d42a96f03f9ff39e9b50b3344c5d6cf80e /fs/signalfd.c | |
parent | 61e5e6a7272e995083e4a23972aa9e0dc6e7d9ef (diff) | |
parent | 830ddd56fdb4a32e7e7227cec0ee8ac1721fba45 (diff) |
Merge branch 'akpm/master'
Diffstat (limited to 'fs/signalfd.c')
-rw-r--r-- | fs/signalfd.c | 65 |
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(¤t->sighand->signalfd_wqh); fdput(f); } - +out: return ufd; } |