1 /* spice-server spicevmc passthrough channel code
2 
3    Copyright (C) 2011 Red Hat, Inc.
4 
5    Red Hat Authors:
6    Hans de Goede <hdegoede@redhat.com>
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public
10    License as published by the Free Software Foundation; either
11    version 2.1 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17 
18    You should have received a copy of the GNU Lesser General Public
19    License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 */
21 #include <config.h>
22 
23 #include <assert.h>
24 #include <string.h>
25 #ifdef USE_LZ4
26 #include <lz4.h>
27 #endif
28 
29 #include <common/generated_server_marshallers.h>
30 
31 #include "char-device.h"
32 #include "red-channel.h"
33 #include "red-channel-client.h"
34 #include "reds.h"
35 #include "migration-protocol.h"
36 
37 /* 64K should be enough for all but the largest writes + 32 bytes hdr */
38 #define BUF_SIZE (64 * 1024 + 32)
39 #define COMPRESS_THRESHOLD 1000
40 
41 // limit of the queued data, at this limit we stop reading from device to
42 // avoid DoS
43 #define QUEUED_DATA_LIMIT (1024*1024)
44 
45 enum {
46     RED_PIPE_ITEM_TYPE_SPICEVMC_DATA = RED_PIPE_ITEM_TYPE_CHANNEL_BASE,
47     RED_PIPE_ITEM_TYPE_SPICEVMC_MIGRATE_DATA,
48     RED_PIPE_ITEM_TYPE_PORT_INIT,
49     RED_PIPE_ITEM_TYPE_PORT_EVENT,
50 };
51 
52 struct RedVmcChannel;
53 class VmcChannelClient;
54 
55 struct RedVmcPipeItem: public RedPipeItemNum<RED_PIPE_ITEM_TYPE_SPICEVMC_DATA> {
56     SpiceDataCompressionType type;
57     uint32_t uncompressed_data_size = 0;
58     /* writes which don't fit this will get split, this is not a problem */
59     uint8_t buf[BUF_SIZE];
60     uint32_t buf_used = 0;
61 };
62 
63 struct RedCharDeviceSpiceVmc: public RedCharDevice
64 {
65     RedCharDeviceSpiceVmc(SpiceCharDeviceInstance *sin, RedsState *reds, RedVmcChannel *channel);
66     ~RedCharDeviceSpiceVmc() override;
67 
68     RedPipeItemPtr read_one_msg_from_device() override;
69     void remove_client(RedCharDeviceClientOpaque *opaque) override;
70     void on_free_self_token() override;
71     void port_event(uint8_t event) override;
72 
73     red::shared_ptr<RedVmcChannel> channel;
74 };
75 
76 static void spicevmc_red_channel_queue_data(RedVmcChannel *channel, red::shared_ptr<RedVmcPipeItem>&& item);
77 
78 struct RedVmcChannel: public RedChannel
79 {
80     RedVmcChannel(RedsState *reds, uint32_t type, uint32_t id);
81     ~RedVmcChannel() override;
82 
83     void on_connect(RedClient *client, RedStream *stream, int migration,
84                     RedChannelCapabilities *caps) override;
85 
86     VmcChannelClient *rcc;
87     RedCharDevice *chardev; /* weak */
88     SpiceCharDeviceInstance *chardev_sin;
89     red::shared_ptr<RedVmcPipeItem> pipe_item;
90     RedCharDeviceWriteBuffer *recv_from_client_buf;
91     uint8_t port_opened;
92     uint32_t queued_data;
93     RedStatCounter in_data;
94     RedStatCounter in_compressed;
95     RedStatCounter in_decompressed;
96     RedStatCounter out_data;
97     RedStatCounter out_compressed;
98     RedStatCounter out_uncompressed;
99 };
100 
101 
102 class VmcChannelClient final: public RedChannelClient
103 {
104     using RedChannelClient::RedChannelClient;
105 public:
get_channel()106     RedVmcChannel* get_channel()
107     {
108         return static_cast<RedVmcChannel*>(RedChannelClient::get_channel());
109     }
110 protected:
111     uint8_t *alloc_recv_buf(uint16_t type, uint32_t size) override;
112     void release_recv_buf(uint16_t type, uint32_t size, uint8_t *msg) override;
113     void on_disconnect() override;
114     bool handle_message(uint16_t type, uint32_t size, void *msg) override;
115     void send_item(RedPipeItem *item) override;
116     bool handle_migrate_data(uint32_t size, void *message) override;
117     void handle_migrate_flush_mark() override;
118 };
119 
120 static VmcChannelClient *
121 vmc_channel_client_create(RedChannel *channel, RedClient *client,
122                           RedStream *stream,
123                           RedChannelCapabilities *caps);
124 
125 
RedVmcChannel(RedsState * reds,uint32_t type,uint32_t id)126 RedVmcChannel::RedVmcChannel(RedsState *reds, uint32_t type, uint32_t id):
127     RedChannel(reds, type, id, RedChannel::MigrateAll)
128 {
129     init_stat_node(nullptr, "spicevmc");
130     const RedStatNode *stat = get_stat_node();
131     stat_init_counter(&in_data, reds, stat, "in_data", TRUE);
132     stat_init_counter(&in_compressed, reds, stat, "in_compressed", TRUE);
133     stat_init_counter(&in_decompressed, reds, stat, "in_decompressed", TRUE);
134     stat_init_counter(&out_data, reds, stat, "out_data", TRUE);
135     stat_init_counter(&out_compressed, reds, stat, "out_compressed", TRUE);
136     stat_init_counter(&out_uncompressed, reds, stat, "out_uncompressed", TRUE);
137 
138 #ifdef USE_LZ4
139     set_cap(SPICE_SPICEVMC_CAP_DATA_COMPRESS_LZ4);
140 #endif
141 
142     reds_register_channel(reds, this);
143 }
144 
~RedVmcChannel()145 RedVmcChannel::~RedVmcChannel()
146 {
147     RedCharDevice::write_buffer_release(chardev, &recv_from_client_buf);
148 }
149 
red_vmc_channel_new(RedsState * reds,uint8_t channel_type)150 static red::shared_ptr<RedVmcChannel> red_vmc_channel_new(RedsState *reds, uint8_t channel_type)
151 {
152     switch (channel_type) {
153         case SPICE_CHANNEL_USBREDIR:
154         case SPICE_CHANNEL_WEBDAV:
155         case SPICE_CHANNEL_PORT:
156             break;
157         default:
158             g_error("Unsupported channel_type for red_vmc_channel_new(): %u", channel_type);
159             return red::shared_ptr<RedVmcChannel>();
160     }
161 
162     int id = reds_get_free_channel_id(reds, channel_type);
163     if (id < 0) {
164         g_warning("Free ID not found creating new VMC channel");
165         return red::shared_ptr<RedVmcChannel>();
166     }
167 
168     return red::make_shared<RedVmcChannel>(reds, channel_type, id);
169 }
170 
171 struct RedPortInitPipeItem: public RedPipeItemNum<RED_PIPE_ITEM_TYPE_PORT_INIT> {
172     RedPortInitPipeItem(const char *name, uint8_t opened);
173 
174     red::glib_unique_ptr<char> name;
175     uint8_t opened;
176 };
177 
178 struct RedPortEventPipeItem: public RedPipeItemNum<RED_PIPE_ITEM_TYPE_PORT_EVENT> {
179     uint8_t event;
180 };
181 
182 /* msg_item -- the current pipe item with the uncompressed data
183  * This function returns:
184  *  - false upon failure.
185  *  - true if compression succeeded
186  */
187 static bool
try_compress_lz4(RedVmcChannel * channel,red::shared_ptr<RedVmcPipeItem> & msg_item)188 try_compress_lz4(RedVmcChannel *channel, red::shared_ptr<RedVmcPipeItem>& msg_item)
189 {
190 #ifdef USE_LZ4
191     int compressed_data_count;
192     auto n = msg_item->buf_used;
193 
194     if (red_stream_get_family(channel->rcc->get_stream()) == AF_UNIX) {
195         /* AF_LOCAL - data will not be compressed */
196         return false;
197     }
198     if (n <= COMPRESS_THRESHOLD) {
199         /* n <= threshold - data will not be compressed */
200         return false;
201     }
202     if (!channel->rcc->test_remote_cap(SPICE_SPICEVMC_CAP_DATA_COMPRESS_LZ4)) {
203         /* Client doesn't have compression cap - data will not be compressed */
204         return false;
205     }
206     auto msg_item_compressed = red::make_shared<RedVmcPipeItem>();
207     compressed_data_count = LZ4_compress_default((char*)&msg_item->buf,
208                                                  (char*)&msg_item_compressed->buf,
209                                                  n,
210                                                  BUF_SIZE);
211 
212     if (compressed_data_count > 0 && compressed_data_count < n) {
213         stat_inc_counter(channel->out_uncompressed, n);
214         stat_inc_counter(channel->out_compressed, compressed_data_count);
215         msg_item_compressed->type = SPICE_DATA_COMPRESSION_TYPE_LZ4;
216         msg_item_compressed->uncompressed_data_size = n;
217         msg_item_compressed->buf_used = compressed_data_count;
218         msg_item = std::move(msg_item_compressed);
219         return true;
220     }
221 
222     /* LZ4 compression failed or did non compress, fallback a non-compressed data is to be sent */
223 #endif
224     return false;
225 }
226 
227 RedPipeItemPtr
read_one_msg_from_device()228 RedCharDeviceSpiceVmc::read_one_msg_from_device()
229 {
230     red::shared_ptr<RedVmcPipeItem> msg_item;
231     int n;
232 
233     if (!channel->rcc || channel->queued_data >= QUEUED_DATA_LIMIT) {
234         return RedPipeItemPtr();
235     }
236 
237     if (!channel->pipe_item) {
238         msg_item = red::make_shared<RedVmcPipeItem>();
239         msg_item->type = SPICE_DATA_COMPRESSION_TYPE_NONE;
240     } else {
241         spice_assert(channel->pipe_item->buf_used == 0);
242         msg_item = std::move(channel->pipe_item);
243     }
244 
245     n = read(msg_item->buf, sizeof(msg_item->buf));
246     if (n > 0) {
247         spice_debug("read from dev %d", n);
248         msg_item->uncompressed_data_size = n;
249         msg_item->buf_used = n;
250 
251         if (!try_compress_lz4(channel.get(), msg_item)) {
252             stat_inc_counter(channel->out_data, n);
253         }
254         spicevmc_red_channel_queue_data(channel.get(), std::move(msg_item));
255         return RedPipeItemPtr();
256     }
257     channel->pipe_item = std::move(msg_item);
258     return RedPipeItemPtr();
259 }
260 
RedPortInitPipeItem(const char * init_name,uint8_t init_opened)261 RedPortInitPipeItem::RedPortInitPipeItem(const char *init_name, uint8_t init_opened):
262     name(g_strdup(init_name)),
263     opened(init_opened)
264 {
265 }
266 
spicevmc_port_send_init(VmcChannelClient * rcc)267 static void spicevmc_port_send_init(VmcChannelClient *rcc)
268 {
269     RedVmcChannel *channel = rcc->get_channel();
270     SpiceCharDeviceInstance *sin = channel->chardev_sin;
271     auto item = red::make_shared<RedPortInitPipeItem>(sin->portname, channel->port_opened);
272 
273     rcc->pipe_add_push(item);
274 }
275 
spicevmc_port_send_event(RedChannelClient * rcc,uint8_t event)276 static void spicevmc_port_send_event(RedChannelClient *rcc, uint8_t event)
277 {
278     auto item = red::make_shared<RedPortEventPipeItem>();
279 
280     item->event = event;
281     rcc->pipe_add_push(item);
282 }
283 
remove_client(RedCharDeviceClientOpaque * opaque)284 void RedCharDeviceSpiceVmc::remove_client(RedCharDeviceClientOpaque *opaque)
285 {
286     auto client = (RedClient *) opaque;
287 
288     spice_assert(channel->rcc &&
289                  channel->rcc->get_client() == client);
290 
291     channel->rcc->shutdown();
292 }
293 
on_disconnect()294 void VmcChannelClient::on_disconnect()
295 {
296     RedVmcChannel *channel;
297     SpiceCharDeviceInterface *sif;
298     RedClient *client = get_client();
299 
300     channel = get_channel();
301 
302     /* partial message which wasn't pushed to device */
303     RedCharDevice::write_buffer_release(channel->chardev,
304                                         &channel->recv_from_client_buf);
305 
306     if (channel->chardev) {
307         if (channel->chardev->client_exists((RedCharDeviceClientOpaque *)client)) {
308             channel->chardev->client_remove((RedCharDeviceClientOpaque *)client);
309         } else {
310             red_channel_warning(channel,
311                                 "client %p have already been removed from char dev %p",
312                                 client, channel->chardev);
313         }
314     }
315 
316     channel->rcc = nullptr;
317     sif = spice_char_device_get_interface(channel->chardev_sin);
318     if (sif->state) {
319         sif->state(channel->chardev_sin, 0);
320     }
321 }
322 
handle_migrate_flush_mark()323 void VmcChannelClient::handle_migrate_flush_mark()
324 {
325     pipe_add_type(RED_PIPE_ITEM_TYPE_SPICEVMC_MIGRATE_DATA);
326 }
327 
handle_migrate_data(uint32_t size,void * message)328 bool VmcChannelClient::handle_migrate_data(uint32_t size, void *message)
329 {
330     SpiceMigrateDataHeader *header;
331     SpiceMigrateDataSpiceVmc *mig_data;
332     RedVmcChannel *channel;
333 
334     channel = get_channel();
335 
336     header = (SpiceMigrateDataHeader *)message;
337     mig_data = (SpiceMigrateDataSpiceVmc *)(header + 1);
338     spice_assert(size >= sizeof(SpiceMigrateDataHeader) + sizeof(SpiceMigrateDataSpiceVmc));
339 
340     if (!migration_protocol_validate_header(header,
341                                             SPICE_MIGRATE_DATA_SPICEVMC_MAGIC,
342                                             SPICE_MIGRATE_DATA_SPICEVMC_VERSION)) {
343         spice_error("bad header");
344         return FALSE;
345     }
346     return channel->chardev->restore(&mig_data->base);
347 }
348 
handle_compressed_msg(RedVmcChannel * channel,RedChannelClient * rcc,SpiceMsgCompressedData * compressed_data_msg)349 static bool handle_compressed_msg(RedVmcChannel *channel, RedChannelClient *rcc,
350                                   SpiceMsgCompressedData *compressed_data_msg)
351 {
352     /* NOTE: *decompressed is free by the char-device */
353     int decompressed_size;
354     RedCharDeviceWriteBuffer *write_buf;
355 
356     write_buf = channel->chardev->write_buffer_get_server(compressed_data_msg->uncompressed_size,
357                                                           false);
358     if (!write_buf) {
359         return FALSE;
360     }
361 
362     switch (compressed_data_msg->type) {
363 #ifdef USE_LZ4
364     case SPICE_DATA_COMPRESSION_TYPE_LZ4: {
365         uint8_t *decompressed = write_buf->buf;
366         decompressed_size = LZ4_decompress_safe ((char *)compressed_data_msg->compressed_data,
367                                                  (char *)decompressed,
368                                                  compressed_data_msg->compressed_size,
369                                                  compressed_data_msg->uncompressed_size);
370         stat_inc_counter(channel->in_compressed, compressed_data_msg->compressed_size);
371         stat_inc_counter(channel->in_decompressed, decompressed_size);
372         break;
373     }
374 #endif
375     default:
376         spice_warning("Invalid Compression Type");
377         RedCharDevice::write_buffer_release(channel->chardev, &write_buf);
378         return FALSE;
379     }
380     if (decompressed_size != compressed_data_msg->uncompressed_size) {
381         spice_warning("Decompression Error");
382         RedCharDevice::write_buffer_release(channel->chardev, &write_buf);
383         return FALSE;
384     }
385     write_buf->buf_used = decompressed_size;
386     channel->chardev->write_buffer_add(write_buf);
387     return TRUE;
388 }
389 
handle_message(uint16_t type,uint32_t size,void * msg)390 bool VmcChannelClient::handle_message(uint16_t type, uint32_t size, void *msg)
391 {
392     /* NOTE: *msg free by g_free() (when cb to VmcChannelClient::release_recv_buf
393      * with the compressed msg type) */
394     RedVmcChannel *channel;
395     SpiceCharDeviceInterface *sif;
396 
397     channel = get_channel();
398     sif = spice_char_device_get_interface(channel->chardev_sin);
399 
400     switch (type) {
401     case SPICE_MSGC_SPICEVMC_DATA:
402         spice_assert(channel->recv_from_client_buf->buf == msg);
403         stat_inc_counter(channel->in_data, size);
404         channel->recv_from_client_buf->buf_used = size;
405         channel->chardev->write_buffer_add(channel->recv_from_client_buf);
406         channel->recv_from_client_buf = nullptr;
407         break;
408     case SPICE_MSGC_SPICEVMC_COMPRESSED_DATA:
409         return handle_compressed_msg(channel, this, (SpiceMsgCompressedData*)msg);
410         break;
411     case SPICE_MSGC_PORT_EVENT:
412         if (size != sizeof(uint8_t)) {
413             spice_warning("bad port event message size");
414             return FALSE;
415         }
416         if (sif->base.minor_version >= 2 && sif->event != nullptr)
417             sif->event(channel->chardev_sin, *(uint8_t*)msg);
418         break;
419     default:
420         return RedChannelClient::handle_message(type, size, msg);
421     }
422 
423     return TRUE;
424 }
425 
426 /* if device manage to send some data attempt to unblock the channel */
on_free_self_token()427 void RedCharDeviceSpiceVmc::on_free_self_token()
428 {
429     channel->rcc->unblock_read();
430 }
431 
alloc_recv_buf(uint16_t type,uint32_t size)432 uint8_t *VmcChannelClient::alloc_recv_buf(uint16_t type, uint32_t size)
433 {
434 
435     switch (type) {
436     case SPICE_MSGC_SPICEVMC_DATA: {
437         RedVmcChannel *channel = get_channel();
438 
439         assert(!channel->recv_from_client_buf);
440 
441         channel->recv_from_client_buf = channel->chardev->write_buffer_get_server(size,
442                                                                                   true);
443         if (!channel->recv_from_client_buf) {
444             block_read();
445             return nullptr;
446         }
447         return channel->recv_from_client_buf->buf;
448     }
449 
450     default:
451         return (uint8_t*) g_malloc(size);
452     }
453 
454 }
455 
release_recv_buf(uint16_t type,uint32_t size,uint8_t * msg)456 void VmcChannelClient::release_recv_buf(uint16_t type, uint32_t size, uint8_t *msg)
457 {
458 
459     switch (type) {
460     case SPICE_MSGC_SPICEVMC_DATA: {
461         RedVmcChannel *channel = get_channel();
462         /* buffer wasn't pushed to device */
463         RedCharDevice::write_buffer_release(channel->chardev,
464                                             &channel->recv_from_client_buf);
465         break;
466     }
467     default:
468         g_free(msg);
469     }
470 }
471 
472 static void
spicevmc_red_channel_queue_data(RedVmcChannel * channel,red::shared_ptr<RedVmcPipeItem> && item)473 spicevmc_red_channel_queue_data(RedVmcChannel *channel, red::shared_ptr<RedVmcPipeItem>&& item)
474 {
475     channel->queued_data += item->buf_used;
476     channel->rcc->pipe_add_push(item);
477 }
478 
spicevmc_red_channel_send_data(VmcChannelClient * rcc,SpiceMarshaller * m,RedPipeItem * item)479 static void spicevmc_red_channel_send_data(VmcChannelClient *rcc,
480                                            SpiceMarshaller *m,
481                                            RedPipeItem *item)
482 {
483     auto i = static_cast<RedVmcPipeItem*>(item);
484     RedVmcChannel *channel = rcc->get_channel();
485 
486     /* for compatibility send using not compressed data message */
487     if (i->type == SPICE_DATA_COMPRESSION_TYPE_NONE) {
488         rcc->init_send_data(SPICE_MSG_SPICEVMC_DATA);
489     } else {
490         /* send as compressed */
491         rcc->init_send_data(SPICE_MSG_SPICEVMC_COMPRESSED_DATA);
492         SpiceMsgCompressedData compressed_msg = {
493             .type = i->type,
494             .uncompressed_size = i->uncompressed_data_size
495         };
496         spice_marshall_SpiceMsgCompressedData(m, &compressed_msg);
497     }
498     item->add_to_marshaller(m, i->buf, i->buf_used);
499 
500     // account for sent data and wake up device if was blocked
501     uint32_t old_queued_data = channel->queued_data;
502     channel->queued_data -= i->buf_used;
503     if (channel->chardev &&
504         old_queued_data >= QUEUED_DATA_LIMIT && channel->queued_data < QUEUED_DATA_LIMIT) {
505         channel->chardev->wakeup();
506     }
507 }
508 
spicevmc_red_channel_send_migrate_data(VmcChannelClient * rcc,SpiceMarshaller * m,RedPipeItem * item)509 static void spicevmc_red_channel_send_migrate_data(VmcChannelClient *rcc,
510                                                    SpiceMarshaller *m,
511                                                    RedPipeItem *item)
512 {
513     RedVmcChannel *channel;
514 
515     channel = rcc->get_channel();
516     rcc->init_send_data(SPICE_MSG_MIGRATE_DATA);
517     spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_SPICEVMC_MAGIC);
518     spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_SPICEVMC_VERSION);
519 
520     channel->chardev->migrate_data_marshall(m);
521 }
522 
spicevmc_red_channel_send_port_init(RedChannelClient * rcc,SpiceMarshaller * m,RedPipeItem * item)523 static void spicevmc_red_channel_send_port_init(RedChannelClient *rcc,
524                                                 SpiceMarshaller *m,
525                                                 RedPipeItem *item)
526 {
527     auto i = static_cast<RedPortInitPipeItem*>(item);
528     SpiceMsgPortInit init;
529 
530     rcc->init_send_data(SPICE_MSG_PORT_INIT);
531     init.name = (uint8_t *)i->name.get();
532     init.name_size = strlen(i->name.get()) + 1;
533     init.opened = i->opened;
534     spice_marshall_msg_port_init(m, &init);
535 }
536 
spicevmc_red_channel_send_port_event(RedChannelClient * rcc,SpiceMarshaller * m,RedPipeItem * item)537 static void spicevmc_red_channel_send_port_event(RedChannelClient *rcc,
538                                                  SpiceMarshaller *m,
539                                                  RedPipeItem *item)
540 {
541     auto i = static_cast<RedPortEventPipeItem*>(item);
542     SpiceMsgPortEvent event;
543 
544     rcc->init_send_data(SPICE_MSG_PORT_EVENT);
545     event.event = i->event;
546     spice_marshall_msg_port_event(m, &event);
547 }
548 
send_item(RedPipeItem * item)549 void VmcChannelClient::send_item(RedPipeItem *item)
550 {
551     SpiceMarshaller *m = get_marshaller();
552 
553     switch (item->type) {
554     case RED_PIPE_ITEM_TYPE_SPICEVMC_DATA:
555         spicevmc_red_channel_send_data(this, m, item);
556         break;
557     case RED_PIPE_ITEM_TYPE_SPICEVMC_MIGRATE_DATA:
558         spicevmc_red_channel_send_migrate_data(this, m, item);
559         break;
560     case RED_PIPE_ITEM_TYPE_PORT_INIT:
561         spicevmc_red_channel_send_port_init(this, m, item);
562         break;
563     case RED_PIPE_ITEM_TYPE_PORT_EVENT:
564         spicevmc_red_channel_send_port_event(this, m, item);
565         break;
566     default:
567         spice_error("bad pipe item %d", item->type);
568         return;
569     }
570     begin_send_message();
571 }
572 
573 
on_connect(RedClient * client,RedStream * stream,int migration,RedChannelCapabilities * caps)574 void RedVmcChannel::on_connect(RedClient *client, RedStream *stream, int migration,
575                                RedChannelCapabilities *caps)
576 {
577     RedVmcChannel *vmc_channel;
578     SpiceCharDeviceInstance *sin;
579     SpiceCharDeviceInterface *sif;
580 
581     vmc_channel = this;
582     sin = vmc_channel->chardev_sin;
583 
584     if (rcc) {
585         red_channel_warning(this,
586                             "channel client (%p) already connected, refusing second connection",
587                             rcc);
588         // TODO: notify client in advance about the in use channel using
589         // SPICE_MSG_MAIN_CHANNEL_IN_USE (for example)
590         red_stream_free(stream);
591         return;
592     }
593 
594     rcc = vmc_channel_client_create(this, client, stream, caps);
595     if (!rcc) {
596         return;
597     }
598     vmc_channel->queued_data = 0;
599     rcc->ack_zero_messages_window();
600 
601     if (strcmp(sin->subtype, "port") == 0) {
602         spicevmc_port_send_init(rcc);
603     }
604 
605     if (!vmc_channel->chardev->client_add((RedCharDeviceClientOpaque *)client, FALSE, 0, ~0, ~0, rcc->is_waiting_for_migrate_data())) {
606         spice_warning("failed to add client to spicevmc");
607         rcc->disconnect();
608         return;
609     }
610 
611     sif = spice_char_device_get_interface(sin);
612     if (sif->state) {
613         sif->state(sin, 1);
614     }
615 }
616 
617 red::shared_ptr<RedCharDevice>
spicevmc_device_connect(RedsState * reds,SpiceCharDeviceInstance * sin,uint8_t channel_type)618 spicevmc_device_connect(RedsState *reds, SpiceCharDeviceInstance *sin, uint8_t channel_type)
619 {
620     auto channel(red_vmc_channel_new(reds, channel_type));
621     if (!channel) {
622         return red::shared_ptr<RedCharDevice>();
623     }
624 
625     /* char device takes ownership of channel */
626     auto dev = red::make_shared<RedCharDeviceSpiceVmc>(sin, reds, channel.get());
627 
628     channel->chardev_sin = sin;
629 
630     return dev;
631 }
632 
port_event(uint8_t event)633 void RedCharDeviceSpiceVmc::port_event(uint8_t event)
634 {
635     if (event == SPICE_PORT_EVENT_OPENED) {
636         channel->port_opened = TRUE;
637     } else if (event == SPICE_PORT_EVENT_CLOSED) {
638         channel->port_opened = FALSE;
639     }
640 
641     if (channel->rcc == nullptr) {
642         return;
643     }
644 
645     spicevmc_port_send_event(channel->rcc, event);
646 }
647 
RedCharDeviceSpiceVmc(SpiceCharDeviceInstance * sin,RedsState * reds,RedVmcChannel * init_channel)648 RedCharDeviceSpiceVmc::RedCharDeviceSpiceVmc(SpiceCharDeviceInstance *sin, RedsState *reds,
649                                              RedVmcChannel *init_channel):
650     RedCharDevice(reds, sin, 0, 128),
651     channel(init_channel)
652 {
653     if (channel) {
654         channel->chardev = this;
655     }
656 }
657 
~RedCharDeviceSpiceVmc()658 RedCharDeviceSpiceVmc::~RedCharDeviceSpiceVmc()
659 {
660     if (channel) {
661         // prevent possible recursive calls
662         channel->chardev = nullptr;
663 
664         // close all current connections and drop the reference
665         channel->destroy();
666     }
667 }
668 
669 static VmcChannelClient *
vmc_channel_client_create(RedChannel * channel,RedClient * client,RedStream * stream,RedChannelCapabilities * caps)670 vmc_channel_client_create(RedChannel *channel, RedClient *client,
671                           RedStream *stream,
672                           RedChannelCapabilities *caps)
673 {
674     auto rcc = red::make_shared<VmcChannelClient>(channel, client, stream, caps);
675     if (!rcc->init()) {
676         return nullptr;
677     }
678     return rcc.get();
679 }
680