Home | History | Annotate | Line # | Download | only in src
      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 
     17 static const char *vdagent_virtio_filename;
     18 static int virtio_fd;
     19 static int virtio_client_fd = -1;
     20 static SpiceWatch *virtio_client_watch;
     21 
     22 typedef struct XSpiceVdagentCharDeviceInstance {
     23     SpiceCharDeviceInstance base;
     24     qxl_screen_t *qxl;
     25 } XSpiceVdagentCharDeviceInstance;
     26 
     27 static XSpiceVdagentCharDeviceInstance vdagent_sin = {
     28     .base = {
     29         .subtype = "vdagent"
     30     }
     31 };
     32 
     33 static 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 
     47 static 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 
     70 static 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
     79 static void vmc_event(SpiceCharDeviceInstance *sin, uint8_t event)
     80 {
     81 }
     82 #endif
     83 
     84 static void vmc_state(SpiceCharDeviceInstance *sin, int connected)
     85 {
     86 }
     87 
     88 static 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 
    101 static 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 
    131 error:
    132     if (virtio_client_fd != -1) {
    133         close(virtio_client_fd);
    134         virtio_client_fd = -1;
    135     }
    136 }
    137 
    138 void 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