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