/* * Virt Viewer: A virtual machine console viewer * * Copyright (C) 2007-2012 Red Hat, Inc. * Copyright (C) 2009-2012 Daniel P. Berrange * Copyright (C) 2010 Marc-André Lureau * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_SOCKETPAIR) #include #endif #include "virt-viewer.h" #include "virt-viewer-app.h" #include "virt-viewer-vm-connection.h" #include "virt-viewer-auth.h" struct _VirtViewerPrivate { char *uri; virConnectPtr conn; virDomainPtr dom; char *domkey; gboolean waitvm; gboolean reconnect; gboolean auth_cancelled; gint domain_event; guint reconnect_poll; /* source id */ }; G_DEFINE_TYPE (VirtViewer, virt_viewer, VIRT_VIEWER_TYPE_APP) #define GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), VIRT_VIEWER_TYPE, VirtViewerPrivate)) static gboolean virt_viewer_initial_connect(VirtViewerApp *self, GError **error); static gboolean virt_viewer_open_connection(VirtViewerApp *self, int *fd); static void virt_viewer_deactivated(VirtViewerApp *self, gboolean connect_error); static gboolean virt_viewer_start(VirtViewerApp *self, GError **error); static void virt_viewer_dispose (GObject *object); static int virt_viewer_connect(VirtViewerApp *app, GError **error); static gchar **opt_args = NULL; static gchar *opt_uri = NULL; static gboolean opt_direct = FALSE; static gboolean opt_attach = FALSE; static gboolean opt_waitvm = FALSE; static gboolean opt_reconnect = FALSE; static void virt_viewer_add_option_entries(VirtViewerApp *self, GOptionContext *context, GOptionGroup *group) { static const GOptionEntry options[] = { { "direct", 'd', 0, G_OPTION_ARG_NONE, &opt_direct, N_("Direct connection with no automatic tunnels"), NULL }, { "attach", 'a', 0, G_OPTION_ARG_NONE, &opt_attach, N_("Attach to the local display using libvirt"), NULL }, { "connect", 'c', 0, G_OPTION_ARG_STRING, &opt_uri, N_("Connect to hypervisor"), "URI"}, { "wait", 'w', 0, G_OPTION_ARG_NONE, &opt_waitvm, N_("Wait for domain to start"), NULL }, { "reconnect", 'r', 0, G_OPTION_ARG_NONE, &opt_reconnect, N_("Reconnect to domain upon restart"), NULL }, { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &opt_args, NULL, "-- DOMAIN-NAME|ID|UUID" }, { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } }; VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->add_option_entries(self, context, group); g_option_context_set_summary(context, _("Virtual machine graphical console")); g_option_group_add_entries(group, options); } static gboolean virt_viewer_local_command_line (GApplication *gapp, gchar ***args, int *status) { gboolean ret = FALSE; VirtViewer *self = VIRT_VIEWER(gapp); VirtViewerApp *app = VIRT_VIEWER_APP(gapp); ret = G_APPLICATION_CLASS(virt_viewer_parent_class)->local_command_line(gapp, args, status); if (ret) goto end; if (opt_args) { if (g_strv_length(opt_args) != 1) { g_printerr(_("\nUsage: %s [OPTIONS] [DOMAIN-NAME|ID|UUID]\n\n"), PACKAGE); ret = TRUE; *status = 1; goto end; } self->priv->domkey = g_strdup(opt_args[0]); } if (opt_waitvm) { if (!self->priv->domkey) { g_printerr(_("\nNo DOMAIN-NAME|ID|UUID was specified for '--wait'\n\n")); ret = TRUE; *status = 1; goto end; } self->priv->waitvm = TRUE; } virt_viewer_app_set_direct(app, opt_direct); virt_viewer_app_set_attach(app, opt_attach); self->priv->reconnect = opt_reconnect; self->priv->uri = g_strdup(opt_uri); end: if (ret && *status) g_printerr(_("Run '%s --help' to see a full list of available command line options\n"), g_get_prgname()); g_strfreev(opt_args); g_free(opt_uri); return ret; } static void virt_viewer_class_init (VirtViewerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); VirtViewerAppClass *app_class = VIRT_VIEWER_APP_CLASS (klass); GApplicationClass *g_app_class = G_APPLICATION_CLASS(klass); g_type_class_add_private (klass, sizeof (VirtViewerPrivate)); object_class->dispose = virt_viewer_dispose; app_class->initial_connect = virt_viewer_initial_connect; app_class->deactivated = virt_viewer_deactivated; app_class->open_connection = virt_viewer_open_connection; app_class->start = virt_viewer_start; app_class->add_option_entries = virt_viewer_add_option_entries; g_app_class->local_command_line = virt_viewer_local_command_line; } static void virt_viewer_init(VirtViewer *self) { self->priv = GET_PRIVATE(self); self->priv->domain_event = -1; } static gboolean virt_viewer_connect_timer(void *opaque) { VirtViewer *self = VIRT_VIEWER(opaque); VirtViewerApp *app = VIRT_VIEWER_APP(self); g_debug("Connect timer fired"); if (!virt_viewer_app_is_active(app) && !virt_viewer_app_initial_connect(app, NULL)) g_application_quit(G_APPLICATION(app)); if (virt_viewer_app_is_active(app)) { self->priv->reconnect_poll = 0; return FALSE; } return TRUE; } static void virt_viewer_start_reconnect_poll(VirtViewer *self) { VirtViewerPrivate *priv = self->priv; g_debug("reconnect_poll: %d", priv->reconnect_poll); if (priv->reconnect_poll != 0) return; priv->reconnect_poll = g_timeout_add(500, virt_viewer_connect_timer, self); } static void virt_viewer_stop_reconnect_poll(VirtViewer *self) { VirtViewerPrivate *priv = self->priv; g_debug("reconnect_poll: %d", priv->reconnect_poll); if (priv->reconnect_poll == 0) return; g_source_remove(priv->reconnect_poll); priv->reconnect_poll = 0; } static void virt_viewer_deactivated(VirtViewerApp *app, gboolean connect_error) { VirtViewer *self = VIRT_VIEWER(app); VirtViewerPrivate *priv = self->priv; if (priv->dom) { virDomainFree(priv->dom); priv->dom = NULL; } if (priv->reconnect && !virt_viewer_app_get_session_cancelled(app)) { if (priv->domain_event < 0) { g_debug("No domain events, falling back to polling"); virt_viewer_start_reconnect_poll(self); } virt_viewer_app_show_status(app, _("Waiting for guest domain to re-start")); virt_viewer_app_trace(app, "Guest %s display has disconnected, waiting to reconnect", priv->domkey); virt_viewer_app_set_menus_sensitive(app, FALSE); } else { VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->deactivated(app, connect_error); } } static int virt_viewer_parse_uuid(const char *name, unsigned char *uuid) { int i; const char *cur = name; for (i = 0;i < 16;) { uuid[i] = 0; if (*cur == 0) return -1; if ((*cur == '-') || (*cur == ' ')) { cur++; continue; } if ((*cur >= '0') && (*cur <= '9')) uuid[i] = *cur - '0'; else if ((*cur >= 'a') && (*cur <= 'f')) uuid[i] = *cur - 'a' + 10; else if ((*cur >= 'A') && (*cur <= 'F')) uuid[i] = *cur - 'A' + 10; else return -1; uuid[i] *= 16; cur++; if (*cur == 0) return -1; if ((*cur >= '0') && (*cur <= '9')) uuid[i] += *cur - '0'; else if ((*cur >= 'a') && (*cur <= 'f')) uuid[i] += *cur - 'a' + 10; else if ((*cur >= 'A') && (*cur <= 'F')) uuid[i] += *cur - 'A' + 10; else return -1; i++; cur++; } return 0; } static virDomainPtr virt_viewer_lookup_domain(VirtViewer *self) { char *end; VirtViewerPrivate *priv = self->priv; int id; virDomainPtr dom = NULL; unsigned char uuid[16]; if (priv->domkey == NULL) { return NULL; } id = strtol(priv->domkey, &end, 10); if (id >= 0 && end && !*end) { dom = virDomainLookupByID(priv->conn, id); } if (!dom && virt_viewer_parse_uuid(priv->domkey, uuid) == 0) { dom = virDomainLookupByUUID(priv->conn, uuid); } if (!dom) { dom = virDomainLookupByName(priv->conn, priv->domkey); } return dom; } static int virt_viewer_matches_domain(VirtViewer *self, virDomainPtr dom) { char *end; const char *name; VirtViewerPrivate *priv = self->priv; int id = strtol(priv->domkey, &end, 10); unsigned char wantuuid[16]; unsigned char domuuid[16]; if (id >= 0 && end && !*end) { if (virDomainGetID(dom) == id) return 1; } if (virt_viewer_parse_uuid(priv->domkey, wantuuid) == 0) { virDomainGetUUID(dom, domuuid); if (memcmp(wantuuid, domuuid, VIR_UUID_BUFLEN) == 0) return 1; } name = virDomainGetName(dom); if (strcmp(name, priv->domkey) == 0) return 1; return 0; } static char * virt_viewer_extract_xpath_string(const gchar *xmldesc, const gchar *xpath) { xmlDocPtr xml = NULL; xmlParserCtxtPtr pctxt = NULL; xmlXPathContextPtr ctxt = NULL; xmlXPathObjectPtr obj = NULL; char *port = NULL; pctxt = xmlNewParserCtxt(); if (!pctxt || !pctxt->sax) goto error; xml = xmlCtxtReadDoc(pctxt, (const xmlChar *)xmldesc, "domain.xml", NULL, XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOWARNING); if (!xml) goto error; ctxt = xmlXPathNewContext(xml); if (!ctxt) goto error; obj = xmlXPathEval((const xmlChar *)xpath, ctxt); if (!obj || obj->type != XPATH_STRING || !obj->stringval || !obj->stringval[0]) goto error; if (!strcmp((const char*)obj->stringval, "-1")) goto error; port = g_strdup((const char*)obj->stringval); xmlXPathFreeObject(obj); obj = NULL; error: xmlXPathFreeObject(obj); xmlXPathFreeContext(ctxt); xmlFreeDoc(xml); xmlFreeParserCtxt(pctxt); return port; } static gboolean virt_viewer_replace_host(const gchar *host) { GInetAddress *addr; gboolean ret; if (!host) return TRUE; addr = g_inet_address_new_from_string(host); if (!addr) /* Parsing error means it was probably a hostname */ return FALSE; ret = g_inet_address_get_is_any(addr); g_object_unref(addr); return ret; } static gboolean virt_viewer_is_loopback(const char *host) { GInetAddress *addr = NULL; gboolean is_loopback = FALSE; g_return_val_if_fail(host != NULL, FALSE); addr = g_inet_address_new_from_string(host); if (!addr) /* Parsing error means it was probably a hostname */ return (strcmp(host, "localhost") == 0); is_loopback = g_inet_address_get_is_loopback(addr); g_object_unref(addr); return is_loopback; } static gboolean virt_viewer_is_reachable(const gchar *host, const char *transport, const char *transport_host, gboolean direct) { gboolean host_is_loopback; gboolean transport_is_loopback; if (!host) return FALSE; if (!transport) return TRUE; if (strcmp(transport, "ssh") == 0 && !direct) return TRUE; if (strcmp(transport, "unix") == 0) return TRUE; host_is_loopback = virt_viewer_is_loopback(host); transport_is_loopback = virt_viewer_is_loopback(transport_host); if (transport_is_loopback && host_is_loopback) return TRUE; else return !host_is_loopback; } static gboolean virt_viewer_extract_connect_info(VirtViewer *self, virDomainPtr dom, GError **error) { char *type = NULL; char *xpath = NULL; gboolean retval = FALSE; char *xmldesc = virDomainGetXMLDesc(dom, 0); VirtViewerPrivate *priv = self->priv; VirtViewerApp *app = VIRT_VIEWER_APP(self); gchar *gport = NULL; gchar *gtlsport = NULL; gchar *ghost = NULL; gchar *unixsock = NULL; gchar *host = NULL; gchar *transport = NULL; gchar *user = NULL; gint port = 0; gchar *uri = NULL; gboolean direct = virt_viewer_app_get_direct(app); virt_viewer_app_free_connect_info(app); if ((type = virt_viewer_extract_xpath_string(xmldesc, "string(/domain/devices/graphics/@type)")) == NULL) { g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, _("Cannot determine the graphic type for the guest %s"), priv->domkey); goto cleanup; } if (!virt_viewer_app_create_session(app, type, error)) goto cleanup; xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@port)", type); gport = virt_viewer_extract_xpath_string(xmldesc, xpath); g_free(xpath); if (g_str_equal(type, "spice")) { xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@tlsPort)", type); gtlsport = virt_viewer_extract_xpath_string(xmldesc, xpath); g_free(xpath); } if (gport || gtlsport) { xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@listen)", type); ghost = virt_viewer_extract_xpath_string(xmldesc, xpath); } else { xpath = g_strdup_printf("string(/domain/devices/graphics[@type='%s']/@socket)", type); unixsock = virt_viewer_extract_xpath_string(xmldesc, xpath); } if (ghost && gport) { g_debug("Guest graphics address is %s:%s", ghost, gport); } else if (unixsock) { g_debug("Guest graphics address is %s", unixsock); } else { g_debug("Using direct libvirt connection"); retval = TRUE; goto cleanup; } uri = virConnectGetURI(priv->conn); if (virt_viewer_util_extract_host(uri, NULL, &host, &transport, &user, &port) < 0) { g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, _("Cannot determine the host for the guest %s"), priv->domkey); goto cleanup; } /* If the XML listen attribute shows a wildcard address, we need to * throw that away since you obviously can't 'connect(2)' to that * from a remote host. Instead we fallback to the hostname used in * the libvirt URI. This isn't perfect but it is better than nothing. * If the transport is SSH, fallback to localhost as the connection * will be made from the remote end of the ssh connection. */ if (virt_viewer_replace_host(ghost)) { gchar *replacement_host = NULL; if ((g_strcmp0(transport, "ssh") == 0) && !direct) { replacement_host = g_strdup("localhost"); } else { replacement_host = g_strdup(host); } g_debug("Guest graphics listen '%s' is NULL or a wildcard, replacing with '%s'", ghost ? ghost : "", replacement_host); g_free(ghost); ghost = replacement_host; } if (!virt_viewer_is_reachable(ghost, transport, host, direct)) { g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, _("Guest '%s' is not reachable"), priv->domkey); g_debug("graphics listen '%s' is not reachable from this machine", ghost ? ghost : ""); goto cleanup; } virt_viewer_app_set_connect_info(app, host, ghost, gport, gtlsport,transport, unixsock, user, port, NULL); retval = TRUE; cleanup: g_free(gport); g_free(gtlsport); g_free(ghost); g_free(unixsock); g_free(host); g_free(transport); g_free(user); g_free(type); g_free(xpath); g_free(xmldesc); g_free(uri); return retval; } static gboolean virt_viewer_update_display(VirtViewer *self, virDomainPtr dom, GError **error) { VirtViewerPrivate *priv = self->priv; VirtViewerApp *app = VIRT_VIEWER_APP(self); if (priv->dom) virDomainFree(priv->dom); priv->dom = dom; virDomainRef(priv->dom); virt_viewer_app_trace(app, "Guest %s is running, determining display", priv->domkey); g_object_set(app, "guest-name", virDomainGetName(dom), NULL); if (virt_viewer_app_has_session(app)) return TRUE; return virt_viewer_extract_connect_info(self, dom, error); } static gboolean virt_viewer_open_connection(VirtViewerApp *self G_GNUC_UNUSED, int *fd) { VirtViewer *viewer = VIRT_VIEWER(self); VirtViewerPrivate *priv = viewer->priv; #if defined(HAVE_SOCKETPAIR) || defined(HAVE_VIR_DOMAIN_OPEN_GRAPHICS_FD) virErrorPtr err; #endif #if defined(HAVE_SOCKETPAIR) int pair[2]; #endif *fd = -1; if (!priv->dom) return TRUE; #ifdef HAVE_VIR_DOMAIN_OPEN_GRAPHICS_FD if ((*fd = virDomainOpenGraphicsFD(priv->dom, 0, VIR_DOMAIN_OPEN_GRAPHICS_SKIPAUTH)) >= 0) return TRUE; err = virGetLastError(); if (err && err->code != VIR_ERR_NO_SUPPORT) { g_debug("Error %s", err->message ? err->message : "Unknown"); return TRUE; } #endif #if defined(HAVE_SOCKETPAIR) if (socketpair(PF_UNIX, SOCK_STREAM, 0, pair) < 0) return FALSE; if (virDomainOpenGraphics(priv->dom, 0, pair[0], VIR_DOMAIN_OPEN_GRAPHICS_SKIPAUTH) < 0) { err = virGetLastError(); g_debug("Error %s", err && err->message ? err->message : "Unknown"); close(pair[0]); close(pair[1]); return TRUE; } close(pair[0]); *fd = pair[1]; #endif return TRUE; } static int virt_viewer_domain_event(virConnectPtr conn G_GNUC_UNUSED, virDomainPtr dom, int event, int detail G_GNUC_UNUSED, void *opaque) { VirtViewer *self = opaque; VirtViewerApp *app = VIRT_VIEWER_APP(self); GError *error = NULL; g_debug("Got domain event %d %d", event, detail); if (!virt_viewer_matches_domain(self, dom)) return 0; switch (event) { case VIR_DOMAIN_EVENT_STOPPED: //virt_viewer_deactivate(self); break; case VIR_DOMAIN_EVENT_STARTED: virt_viewer_update_display(self, dom, &error); if (error) { virt_viewer_app_simple_message_dialog(app, error->message); g_clear_error(&error); } virt_viewer_app_activate(app, &error); if (error) { /* we may want to consolidate error reporting in app_activate() instead */ g_warning("%s", error->message); g_clear_error(&error); } break; } return 0; } static void virt_viewer_conn_event(virConnectPtr conn G_GNUC_UNUSED, int reason, void *opaque) { VirtViewer *self = opaque; VirtViewerPrivate *priv = self->priv; g_debug("Got connection event %d", reason); virConnectClose(priv->conn); priv->conn = NULL; virt_viewer_start_reconnect_poll(self); } static void virt_viewer_dispose (GObject *object) { VirtViewer *self = VIRT_VIEWER(object); VirtViewerPrivate *priv = self->priv; if (priv->conn) { if (priv->domain_event >= 0) { virConnectDomainEventDeregisterAny(priv->conn, priv->domain_event); priv->domain_event = -1; } virConnectUnregisterCloseCallback(priv->conn, virt_viewer_conn_event); virConnectClose(priv->conn); priv->conn = NULL; } if (priv->dom) { virDomainFree(priv->dom); priv->dom = NULL; } g_free(priv->uri); priv->uri = NULL; g_free(priv->domkey); priv->domkey = NULL; G_OBJECT_CLASS(virt_viewer_parent_class)->dispose (object); } static virDomainPtr choose_vm(GtkWindow *main_window, char **vm_name, virConnectPtr conn, GError **error) { GtkListStore *model; GtkTreeIter iter; virDomainPtr *domains, dom = NULL; int i, vms_running; unsigned int flags = VIR_CONNECT_LIST_DOMAINS_RUNNING; g_return_val_if_fail(vm_name != NULL, NULL); free(*vm_name); model = gtk_list_store_new(1, G_TYPE_STRING); vms_running = virConnectListAllDomains(conn, &domains, flags); for (i = 0; i < vms_running; i++) { gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, 0, virDomainGetName(domains[i]), -1); virDomainFree(domains[i]); } free(domains); *vm_name = virt_viewer_vm_connection_choose_name_dialog(main_window, GTK_TREE_MODEL(model), error); g_object_unref(G_OBJECT(model)); if (*vm_name == NULL) return NULL; dom = virDomainLookupByName(conn, *vm_name); if (dom == NULL) { virErrorPtr err = virGetLastError(); g_set_error_literal(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, err && err->message ? err->message : "unknown libvirt error"); } else if (virDomainGetState(dom, &i, NULL, 0) < 0 || i != VIR_DOMAIN_RUNNING) { g_set_error(error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, _("Virtual machine %s is not running"), *vm_name); virDomainFree(dom); dom = NULL; } return dom; } static gboolean virt_viewer_initial_connect(VirtViewerApp *app, GError **error) { virDomainPtr dom = NULL; virDomainInfo info; gboolean ret = FALSE; VirtViewer *self = VIRT_VIEWER(app); VirtViewerPrivate *priv = self->priv; char uuid_string[VIR_UUID_STRING_BUFLEN]; GError *err = NULL; g_debug("initial connect"); if (!priv->conn && virt_viewer_connect(app, &err) < 0) { virt_viewer_app_show_status(app, _("Waiting for libvirt to start")); goto wait; } virt_viewer_app_show_status(app, _("Finding guest domain")); dom = virt_viewer_lookup_domain(self); if (!dom) { if (priv->waitvm) { virt_viewer_app_show_status(app, _("Waiting for guest domain to be created")); goto wait; } else { VirtViewerWindow *main_window = virt_viewer_app_get_main_window(app); if (priv->domkey != NULL) g_debug("Cannot find guest %s", priv->domkey); dom = choose_vm(virt_viewer_window_get_window(main_window), &priv->domkey, priv->conn, &err); if (dom == NULL) { goto cleanup; } } } if (virDomainGetUUIDString(dom, uuid_string) < 0) { g_debug("Couldn't get uuid from libvirt"); } else { g_object_set(app, "uuid", uuid_string, NULL); } virt_viewer_app_show_status(app, _("Checking guest domain status")); if (virDomainGetInfo(dom, &info) < 0) { g_set_error_literal(&err, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, _("Cannot get guest state")); g_debug("%s", err->message); goto cleanup; } if (info.state == VIR_DOMAIN_SHUTOFF) { virt_viewer_app_show_status(app, _("Waiting for guest domain to start")); goto wait; } if (!virt_viewer_update_display(self, dom, &err)) goto cleanup; ret = VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->initial_connect(app, &err); if (ret || err) goto cleanup; wait: virt_viewer_app_trace(app, "Guest %s has not activated its display yet, waiting " "for it to start", priv->domkey); ret = TRUE; cleanup: if (err != NULL) g_propagate_error(error, err); if (dom) virDomainFree(dom); return ret; } static void virt_viewer_error_func (void *data G_GNUC_UNUSED, virErrorPtr error G_GNUC_UNUSED) { /* nothing */ } static int virt_viewer_auth_libvirt_credentials(virConnectCredentialPtr cred, unsigned int ncred, void *cbdata) { char **username = NULL, **password = NULL; VirtViewer *app = cbdata; VirtViewerPrivate *priv = app->priv; int i; int ret = 0; g_debug("Got libvirt credential request for %d credential(s)", ncred); for (i = 0 ; i < ncred ; i++) { switch (cred[i].type) { case VIR_CRED_USERNAME: case VIR_CRED_AUTHNAME: username = &cred[i].result; break; case VIR_CRED_PASSPHRASE: password = &cred[i].result; break; default: g_debug("Unsupported libvirt credential %d", cred[i].type); return -1; } } if (username || password) { VirtViewerWindow *vwin = virt_viewer_app_get_main_window(VIRT_VIEWER_APP(app)); GtkWindow *win = virt_viewer_window_get_window(vwin); if (username && (*username == NULL || **username == '\0')) *username = g_strdup(g_get_user_name()); priv->auth_cancelled = !virt_viewer_auth_collect_credentials(win, "libvirt", app->priv->uri, username, password); if (priv->auth_cancelled) { ret = -1; goto cleanup; } } for (i = 0 ; i < ncred ; i++) { switch (cred[i].type) { case VIR_CRED_AUTHNAME: case VIR_CRED_USERNAME: case VIR_CRED_PASSPHRASE: if (cred[i].result) cred[i].resultlen = strlen(cred[i].result); else cred[i].resultlen = 0; g_debug("Got '%s' %d %d", cred[i].result, cred[i].resultlen, cred[i].type); break; } } cleanup: g_debug("Return %d", ret); return ret; } static gchar * virt_viewer_get_error_message_from_vir_error(VirtViewer *self, virErrorPtr error) { VirtViewerPrivate *priv = self->priv; const gchar *error_details = NULL; gchar *detailed_error_message = NULL; gchar *error_message = g_strdup_printf(_("Unable to connect to libvirt with URI: %s."), priv->uri ? priv->uri : _("[none]")); g_debug("Error: %s", error->message); /* For now we are only treating authentication errors. */ switch (error->code) { case VIR_ERR_AUTH_FAILED: error_details = _("Authentication failed."); break; default: break; } if (error_details != NULL) { detailed_error_message = g_strdup_printf("%s\n%s", error_message, error_details); g_free(error_message); return detailed_error_message; } return error_message; } static int virt_viewer_connect(VirtViewerApp *app, GError **err) { VirtViewer *self = VIRT_VIEWER(app); VirtViewerPrivate *priv = self->priv; int cred_types[] = { VIR_CRED_AUTHNAME, VIR_CRED_PASSPHRASE }; virConnectAuth auth_libvirt = { .credtype = cred_types, .ncredtype = G_N_ELEMENTS(cred_types), .cb = virt_viewer_auth_libvirt_credentials, .cbdata = app, }; int oflags = 0; GError *error = NULL; if (!virt_viewer_app_get_attach(app)) oflags |= VIR_CONNECT_RO; g_debug("connecting ..."); virt_viewer_app_trace(app, "Opening connection to libvirt with URI %s", priv->uri ? priv->uri : ""); priv->conn = virConnectOpenAuth(priv->uri, //virConnectAuthPtrDefault, &auth_libvirt, oflags); if (!priv->conn) { if (!priv->auth_cancelled) { gchar *error_message = virt_viewer_get_error_message_from_vir_error(self, virGetLastError()); g_set_error_literal(&error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_FAILED, error_message); g_free(error_message); } else { g_set_error_literal(&error, VIRT_VIEWER_ERROR, VIRT_VIEWER_ERROR_CANCELLED, _("Authentication was cancelled")); } g_propagate_error(err, error); return -1; } if (!virt_viewer_app_initial_connect(app, &error)) { g_propagate_prefixed_error(err, error, _("Failed to connect: ")); return -1; } priv->domain_event = virConnectDomainEventRegisterAny(priv->conn, priv->dom, VIR_DOMAIN_EVENT_ID_LIFECYCLE, VIR_DOMAIN_EVENT_CALLBACK(virt_viewer_domain_event), self, NULL); if (priv->domain_event < 0 && !virt_viewer_app_is_active(app)) { g_debug("No domain events, falling back to polling"); virt_viewer_start_reconnect_poll(self); } else { /* we may be polling if we lost the libvirt connection and are trying * to reconnect */ virt_viewer_stop_reconnect_poll(self); } if (virConnectRegisterCloseCallback(priv->conn, virt_viewer_conn_event, self, NULL) < 0) { g_debug("Unable to register close callback on libvirt connection"); } if (virConnectSetKeepAlive(priv->conn, 5, 3) < 0) { g_debug("Unable to set keep alive"); } return 0; } static gboolean virt_viewer_start(VirtViewerApp *app, GError **error) { gvir_event_register(); virSetErrorFunc(NULL, virt_viewer_error_func); if (virt_viewer_connect(app, error) < 0) return FALSE; return VIRT_VIEWER_APP_CLASS(virt_viewer_parent_class)->start(app, error); } VirtViewer * virt_viewer_new(void) { return g_object_new(VIRT_VIEWER_TYPE, "application-id", "org.virt-manager.virt-viewer", "flags", G_APPLICATION_NON_UNIQUE, NULL); } /* * Local variables: * c-indent-level: 4 * c-basic-offset: 4 * indent-tabs-mode: nil * End: */