1/*
2 * signalfd/eventfd compatibility
3 *
4 * Copyright IBM, Corp. 2008
5 *
6 * Authors:
7 *  Anthony Liguori   <aliguori@us.ibm.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2.  See
10 * the COPYING file in the top-level directory.
11 *
12 * Contributions after 2012-01-13 are licensed under the terms of the
13 * GNU GPL, version 2 or (at your option) any later version.
14 */
15
16// This is necessary to be able to include <sys/syscall.h> on Linux.
17#define _GNU_SOURCE 1
18
19#include "qemu-common.h"
20#include "qemu/compatfd.h"
21#include "qemu/thread.h"
22
23#ifdef CONFIG_SIGNALFD
24#include <sys/syscall.h>
25#endif
26
27struct sigfd_compat_info
28{
29    sigset_t mask;
30    int fd;
31};
32
33static void *sigwait_compat(void *opaque)
34{
35    struct sigfd_compat_info *info = opaque;
36
37    while (1) {
38        int sig;
39        int err;
40
41        err = sigwait(&info->mask, &sig);
42        if (err != 0) {
43            if (errno == EINTR) {
44                continue;
45            } else {
46                return NULL;
47            }
48        } else {
49            struct qemu_signalfd_siginfo buffer;
50            size_t offset = 0;
51
52            memset(&buffer, 0, sizeof(buffer));
53            buffer.ssi_signo = sig;
54
55            while (offset < sizeof(buffer)) {
56                ssize_t len;
57
58                len = write(info->fd, (char *)&buffer + offset,
59                            sizeof(buffer) - offset);
60                if (len == -1 && errno == EINTR)
61                    continue;
62
63                if (len <= 0) {
64                    return NULL;
65                }
66
67                offset += len;
68            }
69        }
70    }
71}
72
73static int qemu_signalfd_compat(const sigset_t *mask)
74{
75    struct sigfd_compat_info *info;
76    QemuThread thread;
77    int fds[2];
78
79    info = malloc(sizeof(*info));
80    if (info == NULL) {
81        errno = ENOMEM;
82        return -1;
83    }
84
85    if (pipe(fds) == -1) {
86        free(info);
87        return -1;
88    }
89
90    qemu_set_cloexec(fds[0]);
91    qemu_set_cloexec(fds[1]);
92
93    memcpy(&info->mask, mask, sizeof(*mask));
94    info->fd = fds[1];
95
96    qemu_thread_create(&thread, sigwait_compat, info, QEMU_THREAD_DETACHED);
97
98    return fds[0];
99}
100
101int qemu_signalfd(const sigset_t *mask)
102{
103#if defined(CONFIG_SIGNALFD)
104    int ret;
105
106    ret = syscall(SYS_signalfd, -1, mask, _NSIG / 8);
107    if (ret != -1) {
108        qemu_set_cloexec(ret);
109        return ret;
110    }
111#endif
112
113    return qemu_signalfd_compat(mask);
114}
115
116bool qemu_signalfd_available(void)
117{
118#ifdef CONFIG_SIGNALFD
119    sigset_t mask;
120    int fd;
121    bool ok;
122    sigemptyset(&mask);
123    errno = 0;
124    fd = syscall(SYS_signalfd, -1, &mask, _NSIG / 8);
125    ok = (errno != ENOSYS);
126    if (fd >= 0) {
127        close(fd);
128    }
129    return ok;
130#else
131    return false;
132#endif
133}
134