/* coroutine context */ G_GNUC_INTERNAL void spice_channel_handle_migrate(SpiceChannel *channel, spice_msg_in *in) { spice_msg_out *out; spice_msg_in *data = NULL; SpiceMsgMigrate *mig = spice_msg_in_parsed(in); spice_channel *c = channel->priv; SPICE_DEBUG("%s: channel %s flags %u", __FUNCTION__, c->name, mig->flags); if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) { /* iterate_write is blocking and flushing all pending write */ SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK); spice_msg_out_send_internal(out); spice_msg_out_unref(out); SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); } if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) { spice_channel_recv_msg(channel, get_msg_handler, &data); if (!data || data->header.type != SPICE_MSG_MIGRATE_DATA) { g_warning("expected SPICE_MSG_MIGRATE_DATA, got %d", data->header.type); } } spice_session_channel_migrate(c->session, channel); if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) { out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA); spice_marshaller_add(out->marshaller, data->data, data->header.size); spice_msg_out_send_internal(out); spice_msg_out_unref(out); } }
/* coroutine context */ G_GNUC_INTERNAL void spice_channel_handle_migrate(SpiceChannel *channel, SpiceMsgIn *in) { SpiceMsgOut *out; SpiceMsgIn *data = NULL; SpiceMsgMigrate *mig = spice_msg_in_parsed(in); SpiceChannelPrivate *c = channel->priv; g_message("spice_channel_handle_migrate.\n"); CHANNEL_DEBUG(channel, "%s: flags %u", __FUNCTION__, mig->flags); if (mig->flags & SPICE_MIGRATE_NEED_FLUSH) { g_message("++++++++++++ SPICE_MIGRATE_NEED_FLUSH.\n"); /* if peer version > 1: pushing the mark msg before all other messgages and sending it, * and only it */ if (c->peer_hdr.major_version == 1) { /* iterate_write is blocking and flushing all pending write */ SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel); } out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_FLUSH_MARK); spice_msg_out_send_internal(out); } if (mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) { g_message("+++++++++++ SPICE_MIGRATE_NEED_DATA_TRANSFER.\n"); spice_channel_recv_msg(channel, get_msg_handler, &data); if (!data) { g_critical("expected SPICE_MSG_MIGRATE_DATA, got empty message"); goto end; } else if (spice_header_get_msg_type(data->header, c->use_mini_header) != SPICE_MSG_MIGRATE_DATA) { g_critical("expected SPICE_MSG_MIGRATE_DATA, got %d", spice_header_get_msg_type(data->header, c->use_mini_header)); goto end; } } /* swapping channels sockets */ spice_session_channel_migrate(c->session, channel); /* pushing the MIGRATE_DATA before all other pending messages */ if ((mig->flags & SPICE_MIGRATE_NEED_DATA_TRANSFER) && (data != NULL)) { out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MIGRATE_DATA); spice_marshaller_add(out->marshaller, data->data, spice_header_get_msg_size(data->header, c->use_mini_header)); spice_msg_out_send_internal(out); } end: if (data) spice_msg_in_unref(data); }
/* we use an idle function to allow the coroutine to exit before we actually * unref the object since the coroutine's state is part of the object */ static gboolean spice_channel_delayed_unref(gpointer data) { SpiceChannel *channel = SPICE_CHANNEL(data); spice_channel *c = channel->priv; g_return_val_if_fail(channel != NULL, FALSE); SPICE_DEBUG("Delayed unref channel %s %p", c->name, channel); g_return_val_if_fail(c->coroutine.exited == TRUE, FALSE); g_object_unref(G_OBJECT(data)); return FALSE; }
static void virt_viewer_session_spice_clear_displays(VirtViewerSessionSpice *self) { SpiceSession *session = self->priv->session; GList *l; GList *channels; channels = spice_session_get_channels(session); for (l = channels; l != NULL; l = l->next) { SpiceChannel *channel = SPICE_CHANNEL(l->data); g_object_set_data(G_OBJECT(channel), "virt-viewer-displays", NULL); } g_list_free(channels); virt_viewer_session_clear_displays(VIRT_VIEWER_SESSION(self)); }
static void spice_channel_constructed(GObject *gobject) { SpiceChannel *channel = SPICE_CHANNEL(gobject); spice_channel *c = channel->priv; const char *desc = NULL; if (c->channel_type < SPICE_N_ELEMENTS(channel_desc)) desc = channel_desc[c->channel_type]; snprintf(c->name, sizeof(c->name), "%s-%d:%d", desc ? desc : "unknown", c->channel_type, c->channel_id); SPICE_DEBUG("%s: %s", c->name, __FUNCTION__); c->connection_id = spice_session_get_connection_id(c->session); spice_session_channel_new(c->session, channel); /* Chain up to the parent class */ if (G_OBJECT_CLASS(spice_channel_parent_class)->constructed) G_OBJECT_CLASS(spice_channel_parent_class)->constructed(gobject); }
static void spice_channel_dispose(GObject *gobject) { SpiceChannel *channel = SPICE_CHANNEL(gobject); spice_channel *c = channel->priv; SPICE_DEBUG("%s: %s %p", c->name, __FUNCTION__, gobject); if (c->session) spice_session_channel_destroy(c->session, channel); spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED); if (c->session) { g_object_unref(c->session); c->session = NULL; } /* Chain up to the parent class */ if (G_OBJECT_CLASS(spice_channel_parent_class)->dispose) G_OBJECT_CLASS(spice_channel_parent_class)->dispose(gobject); }
static void spice_channel_set_property(GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { SpiceChannel *channel = SPICE_CHANNEL(gobject); spice_channel *c = channel->priv; switch (prop_id) { case PROP_SESSION: c->session = g_value_dup_object(value); break; case PROP_CHANNEL_TYPE: c->channel_type = g_value_get_int(value); break; case PROP_CHANNEL_ID: c->channel_id = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); break; } }
static void spice_channel_finalize(GObject *gobject) { SpiceChannel *channel = SPICE_CHANNEL(gobject); spice_channel *c = channel->priv; SPICE_DEBUG("%s: %s %p", c->name, __FUNCTION__, gobject); if (c->caps) g_array_free(c->caps, TRUE); if (c->common_caps) g_array_free(c->common_caps, TRUE); if (c->remote_caps) g_array_free(c->remote_caps, TRUE); if (c->remote_common_caps) g_array_free(c->remote_common_caps, TRUE); /* Chain up to the parent class */ if (G_OBJECT_CLASS(spice_channel_parent_class)->finalize) G_OBJECT_CLASS(spice_channel_parent_class)->finalize(gobject); }
/** * spice_channel_new: * @s: the @SpiceSession the channel is linked to * @type: the requested SPICE_CHANNEL type * @id: the channel-id * * Create a new #SpiceChannel of type @type, and channel ID @id. * * Returns: a #SpiceChannel **/ SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id) { SpiceChannel *channel; GType gtype = 0; g_return_val_if_fail(s != NULL, NULL); switch (type) { case SPICE_CHANNEL_MAIN: gtype = SPICE_TYPE_MAIN_CHANNEL; break; case SPICE_CHANNEL_DISPLAY: gtype = SPICE_TYPE_DISPLAY_CHANNEL; break; case SPICE_CHANNEL_CURSOR: //gtype = SPICE_TYPE_CURSOR_CHANNEL; break; case SPICE_CHANNEL_INPUTS: gtype = SPICE_TYPE_INPUTS_CHANNEL; break; case SPICE_CHANNEL_PLAYBACK: //gtype = SPICE_TYPE_PLAYBACK_CHANNEL; break; case SPICE_CHANNEL_RECORD: //gtype = SPICE_TYPE_RECORD_CHANNEL; break; default: return NULL; } channel = SPICE_CHANNEL(g_object_new(gtype, "spice-session", s, "channel-type", type, "channel-id", id, NULL)); return channel; }
/* coroutine context */ static void *spice_channel_coroutine(void *data) { SpiceChannel *channel = SPICE_CHANNEL(data); spice_channel *c = channel->priv; int ret; guint verify; SPICE_DEBUG("Started background coroutine %p", &c->coroutine); if (spice_session_get_client_provided_socket(c->session)) { if (c->fd < 0) { g_critical("fd not provided!"); goto cleanup; } if (!(c->sock = g_socket_new_from_fd(c->fd, NULL))) { SPICE_DEBUG("Failed to open socket from fd %d", c->fd); return FALSE; } g_socket_set_blocking(c->sock, FALSE); goto connected; } reconnect: c->sock = spice_session_channel_open_host(c->session, c->tls); if (c->sock == NULL) { if (!c->tls) { SPICE_DEBUG("connection failed, trying with TLS port"); c->tls = true; /* FIXME: does that really work with provided fd */ goto reconnect; } else { SPICE_DEBUG("Connect error"); emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_CONNECT); goto cleanup; } } c->has_error = FALSE; if (c->tls) { int rc; c->ctx = SSL_CTX_new(TLSv1_method()); if (c->ctx == NULL) { g_critical("SSL_CTX_new failed"); goto cleanup; } verify = spice_session_get_verify(c->session); if (verify & (SPICE_SESSION_VERIFY_PUBKEY | SPICE_SESSION_VERIFY_HOSTNAME)) { gchar *ca_file; g_object_get(c->session, "ca-file", &ca_file, NULL); if (ca_file) { rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL); if (rc != 1) g_warning("loading ca certs from %s failed", ca_file); g_free(ca_file); if (rc != 1) { if (verify & SPICE_SESSION_VERIFY_PUBKEY) { g_warning("only pubkey active"); verify = SPICE_SESSION_VERIFY_PUBKEY; } else goto cleanup; } } } c->ssl = SSL_new(c->ctx); if (c->ssl == NULL) { g_critical("SSL_new failed"); goto cleanup; } rc = SSL_set_fd(c->ssl, g_socket_get_fd(c->sock)); if (rc <= 0) { g_critical("SSL_set_fd failed"); goto cleanup; } { gchar *hostname, *subject; guint8 *pubkey; guint pubkey_len; g_object_get(c->session, "host", &hostname, "cert-subject", &subject, NULL); spice_session_get_pubkey(c->session, &pubkey, &pubkey_len); c->sslverify = spice_openssl_verify_new(c->ssl, verify, hostname, (char*)pubkey, pubkey_len, subject); g_free(hostname); g_free(subject); } ssl_reconnect: rc = SSL_connect(c->ssl); if (rc <= 0) { rc = SSL_get_error(c->ssl, rc); if (rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) { g_io_wait(c->sock, G_IO_OUT|G_IO_ERR|G_IO_HUP); goto ssl_reconnect; } else { g_warning("%s: SSL_connect: %s", c->name, ERR_error_string(rc, NULL)); emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_ERROR_TLS); goto cleanup; } } } connected: c->state = SPICE_CHANNEL_STATE_LINK_HDR; spice_channel_send_link(channel); while ((ret = spice_channel_iterate(channel))) ; /* TODO: improve it, this is a bit hairy, c->coroutine will be overwritten on (re)connect, so we skip the normal cleanup path. Ideally, we shouldn't use the same channel structure? */ if (c->state == SPICE_CHANNEL_STATE_CONNECTING) { g_object_unref(channel); goto end; } cleanup: SPICE_DEBUG("Coroutine exit"); SPICE_CHANNEL_GET_CLASS(channel)->channel_disconnect(channel); g_idle_add(spice_channel_delayed_unref, data); end: /* Co-routine exits now - the SpiceChannel object may no longer exist, so don't do anything else now unless you like SEGVs */ exit(1); return NULL; }