From b2347ec718aea0d063d714320a35a62bd2a86337 Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Thu, 7 Feb 2013 12:27:54 +1100 Subject: [PATCH] signalfd: add ability to return siginfo in a raw format signalfd should be called with the flag SFD_RAW for that. signalfd_siginfo is not full for siginfo with a negative si_code. copy_siginfo_to_user() is copied a full siginfo to user-space, if si_code is negative. signalfd_copyinfo() doesn't do that and can't be expanded, because it has not compatible format with siginfo_t. Another problem is that a constant __SI_* is removed from si_code. It's not a problem for usual applications, because they expect a defined type of siginfo (internal logic). When we want to dump pending signals, we can't predict a type of siginfo, so we should get it from kernel. The main idea of the raw format is that it should be enough for restoring exactly the same siginfo for the current process. This functionality is required for checkpointing pending signals. Signed-off-by: Andrey Vagin Cc: Alexander Viro Cc: "Paul E. McKenney" Cc: David Howells Cc: Thomas Gleixner Cc: Michael Kerrisk Cc: Pavel Emelyanov Cc: Cyrill Gorcunov Cc: Michael Kerrisk Reviewed-by: Oleg Nesterov Signed-off-by: Andrew Morton --- fs/signalfd.c | 64 ++++++++++++++++++++++++++++++++--- include/uapi/linux/signalfd.h | 1 + 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/fs/signalfd.c b/fs/signalfd.c index b53486961735..4439a813ad1d 100644 --- a/fs/signalfd.c +++ b/fs/signalfd.c @@ -30,6 +30,7 @@ #include #include #include +#include void signalfd_cleanup(struct sighand_struct *sighand) { @@ -73,6 +74,38 @@ static unsigned int signalfd_poll(struct file *file, poll_table *wait) return events; } +/* + * Copy a whole siginfo into users spaces. + * 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 *uinfo = (siginfo_t *) 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 *compat_uinfo = (compat_siginfo_t *) 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 */ @@ -205,6 +238,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 +251,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 +301,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 +311,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 +356,7 @@ SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask, wake_up(¤t->sighand->signalfd_wqh); fdput(f); } - +out: return ufd; } diff --git a/include/uapi/linux/signalfd.h b/include/uapi/linux/signalfd.h index 492c6def340d..bc3184965b89 100644 --- a/include/uapi/linux/signalfd.h +++ b/include/uapi/linux/signalfd.h @@ -15,6 +15,7 @@ /* Flags for signalfd4. */ #define SFD_CLOEXEC O_CLOEXEC #define SFD_NONBLOCK O_NONBLOCK +#define SFD_RAW O_DIRECT struct signalfd_siginfo { __u32 ssi_signo; -- 2.39.5