1#ifdef HAVE_CONFIG_H
2#include "config.h"
3#endif
4
5#include <errno.h>
6#include <sys/socket.h>
7#include <sys/un.h>
8#include <fcntl.h>
9#include <unistd.h>
10
11#include "qxl_option_helpers.h"
12
13#include "spiceqxl_util.h"
14#include "spiceqxl_uinput.h"
15#include "spiceqxl_vdagent.h"
16
17static const char *vdagent_virtio_filename;
18static int virtio_fd;
19static int virtio_client_fd = -1;
20static SpiceWatch *virtio_client_watch;
21
22typedef struct XSpiceVdagentCharDeviceInstance {
23    SpiceCharDeviceInstance base;
24    qxl_screen_t *qxl;
25} XSpiceVdagentCharDeviceInstance;
26
27static XSpiceVdagentCharDeviceInstance vdagent_sin = {
28    .base = {
29        .subtype = "vdagent"
30    }
31};
32
33static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
34{
35    int written;
36
37    if (virtio_client_fd == -1) {
38        return 0;
39    }
40    written = send(virtio_client_fd, buf, len, 0);
41    if (written != len) {
42        fprintf(stderr, "%s: ERROR: short write to vdagentd - TODO buffering\n", __func__);
43    }
44    return written;
45}
46
47static int vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len)
48{
49    int nbytes;
50
51    if (virtio_client_fd == -1) {
52        return 0;
53    }
54    nbytes = recv(virtio_client_fd, buf, len, 0);
55    if (nbytes <= 0) {
56        if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
57            return 0;
58        }
59        fprintf(stderr, "ERROR: vdagent died\n");
60        close(virtio_client_fd);
61        virtio_client_fd = -1;
62        vdagent_sin.qxl->core->watch_remove(virtio_client_watch);
63        virtio_client_watch = NULL;
64        spice_server_remove_interface(&vdagent_sin.base.base);
65        spiceqxl_uinput_watch(vdagent_sin.qxl, FALSE);
66    }
67    return nbytes;
68}
69
70static void on_read_available(int fd, int event, void *opaque)
71{
72    if (virtio_client_fd == -1) {
73        return;
74    }
75    spice_server_char_device_wakeup(&vdagent_sin.base);
76}
77
78#if SPICE_SERVER_VERSION >= 0x000c02
79static void vmc_event(SpiceCharDeviceInstance *sin, uint8_t event)
80{
81}
82#endif
83
84static void vmc_state(SpiceCharDeviceInstance *sin, int connected)
85{
86}
87
88static SpiceCharDeviceInterface vmc_interface = {
89    .base.type          = SPICE_INTERFACE_CHAR_DEVICE,
90    .base.description   = "Xspice virtual channel char device",
91    .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
92    .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
93    .state              = vmc_state,
94    .write              = vmc_write,
95    .read               = vmc_read,
96#if SPICE_SERVER_VERSION >= 0x000c02
97    .event              = vmc_event,
98#endif
99};
100
101static void on_accept(int fd, int event, void *opaque)
102{
103    qxl_screen_t *qxl = opaque;
104    struct sockaddr_un address;
105    socklen_t length = sizeof(address);
106    int flags;
107
108    virtio_client_fd = accept(virtio_fd, (struct sockaddr *)&address, &length);
109    if (virtio_client_fd == -1) {
110        fprintf(stderr, "error accepting on unix domain socket: %s\n", strerror(errno));
111        return;
112    }
113    flags = fcntl(virtio_client_fd, F_GETFL);
114    if (flags == -1) {
115        fprintf(stderr, "error getting flags from uds client fd: %s\n", strerror(errno));
116        goto error;
117    }
118    if (fcntl(virtio_client_fd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC) == -1) {
119        fprintf(stderr, "error setting CLOEXEC & NONBLOCK flags from uds client fd: %s\n",
120                strerror(errno));
121        goto error;
122    }
123    virtio_client_watch = qxl->core->watch_add(virtio_client_fd, SPICE_WATCH_EVENT_READ
124        /* TODO - SPICE_WATCH_EVENT_WRITE */, on_read_available, qxl);
125
126    spice_server_add_interface(qxl->spice_server, &vdagent_sin.base.base);
127    spiceqxl_uinput_watch(qxl, TRUE);
128
129    return;
130
131error:
132    if (virtio_client_fd != -1) {
133        close(virtio_client_fd);
134        virtio_client_fd = -1;
135    }
136}
137
138void spiceqxl_vdagent_init(qxl_screen_t *qxl)
139{
140    struct sockaddr_un address;
141    int c;
142    int enabled;
143
144    vdagent_sin.qxl = qxl;
145    vdagent_virtio_filename = get_str_option(qxl->options, OPTION_SPICE_VDAGENT_VIRTIO_PATH,
146               "XSPICE_VDAGENT_VIRTIO_PATH");
147    enabled = get_bool_option(qxl->options, OPTION_SPICE_VDAGENT_ENABLED, "XSPICE_VDAGENT_ENABLED");
148
149    if (!enabled || !vdagent_virtio_filename) {
150        return;
151    }
152
153    virtio_fd = socket(PF_UNIX, SOCK_STREAM, 0);
154    if (virtio_fd == -1) {
155        fprintf(stderr, "error creating unix domain socket\n");
156        return;
157    }
158    address.sun_family = AF_UNIX;
159    snprintf(address.sun_path, sizeof(address.sun_path), "%s", vdagent_virtio_filename);
160    c = bind(virtio_fd, (struct sockaddr *)&address, sizeof(address));
161    if (c != 0) {
162        fprintf(stderr, "error binding unix domain socket to %s: %s\n",
163                vdagent_virtio_filename, strerror(errno));
164        return;
165    }
166    spiceqxl_chown_agent_file(qxl, vdagent_virtio_filename);
167    c = listen(virtio_fd, 1);
168    if (c != 0) {
169        fprintf(stderr, "error listening to unix domain socket: %s\n", strerror(errno));
170        return;
171    }
172    qxl->core->watch_add(virtio_fd, SPICE_WATCH_EVENT_READ
173        /* TODO - SPICE_WATCH_EVENT_WRITE */, on_accept, qxl);
174
175    vdagent_sin.base.base.sif = &vmc_interface.base;
176    spiceqxl_uinput_init(qxl);
177}
178