From dc0e64a4c244fa084eb7bfce4503404824ada6ef Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 28 Nov 2008 15:20:11 +0000 Subject: [PATCH] Daily update. --- internal.h | 56 +++++++++- main.c | 160 +++++++++++++++++++++++++++- wui_thread.c | 334 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 534 insertions(+), 16 deletions(-) diff --git a/internal.h b/internal.h index d838fde..2fbf2c7 100644 --- a/internal.h +++ b/internal.h @@ -83,16 +83,30 @@ extern void wui_thread_send_refresh_vm_list (void); /* Retrieve the list of VMs. * + * wui_thread_get_vmlist returns TRUE if there was a valid list of + * VMs (even if it is empty), or FALSE if we don't have a valid list + * of VMs to return. + * * NB: Caller must call free_vmlist once it is done with the list. * - * This can return NULL if the WUI thread doesn't have a valid - * list of VMs to return to the caller. In the case of a valid, - * empty list, you will get a non-NULL GSList pointer to an empty - * list. */ -extern GSList *wui_thread_get_vmlist (void); +extern gboolean wui_thread_get_vmlist (GSList **ret); extern void free_vmlist (GSList *vmlist); +struct vm { + char *description; + int hostid; + int id; + long mem_allocated; /* Kbytes */ + long mem_used; /* Kbytes */ + int vcpus_allocated; + int vcpus_used; + char *state; + char *uuid; /* Printable UUID. */ + int vnc_port; + char *mac_addr; /* Printable MAC addr. */ +}; + /* Returns true if the WUI thread thinks it is connected to a remote * WUI. REST is connectionless so really this means that we * successfully made an HTTP/HTTPS request "recently", and we haven't @@ -117,4 +131,36 @@ extern gboolean wui_thread_has_valid_vmlist (void); */ extern gboolean wui_thread_is_busy (void); +/* Callbacks from the WUI thread to the main thread. The WUI thread + * adds these to the Glib main loop using g_idle_add, which means they + * actually get executed in the context of the main thread. + */ + +/* The WUI thread has changed its state to connected. */ +extern gboolean main_connected (gpointer); + +/* The WUI thread has changed its state to disconnected. */ +extern gboolean main_disconnected (gpointer); + +/* The WUI thread has changed its state to logged in. */ +extern gboolean main_logged_in (gpointer); + +/* The WUI thread has changed its state to logged out. */ +extern gboolean main_logged_out (gpointer); + +/* The WUI thread has changed its state to busy. */ +extern gboolean main_busy (gpointer); + +/* The WUI thread has changed its state to idle. */ +extern gboolean main_idle (gpointer); + +/* The WUI thread had a connection problem. */ +extern gboolean main_connection_error (gpointer str); + +/* The WUI thread had a login problem. */ +extern gboolean main_login_error (gpointer str); + +/* The WUI thread reports a general status error. */ +extern gboolean main_status_error (gpointer str); + #endif /* OVIRT_VIEWER_INTERNAL_H */ diff --git a/main.c b/main.c index 1761864..0545ec4 100644 --- a/main.c +++ b/main.c @@ -39,10 +39,6 @@ #include #endif -#ifndef G_THREADS_ENABLED -#error "This program requires GLib threads, and cannot be compiled without." -#endif - #include "internal.h" /*#define HTTPS "https"*/ @@ -60,17 +56,21 @@ gboolean check_cert = TRUE; static void start_ui (void); static GtkWidget *menu_item_new (int which_menu); static void connect_to_wui (GtkWidget *, gpointer); +static void login_to_wui (GtkWidget *, gpointer); static gboolean delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); static void destroy (GtkWidget *widget, gpointer data); /* For any widgets accessed from multiple functions. */ +static GtkWidget *window; static GtkWidget *connection_area; static GtkWidget *ca_hostname; static GtkWidget *ca_button; +static GtkWidget *ca_error; static GtkWidget *login_area; static GtkWidget *la_username; static GtkWidget *la_password; static GtkWidget *la_button; +static GdkCursor *busy_cursor; /* Menus. */ enum menuNums { @@ -201,7 +201,6 @@ main (int argc, char *argv[]) static void start_ui (void) { - GtkWidget *window; GtkWidget *vbox; GtkWidget *menubar; GtkWidget *file; @@ -225,6 +224,11 @@ start_ui (void) /* Parse styles. */ gtk_rc_parse_string (styles); + /* Busy cursor, used by main_busy() function. + * XXX This cursor is crap - how can we use the Bluecurve/theme cursor? + */ + busy_cursor = gdk_cursor_new (GDK_WATCH); + /* Window. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); @@ -282,10 +286,12 @@ start_ui (void) ca_hostname = gtk_entry_new (); gtk_entry_set_width_chars (GTK_ENTRY (ca_hostname), 24); ca_button = gtk_button_new_with_label ("Connect"); + ca_error = gtk_label_new (NULL); gtk_box_pack_start (GTK_BOX (ca_hbox), ca_hostname, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (ca_hbox), ca_button, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (ca_vbox), ca_label, FALSE, FALSE, 4); gtk_box_pack_start (GTK_BOX (ca_vbox), ca_hbox, TRUE, FALSE, 4); + gtk_box_pack_start (GTK_BOX (ca_vbox), ca_error, TRUE, FALSE, 4); gtk_container_add (GTK_CONTAINER (connection_area), ca_vbox); gtk_widget_set_name (connection_area, "ovirt-viewer-connection-area"); @@ -308,6 +314,9 @@ start_ui (void) gtk_widget_set_name (login_area, "ovirt-viewer-login-area"); + g_signal_connect (G_OBJECT (la_button), "clicked", + G_CALLBACK (login_to_wui), NULL); + /* Tabbed notebook. */ notebook = gtk_notebook_new (); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT); @@ -388,3 +397,144 @@ connect_to_wui (GtkWidget *widget, gpointer data) wui_thread_send_connect (uri); } + +static void +login_to_wui (GtkWidget *widget, gpointer data) +{ + const char *username, *password; + + username = gtk_entry_get_text (GTK_ENTRY (la_username)); + if (STREQ (username, "")) return; + password = gtk_entry_get_text (GTK_ENTRY (la_password)); + + wui_thread_send_login (username, password); +} + +/* The WUI thread has changed its state to connected. */ +gboolean +main_connected (gpointer data) +{ + DEBUG ("connected"); + + gtk_label_set_text (GTK_LABEL (ca_error), NULL); + + gtk_widget_hide (connection_area); + if (!wui_thread_is_logged_in ()) + gtk_widget_show (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to disconnected. */ +gboolean +main_disconnected (gpointer data) +{ + DEBUG ("disconnected"); + gtk_widget_show (connection_area); + gtk_widget_hide (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to logged in. */ +gboolean +main_logged_in (gpointer data) +{ + DEBUG ("logged in"); + gtk_widget_hide (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to logged out. */ +gboolean +main_logged_out (gpointer data) +{ + DEBUG ("logged out"); + if (wui_thread_is_connected ()) + gtk_widget_show (login_area); + return FALSE; +} + +/* The WUI thread has changed its state to busy. */ +gboolean +main_busy (gpointer data) +{ + GdkWindow *gdk_window; + + DEBUG ("busy"); + + gdk_window = gtk_widget_get_window (window); + if (gdk_window) { + gdk_window_set_cursor (gdk_window, busy_cursor); + gdk_flush (); + } + + return FALSE; +} + +/* The WUI thread has changed its state to idle. */ +gboolean +main_idle (gpointer data) +{ + GdkWindow *gdk_window; + + DEBUG ("idle"); + + gdk_window = gtk_widget_get_window (window); + if (gdk_window) { + gdk_window_set_cursor (gdk_window, NULL); + gdk_flush (); + } + + return FALSE; +} + +/* The WUI thread had a connection error. This function must + * free the string. + */ +gboolean +main_connection_error (gpointer _str) +{ + char *str = (char *) _str; + + DEBUG ("connection error: %s", str); + + gtk_label_set_text (GTK_LABEL (ca_error), str); + g_free (str); + + return FALSE; +} + +/* The WUI thread had a login error. This function must + * free the string. + */ +gboolean +main_login_error (gpointer _str) +{ + char *str = (char *) _str; + + DEBUG ("login error: %s", str); + + /* + gtk_label_set_text (GTK_LABEL (ca_error), str); + */ + g_free (str); + + return FALSE; +} + +/* The WUI thread reports a general status error. This function + * must free the string. + */ +gboolean +main_status_error (gpointer _str) +{ + char *str = (char *) _str; + + DEBUG ("status error: %s", str); + + /* + gtk_label_set_text (GTK_LABEL (ca_error), str); + */ + g_free (str); + + return FALSE; +} diff --git a/wui_thread.c b/wui_thread.c index 8079f75..49dc270 100644 --- a/wui_thread.c +++ b/wui_thread.c @@ -21,9 +21,11 @@ #include #include +#include #include #include +#include #include @@ -33,9 +35,13 @@ static gpointer wui_thread (gpointer data); static void wui_thread_send_quit (void); static void do_curl_init (void); +static void write_fn_start_capture (void); +static char *write_fn_finish_capture (void); +static void write_fn_discard_capture_buffer (void); static gboolean do_connect (void); static gboolean do_login (void); -static void refresh_vm_list (void); +static gboolean refresh_vm_list (void); +static void parse_vmlist_from_xml (const char *xml); /* Messages (main thread -> WUI thread only). * @@ -251,6 +257,11 @@ set_connected (gboolean new_connected) g_static_mutex_lock (&state_mutex); connected = new_connected; g_static_mutex_unlock (&state_mutex); + + if (connected) + g_idle_add (main_connected, NULL); + else + g_idle_add (main_disconnected, NULL); } static void @@ -259,6 +270,11 @@ set_logged_in (gboolean new_logged_in) g_static_mutex_lock (&state_mutex); logged_in = new_logged_in; g_static_mutex_unlock (&state_mutex); + + if (logged_in) + g_idle_add (main_logged_in, NULL); + else + g_idle_add (main_logged_out, NULL); } static void @@ -267,6 +283,11 @@ set_busy (gboolean new_busy) g_static_mutex_lock (&state_mutex); busy = new_busy; g_static_mutex_unlock (&state_mutex); + + if (busy) + g_idle_add (main_busy, NULL); + else + g_idle_add (main_idle, NULL); } /* The main thread should call these functions to get the WUI thread's @@ -311,6 +332,7 @@ process_message (struct message *msg) { switch (msg->type) { case QUIT: + write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); if (uri) g_free (uri); if (username) g_free (username); @@ -320,6 +342,7 @@ process_message (struct message *msg) return 1; case CONNECT: + write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); do_curl_init (); if (uri) g_free (uri); @@ -335,6 +358,7 @@ process_message (struct message *msg) case DISCONNECT: /* This just forgets the state. REST is connectionless. */ + write_fn_discard_capture_buffer (); if (curl) curl_easy_cleanup (curl); curl = NULL; if (uri) g_free (uri); @@ -360,7 +384,8 @@ process_message (struct message *msg) if (connected) { if (do_login ()) { set_logged_in (TRUE); - refresh_vm_list (); + if (refresh_vm_list ()) + secs_between_refresh = 60; } else { set_logged_in (FALSE); } @@ -392,13 +417,60 @@ process_message (struct message *msg) __r; \ }) +/* Libcurl has a really crufty method for handling HTTP headers and + * data. We set these functions as callbacks, because the default + * callback functions print the data out to stderr. In order to + * capture the data, we have to keep state here. + */ + +static char *write_fn_buffer = NULL; +static ssize_t write_fn_len = -1; + static size_t write_fn (void *ptr, size_t size, size_t nmemb, void *stream) { int bytes = size * nmemb; + int old_start; + + if (write_fn_len >= 0) { /* We're capturing. */ + old_start = write_fn_len; + write_fn_len += bytes; + write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len); + memcpy (write_fn_buffer + old_start, ptr, bytes); + } + return bytes; } +/* Start capturing HTTP response data. */ +static void +write_fn_start_capture (void) +{ + write_fn_discard_capture_buffer (); + write_fn_buffer = NULL; + write_fn_len = 0; +} + +/* Finish capture and return the capture buffer. Caller must free. */ +static char * +write_fn_finish_capture (void) +{ + char *ret = write_fn_buffer; + + write_fn_buffer = NULL; + write_fn_len = -1; + return ret; +} + +/* Stop capturing and discard the capture buffer, if any. */ +static void +write_fn_discard_capture_buffer (void) +{ + g_free (write_fn_buffer); + write_fn_buffer = NULL; + write_fn_len = -1; +} + static size_t header_fn (void *ptr, size_t size, size_t nmemb, void *stream) { @@ -446,6 +518,8 @@ static gboolean do_connect (void) { long code = 0; + CURLcode r; + char *error_str; DEBUG ("connecting to uri %s", uri); @@ -453,13 +527,22 @@ do_connect (void) CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri)); /* Try to fetch the URI. */ - if (CURL_CHECK_ERROR (curl_easy_perform, (curl)) != CURLE_OK) + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_connection_error, error_str); return FALSE; + } CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); DEBUG ("HTTP return code is %ld", code); - if (code != 200 || code != 302 || code != 401) + if (code != 200 && code != 302 && code != 401) { + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_connection_error, error_str); return FALSE; + } return TRUE; } @@ -470,13 +553,252 @@ do_connect (void) static gboolean do_login (void) { + int len; + char *login_uri; + char *userpwd; + char *error_str; + CURLcode r; + long code = 0; + DEBUG ("logging in with username %s, password *****", username); - return FALSE; + + /* Generate the login URI from the base URI. */ + len = strlen (uri) + 6 + 1; + login_uri = g_alloca (len); + snprintf (login_uri, len, "%s/login", uri); + + DEBUG ("login URI %s", login_uri); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri)); + + /* Construct the username:password for CURL. */ + len = strlen (username) + strlen (password) + 2; + userpwd = g_alloca (len); + snprintf (userpwd, len, "%s:%s", username, password); + + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd)); + + /* HTTP Basic authentication is OK since we should be sending + * this only over HTTPS. + */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC)); + + /* Follow redirects. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1)); + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10)); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_login_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + switch (code) + { + case 200: + DEBUG ("login was successful"); + return TRUE; + + case 401: + error_str = g_strdup ("server rejected the username or password"); + g_idle_add (main_login_error, error_str); + return FALSE; + + default: + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_login_error, error_str); + return FALSE; + } } /* Called from the message loop. Refresh the list of VMs. */ -static void +static gboolean refresh_vm_list (void) { + int len; + char *vms_uri; + char *error_str; + CURLcode r; + long code = 0; + char *xml; + DEBUG ("refreshing list of VMs"); + + /* Generate the vms URI from the base URI. */ + len = strlen (uri) + 4 + 1; + vms_uri = g_alloca (len); + snprintf (vms_uri, len, "%s/vms", uri); + + DEBUG ("vms URI %s", vms_uri); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri)); + + /* We want to capture the output, so tell our write function + * to place the output into a buffer. + */ + write_fn_start_capture (); + + /* Try to fetch the URI. */ + r = CURL_CHECK_ERROR (curl_easy_perform, (curl)); + if (r != CURLE_OK) { + /* Signal an error back to the main thread. */ + error_str = g_strdup (curl_easy_strerror (r)); + g_idle_add (main_login_error, error_str); + return FALSE; + } + + CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code)); + DEBUG ("HTTP return code is %ld", code); + switch (code) + { + case 200: break; + + /* Hmm - even though we previously logged in, the server is + * rejecting our attempts now with an authorization error. + * We move to the logged out state. + */ + case 401: + set_logged_in (FALSE); + error_str = g_strdup ("server rejected the username or password"); + g_idle_add (main_login_error, error_str); + return FALSE; + + default: + /* XXX If only glib had g_asprintf. */ + error_str = g_strdup ("unexpected HTTP return code from server"); + g_idle_add (main_status_error, error_str); + return FALSE; + } + + /* If we got here then we appear to have a correct + * XML document describing the list of VMs. + */ + secs_between_refresh <<= 1; + + xml = write_fn_finish_capture (); + + /*DEBUG ("XML from /vms =\n%s", xml);*/ + parse_vmlist_from_xml (xml); + g_free (xml); + + return TRUE; +} + +/* Functions to deal with the list of VMs, parsing it from the XML, etc. + * + * A vmlist is a GSList (glib singly-linked list) of vm structures. + * The caller must call free_vmlist to free up this list correctly. + */ + +static void +free_vm (gpointer _vm, gpointer data) +{ + struct vm *vm = (struct vm *) _vm; + + g_free (vm->description); + g_free (vm->state); + g_free (vm->uuid); + g_free (vm->mac_addr); + g_free (vm); +} + +void +free_vmlist (GSList *vmlist) +{ + g_slist_foreach (vmlist, free_vm, NULL); + g_slist_free (vmlist); +} + +static struct vm * +copy_vm (struct vm *vm) +{ + struct vm *vm2; + + vm2 = g_memdup (vm, sizeof (*vm)); + vm2->description = g_strdup (vm->description); + vm2->state = g_strdup (vm->state); + vm2->uuid = g_strdup (vm->uuid); + vm2->mac_addr = g_strdup (vm->mac_addr); + return vm2; +} + +static int +compare_vm (gconstpointer _vm1, gconstpointer _vm2) +{ + const struct vm *vm1 = (const struct vm *) _vm1; + const struct vm *vm2 = (const struct vm *) _vm2; + + return strcmp (vm1->description, vm2->description); +} + +static GSList *vmlist = NULL; +static gboolean vmlist_valid = FALSE; +static GStaticMutex vmlist_mutex; + +/* Called from the main thread to find out if we have a valid vmlist. */ +gboolean +wui_thread_has_valid_vmlist (void) +{ + gboolean ret; + + g_static_mutex_lock (&vmlist_mutex); + ret = vmlist_valid; + g_static_mutex_unlock (&vmlist_mutex); + return ret; +} + +/* Called from the main thread to find return the current vmlist. This + * actually returns a deep copy of it that the caller must free. + */ +static void +duplicate_and_insert_vm (gpointer _vm, gpointer _ret) +{ + struct vm *vm = (struct vm *) _vm; + GSList **ret = (GSList **) _ret; + + *ret = g_slist_prepend (*ret, copy_vm (vm)); +} + +gboolean +wui_thread_get_vmlist (GSList **ret) +{ + gboolean r; + + r = FALSE; + *ret = NULL; + + g_static_mutex_lock (&vmlist_mutex); + if (!vmlist_valid) goto done; + + g_slist_foreach (vmlist, duplicate_and_insert_vm, ret); + *ret = g_slist_sort (*ret, compare_vm); + + r = TRUE; + done: + g_static_mutex_unlock (&vmlist_mutex); + return r; +} + +/* Called from the message loop in the WUI thread, with an XML document + * which we turn into a list of VM structures, and update the vmlist + * if we can. + */ +static void +parse_vmlist_from_xml (const char *xml) +{ + + + + + + + } -- 1.8.3.1