]> git.proxmox.com Git - mirror_qemu.git/blame - ui/spice-core.c
tests: acpi: update expected blobs
[mirror_qemu.git] / ui / spice-core.c
CommitLineData
29b0040b
GH
1/*
2 * Copyright (C) 2010 Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 or
7 * (at your option) version 3 of the License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 */
17
e16f4c87 18#include "qemu/osdep.h"
29b0040b 19#include <spice.h>
29b0040b 20
9c17d615 21#include "sysemu/sysemu.h"
54d31236 22#include "sysemu/runstate.h"
28ecbaee 23#include "ui/qemu-spice.h"
d49b6836 24#include "qemu/error-report.h"
db725815 25#include "qemu/main-loop.h"
0b8fa32f 26#include "qemu/module.h"
1de7afc9
PB
27#include "qemu/thread.h"
28#include "qemu/timer.h"
29#include "qemu/queue.h"
c448e855 30#include "qemu-x509.h"
1de7afc9 31#include "qemu/sockets.h"
e688df6b 32#include "qapi/error.h"
9af23989
MA
33#include "qapi/qapi-commands-ui.h"
34#include "qapi/qapi-events-ui.h"
1de7afc9 35#include "qemu/notify.h"
922a01a0 36#include "qemu/option.h"
99522f69 37#include "crypto/secret_common.h"
c4b63b7c 38#include "migration/misc.h"
be812c0a 39#include "hw/pci/pci_bus.h"
28ecbaee 40#include "ui/spice-display.h"
29b0040b
GH
41
42/* core bits */
43
44static SpiceServer *spice_server;
e866e239 45static Notifier migration_state;
6f8c63fb 46static const char *auth = "spice";
7572150c
GH
47static char *auth_passwd;
48static time_t auth_expires = TIME_MAX;
61c4efe2 49static int spice_migration_completed;
7cc6a25f 50static int spice_display_is_running;
a76a2f72 51static int spice_have_target_host;
29b0040b 52
f9ab6091 53static QemuThread me;
22b626e2 54
29b0040b
GH
55struct SpiceTimer {
56 QEMUTimer *timer;
29b0040b 57};
29b0040b
GH
58
59static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
60{
61 SpiceTimer *timer;
62
7267c094 63 timer = g_malloc0(sizeof(*timer));
bc72ad67 64 timer->timer = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque);
29b0040b
GH
65 return timer;
66}
67
68static void timer_start(SpiceTimer *timer, uint32_t ms)
69{
bc72ad67 70 timer_mod(timer->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + ms);
29b0040b
GH
71}
72
73static void timer_cancel(SpiceTimer *timer)
74{
bc72ad67 75 timer_del(timer->timer);
29b0040b
GH
76}
77
78static void timer_remove(SpiceTimer *timer)
79{
bc72ad67 80 timer_free(timer->timer);
7267c094 81 g_free(timer);
29b0040b
GH
82}
83
84struct SpiceWatch {
85 int fd;
29b0040b
GH
86 SpiceWatchFunc func;
87 void *opaque;
29b0040b 88};
29b0040b
GH
89
90static void watch_read(void *opaque)
91{
92 SpiceWatch *watch = opaque;
93 watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque);
94}
95
96static void watch_write(void *opaque)
97{
98 SpiceWatch *watch = opaque;
99 watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque);
100}
101
102static void watch_update_mask(SpiceWatch *watch, int event_mask)
103{
104 IOHandler *on_read = NULL;
105 IOHandler *on_write = NULL;
106
58a5d33a 107 if (event_mask & SPICE_WATCH_EVENT_READ) {
29b0040b
GH
108 on_read = watch_read;
109 }
58a5d33a 110 if (event_mask & SPICE_WATCH_EVENT_WRITE) {
3d6d306c 111 on_write = watch_write;
29b0040b
GH
112 }
113 qemu_set_fd_handler(watch->fd, on_read, on_write, watch);
114}
115
116static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
117{
118 SpiceWatch *watch;
119
7267c094 120 watch = g_malloc0(sizeof(*watch));
29b0040b
GH
121 watch->fd = fd;
122 watch->func = func;
123 watch->opaque = opaque;
29b0040b
GH
124
125 watch_update_mask(watch, event_mask);
126 return watch;
127}
128
129static void watch_remove(SpiceWatch *watch)
130{
08cc67f3 131 qemu_set_fd_handler(watch->fd, NULL, NULL, NULL);
7267c094 132 g_free(watch);
29b0040b
GH
133}
134
cb42a870
GH
135typedef struct ChannelList ChannelList;
136struct ChannelList {
137 SpiceChannelEventInfo *info;
138 QTAILQ_ENTRY(ChannelList) link;
139};
140static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list);
141
142static void channel_list_add(SpiceChannelEventInfo *info)
143{
144 ChannelList *item;
145
7267c094 146 item = g_malloc0(sizeof(*item));
cb42a870
GH
147 item->info = info;
148 QTAILQ_INSERT_TAIL(&channel_list, item, link);
149}
150
151static void channel_list_del(SpiceChannelEventInfo *info)
152{
153 ChannelList *item;
154
155 QTAILQ_FOREACH(item, &channel_list, link) {
156 if (item->info != info) {
157 continue;
158 }
159 QTAILQ_REMOVE(&channel_list, item, link);
7267c094 160 g_free(item);
cb42a870
GH
161 return;
162 }
163}
164
7cfadb6b 165static void add_addr_info(SpiceBasicInfo *info, struct sockaddr *addr, int len)
6f8c63fb
GH
166{
167 char host[NI_MAXHOST], port[NI_MAXSERV];
6f8c63fb
GH
168
169 getnameinfo(addr, len, host, sizeof(host), port, sizeof(port),
170 NI_NUMERICHOST | NI_NUMERICSERV);
6f8c63fb 171
7cfadb6b
WX
172 info->host = g_strdup(host);
173 info->port = g_strdup(port);
174 info->family = inet_netfamily(addr->sa_family);
6f8c63fb
GH
175}
176
7cfadb6b 177static void add_channel_info(SpiceChannel *sc, SpiceChannelEventInfo *info)
6f8c63fb
GH
178{
179 int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
180
7cfadb6b
WX
181 sc->connection_id = info->connection_id;
182 sc->channel_type = info->type;
183 sc->channel_id = info->id;
184 sc->tls = !!tls;
6f8c63fb
GH
185}
186
187static void channel_event(int event, SpiceChannelEventInfo *info)
188{
7cfadb6b
WX
189 SpiceServerInfo *server = g_malloc0(sizeof(*server));
190 SpiceChannel *client = g_malloc0(sizeof(*client));
6f8c63fb 191
22b626e2
GH
192 /*
193 * Spice server might have called us from spice worker thread
194 * context (happens on display channel disconnects). Spice should
195 * not do that. It isn't that easy to fix it in spice and even
196 * when it is fixed we still should cover the already released
197 * spice versions. So detect that we've been called from another
198 * thread and grab the iothread lock if so before calling qemu
199 * functions.
200 */
f9ab6091 201 bool need_lock = !qemu_thread_is_self(&me);
22b626e2
GH
202 if (need_lock) {
203 qemu_mutex_lock_iothread();
204 }
205
faa98223 206 if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) {
ddf21908
EB
207 add_addr_info(qapi_SpiceChannel_base(client),
208 (struct sockaddr *)&info->paddr_ext,
faa98223 209 info->plen_ext);
ddf21908
EB
210 add_addr_info(qapi_SpiceServerInfo_base(server),
211 (struct sockaddr *)&info->laddr_ext,
faa98223
YH
212 info->llen_ext);
213 } else {
339a475f
CF
214 error_report("spice: %s, extended address is expected",
215 __func__);
faa98223 216 }
6f8c63fb 217
7cfadb6b
WX
218 switch (event) {
219 case SPICE_CHANNEL_EVENT_CONNECTED:
ddf21908 220 qapi_event_send_spice_connected(qapi_SpiceServerInfo_base(server),
3ab72385 221 qapi_SpiceChannel_base(client));
7cfadb6b
WX
222 break;
223 case SPICE_CHANNEL_EVENT_INITIALIZED:
224 if (auth) {
7cfadb6b
WX
225 server->auth = g_strdup(auth);
226 }
6f8c63fb 227 add_channel_info(client, info);
cb42a870 228 channel_list_add(info);
3ab72385 229 qapi_event_send_spice_initialized(server, client);
7cfadb6b
WX
230 break;
231 case SPICE_CHANNEL_EVENT_DISCONNECTED:
cb42a870 232 channel_list_del(info);
ddf21908 233 qapi_event_send_spice_disconnected(qapi_SpiceServerInfo_base(server),
3ab72385 234 qapi_SpiceChannel_base(client));
7cfadb6b
WX
235 break;
236 default:
237 break;
6f8c63fb
GH
238 }
239
22b626e2
GH
240 if (need_lock) {
241 qemu_mutex_unlock_iothread();
242 }
7cfadb6b
WX
243
244 qapi_free_SpiceServerInfo(server);
245 qapi_free_SpiceChannel(client);
6f8c63fb
GH
246}
247
29b0040b
GH
248static SpiceCoreInterface core_interface = {
249 .base.type = SPICE_INTERFACE_CORE,
250 .base.description = "qemu core services",
251 .base.major_version = SPICE_INTERFACE_CORE_MAJOR,
252 .base.minor_version = SPICE_INTERFACE_CORE_MINOR,
253
254 .timer_add = timer_add,
255 .timer_start = timer_start,
256 .timer_cancel = timer_cancel,
257 .timer_remove = timer_remove,
258
259 .watch_add = watch_add,
260 .watch_update_mask = watch_update_mask,
261 .watch_remove = watch_remove,
6f8c63fb 262
6f8c63fb 263 .channel_event = channel_event,
29b0040b
GH
264};
265
026f773f 266static void migrate_connect_complete_cb(SpiceMigrateInstance *sin);
2fdd16e2 267static void migrate_end_complete_cb(SpiceMigrateInstance *sin);
026f773f
YH
268
269static const SpiceMigrateInterface migrate_interface = {
270 .base.type = SPICE_INTERFACE_MIGRATION,
271 .base.description = "migration",
272 .base.major_version = SPICE_INTERFACE_MIGRATION_MAJOR,
273 .base.minor_version = SPICE_INTERFACE_MIGRATION_MINOR,
274 .migrate_connect_complete = migrate_connect_complete_cb,
2fdd16e2 275 .migrate_end_complete = migrate_end_complete_cb,
026f773f
YH
276};
277
3b5704b2 278static SpiceMigrateInstance spice_migrate;
026f773f
YH
279
280static void migrate_connect_complete_cb(SpiceMigrateInstance *sin)
281{
3b5704b2 282 /* nothing, but libspice-server expects this cb being present. */
026f773f 283}
2fdd16e2
YH
284
285static void migrate_end_complete_cb(SpiceMigrateInstance *sin)
286{
3ab72385 287 qapi_event_send_spice_migrate_completed();
61c4efe2 288 spice_migration_completed = true;
2fdd16e2 289}
026f773f 290
9f04e09e
YH
291/* config string parsing */
292
293static int name2enum(const char *string, const char *table[], int entries)
294{
295 int i;
296
297 if (string) {
298 for (i = 0; i < entries; i++) {
299 if (!table[i]) {
300 continue;
301 }
302 if (strcmp(string, table[i]) != 0) {
303 continue;
304 }
305 return i;
306 }
307 }
308 return -1;
309}
310
311static int parse_name(const char *string, const char *optname,
312 const char *table[], int entries)
313{
314 int value = name2enum(string, table, entries);
315
316 if (value != -1) {
317 return value;
318 }
339a475f 319 error_report("spice: invalid %s: %s", optname, string);
9f04e09e
YH
320 exit(1);
321}
322
84a23f25
GH
323static const char *stream_video_names[] = {
324 [ SPICE_STREAM_VIDEO_OFF ] = "off",
325 [ SPICE_STREAM_VIDEO_ALL ] = "all",
326 [ SPICE_STREAM_VIDEO_FILTER ] = "filter",
327};
328#define parse_stream_video(_name) \
835cab85
CF
329 parse_name(_name, "stream video control", \
330 stream_video_names, ARRAY_SIZE(stream_video_names))
84a23f25 331
9f04e09e
YH
332static const char *compression_names[] = {
333 [ SPICE_IMAGE_COMPRESS_OFF ] = "off",
334 [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz",
335 [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz",
336 [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic",
337 [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz",
338 [ SPICE_IMAGE_COMPRESS_LZ ] = "lz",
339};
340#define parse_compression(_name) \
341 parse_name(_name, "image compression", \
342 compression_names, ARRAY_SIZE(compression_names))
343
344static const char *wan_compression_names[] = {
345 [ SPICE_WAN_COMPRESSION_AUTO ] = "auto",
346 [ SPICE_WAN_COMPRESSION_NEVER ] = "never",
347 [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always",
348};
349#define parse_wan_compression(_name) \
350 parse_name(_name, "wan compression", \
351 wan_compression_names, ARRAY_SIZE(wan_compression_names))
352
29b0040b
GH
353/* functions for the rest of qemu */
354
d1f29646 355static SpiceChannelList *qmp_query_spice_channels(void)
cb42a870 356{
95b3a8c8 357 SpiceChannelList *head = NULL, **tail = &head;
d1f29646 358 ChannelList *item;
cb42a870 359
d1f29646 360 QTAILQ_FOREACH(item, &channel_list, link) {
95b3a8c8 361 SpiceChannel *chan;
d1f29646 362 char host[NI_MAXHOST], port[NI_MAXSERV];
faa98223
YH
363 struct sockaddr *paddr;
364 socklen_t plen;
d1f29646 365
a4164270 366 assert(item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT);
26defe81 367
d1f29646 368 chan = g_malloc0(sizeof(*chan));
d1f29646 369
26defe81
MAL
370 paddr = (struct sockaddr *)&item->info->paddr_ext;
371 plen = item->info->plen_ext;
faa98223 372 getnameinfo(paddr, plen,
d1f29646
LC
373 host, sizeof(host), port, sizeof(port),
374 NI_NUMERICHOST | NI_NUMERICSERV);
95b3a8c8
EB
375 chan->host = g_strdup(host);
376 chan->port = g_strdup(port);
377 chan->family = inet_netfamily(paddr->sa_family);
378
379 chan->connection_id = item->info->connection_id;
380 chan->channel_type = item->info->type;
381 chan->channel_id = item->info->id;
382 chan->tls = item->info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
383
384 QAPI_LIST_APPEND(tail, chan);
cb42a870
GH
385 }
386
d1f29646 387 return head;
cb42a870
GH
388}
389
4d454574
PB
390static QemuOptsList qemu_spice_opts = {
391 .name = "spice",
392 .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head),
79216718 393 .merge_lists = true,
4d454574
PB
394 .desc = {
395 {
396 .name = "port",
397 .type = QEMU_OPT_NUMBER,
398 },{
399 .name = "tls-port",
400 .type = QEMU_OPT_NUMBER,
401 },{
402 .name = "addr",
403 .type = QEMU_OPT_STRING,
404 },{
405 .name = "ipv4",
406 .type = QEMU_OPT_BOOL,
407 },{
408 .name = "ipv6",
409 .type = QEMU_OPT_BOOL,
fe4831b1
MAL
410#ifdef SPICE_ADDR_FLAG_UNIX_ONLY
411 },{
412 .name = "unix",
413 .type = QEMU_OPT_BOOL,
414#endif
4d454574
PB
415 },{
416 .name = "password",
417 .type = QEMU_OPT_STRING,
99522f69
DB
418 },{
419 .name = "password-secret",
420 .type = QEMU_OPT_STRING,
4d454574
PB
421 },{
422 .name = "disable-ticketing",
423 .type = QEMU_OPT_BOOL,
424 },{
425 .name = "disable-copy-paste",
426 .type = QEMU_OPT_BOOL,
5ad24e5f
HG
427 },{
428 .name = "disable-agent-file-xfer",
429 .type = QEMU_OPT_BOOL,
4d454574
PB
430 },{
431 .name = "sasl",
432 .type = QEMU_OPT_BOOL,
433 },{
434 .name = "x509-dir",
435 .type = QEMU_OPT_STRING,
436 },{
437 .name = "x509-key-file",
438 .type = QEMU_OPT_STRING,
439 },{
440 .name = "x509-key-password",
441 .type = QEMU_OPT_STRING,
442 },{
443 .name = "x509-cert-file",
444 .type = QEMU_OPT_STRING,
445 },{
446 .name = "x509-cacert-file",
447 .type = QEMU_OPT_STRING,
448 },{
449 .name = "x509-dh-key-file",
450 .type = QEMU_OPT_STRING,
451 },{
452 .name = "tls-ciphers",
453 .type = QEMU_OPT_STRING,
454 },{
455 .name = "tls-channel",
456 .type = QEMU_OPT_STRING,
457 },{
458 .name = "plaintext-channel",
459 .type = QEMU_OPT_STRING,
460 },{
461 .name = "image-compression",
462 .type = QEMU_OPT_STRING,
463 },{
464 .name = "jpeg-wan-compression",
465 .type = QEMU_OPT_STRING,
466 },{
467 .name = "zlib-glz-wan-compression",
468 .type = QEMU_OPT_STRING,
469 },{
470 .name = "streaming-video",
471 .type = QEMU_OPT_STRING,
472 },{
473 .name = "agent-mouse",
474 .type = QEMU_OPT_BOOL,
475 },{
476 .name = "playback-compression",
477 .type = QEMU_OPT_BOOL,
474114b7 478 },{
4d454574
PB
479 .name = "seamless-migration",
480 .type = QEMU_OPT_BOOL,
8bf69b49
GH
481 },{
482 .name = "display",
483 .type = QEMU_OPT_STRING,
484 },{
485 .name = "head",
486 .type = QEMU_OPT_NUMBER,
474114b7
GH
487#ifdef HAVE_SPICE_GL
488 },{
489 .name = "gl",
490 .type = QEMU_OPT_BOOL,
7b525508
MAL
491 },{
492 .name = "rendernode",
493 .type = QEMU_OPT_STRING,
474114b7 494#endif
4d454574
PB
495 },
496 { /* end of list */ }
497 },
498};
499
db5732c9 500static SpiceInfo *qmp_query_spice_real(Error **errp)
cb42a870
GH
501{
502 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
cb42a870 503 int port, tls_port;
d1f29646
LC
504 const char *addr;
505 SpiceInfo *info;
6735aa99
CF
506 unsigned int major;
507 unsigned int minor;
508 unsigned int micro;
cb42a870 509
d1f29646
LC
510 info = g_malloc0(sizeof(*info));
511
3bb781f3 512 if (!spice_server || !opts) {
d1f29646
LC
513 info->enabled = false;
514 return info;
cb42a870
GH
515 }
516
d1f29646 517 info->enabled = true;
61c4efe2 518 info->migrated = spice_migration_completed;
d1f29646 519
cb42a870
GH
520 addr = qemu_opt_get(opts, "addr");
521 port = qemu_opt_get_number(opts, "port", 0);
522 tls_port = qemu_opt_get_number(opts, "tls-port", 0);
cb42a870 523
d1f29646 524 info->auth = g_strdup(auth);
4f60af9a 525 info->host = g_strdup(addr ? addr : "*");
d1f29646 526
6735aa99
CF
527 major = (SPICE_SERVER_VERSION & 0xff0000) >> 16;
528 minor = (SPICE_SERVER_VERSION & 0xff00) >> 8;
529 micro = SPICE_SERVER_VERSION & 0xff;
530 info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro);
d1f29646 531
cb42a870 532 if (port) {
d1f29646
LC
533 info->has_port = true;
534 info->port = port;
cb42a870
GH
535 }
536 if (tls_port) {
d1f29646
LC
537 info->has_tls_port = true;
538 info->tls_port = tls_port;
cb42a870
GH
539 }
540
4efee029
AL
541 info->mouse_mode = spice_server_is_server_mouse(spice_server) ?
542 SPICE_QUERY_MOUSE_MODE_SERVER :
543 SPICE_QUERY_MOUSE_MODE_CLIENT;
67be6726 544
d1f29646
LC
545 /* for compatibility with the original command */
546 info->has_channels = true;
547 info->channels = qmp_query_spice_channels();
548
549 return info;
cb42a870
GH
550}
551
9e8dd451 552static void migration_state_notifier(Notifier *notifier, void *data)
e866e239 553{
7073693b 554 MigrationState *s = data;
e866e239 555
a76a2f72
GH
556 if (!spice_have_target_host) {
557 return;
558 }
559
02edd2e7 560 if (migration_in_setup(s)) {
026f773f 561 spice_server_migrate_start(spice_server);
b82fc321
DDAG
562 } else if (migration_has_finished(s) ||
563 migration_in_postcopy_after_devices(s)) {
026f773f 564 spice_server_migrate_end(spice_server, true);
a76a2f72 565 spice_have_target_host = false;
026f773f
YH
566 } else if (migration_has_failed(s)) {
567 spice_server_migrate_end(spice_server, false);
a76a2f72 568 spice_have_target_host = false;
e866e239
GH
569 }
570}
571
572int qemu_spice_migrate_info(const char *hostname, int port, int tls_port,
3b5704b2 573 const char *subject)
e866e239 574{
edc5cb1a 575 int ret;
67be6726 576
026f773f
YH
577 ret = spice_server_migrate_connect(spice_server, hostname,
578 port, tls_port, subject);
a76a2f72 579 spice_have_target_host = true;
edc5cb1a 580 return ret;
e866e239
GH
581}
582
71df1d83
MA
583static int add_channel(void *opaque, const char *name, const char *value,
584 Error **errp)
17b6dea0
GH
585{
586 int security = 0;
587 int rc;
588
589 if (strcmp(name, "tls-channel") == 0) {
35c63329
CF
590 int *tls_port = opaque;
591 if (!*tls_port) {
9338570b
MA
592 error_setg(errp, "spice: tried to setup tls-channel"
593 " without specifying a TLS port");
594 return -1;
35c63329 595 }
17b6dea0
GH
596 security = SPICE_CHANNEL_SECURITY_SSL;
597 }
598 if (strcmp(name, "plaintext-channel") == 0) {
599 security = SPICE_CHANNEL_SECURITY_NONE;
600 }
601 if (security == 0) {
602 return 0;
603 }
604 if (strcmp(value, "default") == 0) {
605 rc = spice_server_set_channel_security(spice_server, NULL, security);
606 } else {
607 rc = spice_server_set_channel_security(spice_server, value, security);
608 }
609 if (rc != 0) {
9338570b
MA
610 error_setg(errp, "spice: failed to set channel security for %s",
611 value);
612 return -1;
17b6dea0
GH
613 }
614 return 0;
615}
616
538f0497 617static void vm_change_state_handler(void *opaque, bool running,
f5bb039c
YH
618 RunState state)
619{
f5bb039c 620 if (running) {
71d388d4 621 qemu_spice_display_start();
5b1638bc 622 } else if (state != RUN_STATE_PAUSED) {
71d388d4 623 qemu_spice_display_stop();
f5bb039c 624 }
f5bb039c
YH
625}
626
a652b120
MAL
627void qemu_spice_display_init_done(void)
628{
629 if (runstate_is_running()) {
630 qemu_spice_display_start();
631 }
632 qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
633}
634
63be30e6 635static void qemu_spice_init(void)
29b0040b
GH
636{
637 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
99522f69
DB
638 char *password = NULL;
639 const char *passwordSecret;
640 const char *str, *x509_dir, *addr,
c448e855
GH
641 *x509_key_password = NULL,
642 *x509_dh_file = NULL,
643 *tls_ciphers = NULL;
644 char *x509_key_file = NULL,
645 *x509_cert_file = NULL,
646 *x509_cacert_file = NULL;
6735aa99 647 int port, tls_port, addr_flags;
9f04e09e
YH
648 spice_image_compression_t compression;
649 spice_wan_compression_t wan_compr;
8c957053 650 bool seamless_migration;
29b0040b 651
f9ab6091 652 qemu_thread_get_self(&me);
22b626e2 653
ad1be899 654 if (!opts) {
29b0040b
GH
655 return;
656 }
657 port = qemu_opt_get_number(opts, "port", 0);
c448e855 658 tls_port = qemu_opt_get_number(opts, "tls-port", 0);
df9cb669 659 if (port < 0 || port > 65535) {
339a475f 660 error_report("spice port is out of range");
df9cb669
GH
661 exit(1);
662 }
663 if (tls_port < 0 || tls_port > 65535) {
339a475f 664 error_report("spice tls-port is out of range");
df9cb669 665 exit(1);
29b0040b 666 }
99522f69
DB
667 passwordSecret = qemu_opt_get(opts, "password-secret");
668 if (passwordSecret) {
99522f69
DB
669 if (qemu_opt_get(opts, "password")) {
670 error_report("'password' option is mutually exclusive with "
671 "'password-secret'");
672 exit(1);
673 }
674 password = qcrypto_secret_lookup_as_utf8(passwordSecret,
f9734d5d 675 &error_fatal);
99522f69
DB
676 } else {
677 str = qemu_opt_get(opts, "password");
678 if (str) {
c47c0bcb
DB
679 warn_report("'password' option is deprecated and insecure, "
680 "use 'password-secret' instead");
99522f69
DB
681 password = g_strdup(str);
682 }
683 }
29b0040b 684
c448e855
GH
685 if (tls_port) {
686 x509_dir = qemu_opt_get(opts, "x509-dir");
fe8e8327 687 if (!x509_dir) {
c448e855
GH
688 x509_dir = ".";
689 }
c448e855
GH
690
691 str = qemu_opt_get(opts, "x509-key-file");
692 if (str) {
7267c094 693 x509_key_file = g_strdup(str);
c448e855 694 } else {
6735aa99
CF
695 x509_key_file = g_strdup_printf("%s/%s", x509_dir,
696 X509_SERVER_KEY_FILE);
c448e855
GH
697 }
698
699 str = qemu_opt_get(opts, "x509-cert-file");
700 if (str) {
7267c094 701 x509_cert_file = g_strdup(str);
c448e855 702 } else {
6735aa99
CF
703 x509_cert_file = g_strdup_printf("%s/%s", x509_dir,
704 X509_SERVER_CERT_FILE);
c448e855
GH
705 }
706
707 str = qemu_opt_get(opts, "x509-cacert-file");
708 if (str) {
7267c094 709 x509_cacert_file = g_strdup(str);
c448e855 710 } else {
6735aa99
CF
711 x509_cacert_file = g_strdup_printf("%s/%s", x509_dir,
712 X509_CA_CERT_FILE);
c448e855
GH
713 }
714
715 x509_key_password = qemu_opt_get(opts, "x509-key-password");
9995c0b7 716 x509_dh_file = qemu_opt_get(opts, "x509-dh-key-file");
c448e855
GH
717 tls_ciphers = qemu_opt_get(opts, "tls-ciphers");
718 }
719
333b0eeb
GH
720 addr = qemu_opt_get(opts, "addr");
721 addr_flags = 0;
722 if (qemu_opt_get_bool(opts, "ipv4", 0)) {
723 addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY;
724 } else if (qemu_opt_get_bool(opts, "ipv6", 0)) {
725 addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY;
fe4831b1
MAL
726#ifdef SPICE_ADDR_FLAG_UNIX_ONLY
727 } else if (qemu_opt_get_bool(opts, "unix", 0)) {
728 addr_flags |= SPICE_ADDR_FLAG_UNIX_ONLY;
729#endif
333b0eeb
GH
730 }
731
29b0040b 732 spice_server = spice_server_new();
333b0eeb 733 spice_server_set_addr(spice_server, addr ? addr : "", addr_flags);
c448e855
GH
734 if (port) {
735 spice_server_set_port(spice_server, port);
736 }
737 if (tls_port) {
738 spice_server_set_tls(spice_server, tls_port,
739 x509_cacert_file,
740 x509_cert_file,
741 x509_key_file,
742 x509_key_password,
743 x509_dh_file,
744 tls_ciphers);
745 }
29b0040b 746 if (password) {
08ad2626 747 qemu_spice.set_passwd(password, false, false);
29b0040b 748 }
48b3ed0a 749 if (qemu_opt_get_bool(opts, "sasl", 0)) {
06bb8814 750 if (spice_server_set_sasl(spice_server, 1) == -1) {
339a475f 751 error_report("spice: failed to enable sasl");
48b3ed0a
MAL
752 exit(1);
753 }
b1ea7b79 754 auth = "sasl";
48b3ed0a 755 }
29b0040b 756 if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) {
6f8c63fb 757 auth = "none";
29b0040b
GH
758 spice_server_set_noauth(spice_server);
759 }
760
d4970b07
HG
761 if (qemu_opt_get_bool(opts, "disable-copy-paste", 0)) {
762 spice_server_set_agent_copypaste(spice_server, false);
763 }
d4970b07 764
5ad24e5f 765 if (qemu_opt_get_bool(opts, "disable-agent-file-xfer", 0)) {
5ad24e5f 766 spice_server_set_agent_file_xfer(spice_server, false);
5ad24e5f
HG
767 }
768
9f04e09e
YH
769 compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
770 str = qemu_opt_get(opts, "image-compression");
771 if (str) {
772 compression = parse_compression(str);
773 }
774 spice_server_set_image_compression(spice_server, compression);
775
776 wan_compr = SPICE_WAN_COMPRESSION_AUTO;
777 str = qemu_opt_get(opts, "jpeg-wan-compression");
778 if (str) {
779 wan_compr = parse_wan_compression(str);
780 }
781 spice_server_set_jpeg_compression(spice_server, wan_compr);
782
783 wan_compr = SPICE_WAN_COMPRESSION_AUTO;
784 str = qemu_opt_get(opts, "zlib-glz-wan-compression");
785 if (str) {
786 wan_compr = parse_wan_compression(str);
787 }
788 spice_server_set_zlib_glz_compression(spice_server, wan_compr);
29b0040b 789
84a23f25
GH
790 str = qemu_opt_get(opts, "streaming-video");
791 if (str) {
f61d6960 792 int streaming_video = parse_stream_video(str);
84a23f25 793 spice_server_set_streaming_video(spice_server, streaming_video);
f1d3e586
GH
794 } else {
795 spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF);
84a23f25
GH
796 }
797
798 spice_server_set_agent_mouse
799 (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1));
800 spice_server_set_playback_compression
801 (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1));
802
9338570b 803 qemu_opt_foreach(opts, add_channel, &tls_port, &error_fatal);
17b6dea0 804
4c77ee12 805 spice_server_set_name(spice_server, qemu_name ?: "QEMU " QEMU_VERSION);
9c5ce8db 806 spice_server_set_uuid(spice_server, (unsigned char *)&qemu_uuid);
d0638b18 807
8c957053
YH
808 seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0);
809 spice_server_set_seamless_migration(spice_server, seamless_migration);
06bb8814 810 spice_server_set_sasl_appname(spice_server, "qemu");
fe8e8327 811 if (spice_server_init(spice_server, &core_interface) != 0) {
339a475f 812 error_report("failed to initialize spice server");
fba810f1
GH
813 exit(1);
814 };
29b0040b 815 using_spice = 1;
864401c2 816
e866e239
GH
817 migration_state.notify = migration_state_notifier;
818 add_migration_state_change_notifier(&migration_state);
3b5704b2 819 spice_migrate.base.sif = &migrate_interface.base;
05b53636 820 qemu_spice.add_interface(&spice_migrate.base);
e866e239 821
864401c2 822 qemu_spice_input_init();
c448e855 823
641381c1 824 qemu_spice_display_stop();
f5bb039c 825
7267c094
AL
826 g_free(x509_key_file);
827 g_free(x509_cert_file);
828 g_free(x509_cacert_file);
99522f69 829 g_free(password);
afd0b409 830
474114b7
GH
831#ifdef HAVE_SPICE_GL
832 if (qemu_opt_get_bool(opts, "gl", 0)) {
569a93cb
CF
833 if ((port != 0) || (tls_port != 0)) {
834 error_report("SPICE GL support is local-only for now and "
835 "incompatible with -spice port/tls-port");
836 exit(1);
837 }
54d208ff
GH
838 if (egl_rendernode_init(qemu_opt_get(opts, "rendernode"),
839 DISPLAYGL_MODE_ON) != 0) {
daafc661
CR
840 error_report("Failed to initialize EGL render node for SPICE GL");
841 exit(1);
474114b7 842 }
daafc661 843 display_opengl = 1;
fe5c44f9 844 spice_opengl = 1;
474114b7
GH
845 }
846#endif
29b0040b
GH
847}
848
05b53636 849static int qemu_spice_add_interface(SpiceBaseInstance *sin)
29b0040b 850{
a19cbfb3
GH
851 if (!spice_server) {
852 if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) {
339a475f 853 error_report("Oops: spice configured but not active");
a19cbfb3
GH
854 exit(1);
855 }
856 /*
857 * Create a spice server instance.
858 * It does *not* listen on the network.
859 * It handles QXL local rendering only.
860 *
861 * With a command line like '-vnc :0 -vga qxl' you'll end up here.
862 */
863 spice_server = spice_server_new();
764eb39d 864 spice_server_set_sasl_appname(spice_server, "qemu");
a19cbfb3 865 spice_server_init(spice_server, &core_interface);
bfb82a28 866 qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
a19cbfb3 867 }
71d388d4 868
9fa03286
GH
869 return spice_server_add_interface(spice_server, sin);
870}
871
872static GSList *spice_consoles;
9fa03286
GH
873
874bool qemu_spice_have_display_interface(QemuConsole *con)
875{
876 if (g_slist_find(spice_consoles, con)) {
877 return true;
58ae52a8 878 }
9fa03286
GH
879 return false;
880}
58ae52a8 881
9fa03286
GH
882int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
883{
884 if (g_slist_find(spice_consoles, con)) {
885 return -1;
886 }
cd56cc6b 887 qxlin->id = qemu_console_get_index(con);
9fa03286
GH
888 spice_consoles = g_slist_append(spice_consoles, con);
889 return qemu_spice_add_interface(&qxlin->base);
29b0040b
GH
890}
891
7572150c
GH
892static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn)
893{
894 time_t lifetime, now = time(NULL);
895 char *passwd;
896
897 if (now < auth_expires) {
898 passwd = auth_passwd;
899 lifetime = (auth_expires - now);
900 if (lifetime > INT_MAX) {
901 lifetime = INT_MAX;
902 }
903 } else {
904 passwd = NULL;
905 lifetime = 1;
906 }
907 return spice_server_set_ticket(spice_server, passwd, lifetime,
908 fail_if_conn, disconnect_if_conn);
909}
910
08ad2626
GH
911static int qemu_spice_set_passwd(const char *passwd,
912 bool fail_if_conn, bool disconnect_if_conn)
7572150c 913{
b1ea7b79
GH
914 if (strcmp(auth, "spice") != 0) {
915 return -1;
916 }
917
fd3bea3f
MA
918 g_free(auth_passwd);
919 auth_passwd = g_strdup(passwd);
7572150c
GH
920 return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn);
921}
922
08ad2626 923static int qemu_spice_set_pw_expire(time_t expires)
7572150c
GH
924{
925 auth_expires = expires;
926 return qemu_spice_set_ticket(false, false);
927}
928
864a024c 929static int qemu_spice_display_add_client(int csock, int skipauth, int tls)
f1f5f407 930{
f1f5f407
DB
931 if (tls) {
932 return spice_server_add_ssl_client(spice_server, csock, skipauth);
933 } else {
934 return spice_server_add_client(spice_server, csock, skipauth);
935 }
f1f5f407
DB
936}
937
7cc6a25f
GH
938void qemu_spice_display_start(void)
939{
83f71802
MAL
940 if (spice_display_is_running) {
941 return;
942 }
943
7cc6a25f 944 spice_display_is_running = true;
b50f3e42 945 spice_server_vm_start(spice_server);
7cc6a25f
GH
946}
947
948void qemu_spice_display_stop(void)
949{
83f71802
MAL
950 if (!spice_display_is_running) {
951 return;
952 }
953
b50f3e42 954 spice_server_vm_stop(spice_server);
7cc6a25f
GH
955 spice_display_is_running = false;
956}
957
958int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd)
959{
960 return spice_display_is_running;
961}
962
7477477c 963static struct QemuSpiceOps real_spice_ops = {
63be30e6 964 .init = qemu_spice_init,
b192cd1e 965 .display_init = qemu_spice_display_init,
7477477c 966 .migrate_info = qemu_spice_migrate_info,
08ad2626
GH
967 .set_passwd = qemu_spice_set_passwd,
968 .set_pw_expire = qemu_spice_set_pw_expire,
864a024c 969 .display_add_client = qemu_spice_display_add_client,
05b53636 970 .add_interface = qemu_spice_add_interface,
db5732c9 971 .qmp_query = qmp_query_spice_real,
7477477c
GH
972};
973
29b0040b
GH
974static void spice_register_config(void)
975{
7477477c 976 qemu_spice = real_spice_ops;
29b0040b
GH
977 qemu_add_opts(&qemu_spice_opts);
978}
34294e2f 979opts_init(spice_register_config);
b36ae1c1
GH
980module_opts("spice");
981
9a6c69d3 982#ifdef HAVE_SPICE_GL
b36ae1c1
GH
983module_dep("ui-opengl");
984#endif