X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=wui_thread.c;h=94631b6c35950df3a734f3df3a6f41b37fa81440;hb=13a89a846b79145209ac68fcc6295156699e55ea;hp=0db43c8829a41ba06a239ad0eddfe6917597be81;hpb=7e9794277b619bb232ffc874ee7efa4ead8ddb8e;p=ovirt-viewer.git diff --git a/wui_thread.c b/wui_thread.c index 0db43c8..94631b6 100644 --- a/wui_thread.c +++ b/wui_thread.c @@ -21,18 +21,27 @@ #include #include +#include #include #include +#include + +#include #include "internal.h" /* Private functions. */ 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). * @@ -55,6 +64,7 @@ struct message { /* Start the WUI thread. See main() for explanation of the threading model. */ static GThread *wui_gthread = NULL; +static GThread *main_gthread = NULL; static GAsyncQueue *wui_thread_queue = NULL; void @@ -64,6 +74,10 @@ start_wui_thread (void) DEBUG ("starting the WUI thread"); + assert (wui_gthread == NULL); + + main_gthread = g_thread_self (); + /* Create the message queue for main -> WUI thread communications. */ wui_thread_queue = g_async_queue_new (); @@ -79,7 +93,9 @@ void stop_wui_thread (void) { DEBUG ("stopping the WUI thread"); - assert (wui_gthread); + + assert (wui_gthread != NULL); + ASSERT_IS_MAIN_THREAD (); /* Send a quit message then wait for the WUI thread to join. * @@ -98,12 +114,32 @@ stop_wui_thread (void) wui_gthread = NULL; } +void +assert_is_wui_thread (const char *filename, int lineno) +{ + if (g_thread_self () != wui_gthread) { + fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno); + abort (); + } +} + +void +assert_is_main_thread (const char *filename, int lineno) +{ + if (g_thread_self () != main_gthread) { + fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno); + abort (); + } +} + /* Send the quit message to the WUI thread. */ static void wui_thread_send_quit (void) { struct message *msg; + ASSERT_IS_MAIN_THREAD (); + msg = g_new (struct message, 1); msg->type = QUIT; g_async_queue_push (wui_thread_queue, msg); @@ -115,6 +151,8 @@ wui_thread_send_connect (const char *uri) { struct message *msg; + ASSERT_IS_MAIN_THREAD (); + msg = g_new (struct message, 1); msg->type = CONNECT; msg->str1 = g_strdup (uri); @@ -127,6 +165,8 @@ wui_thread_send_disconnect (void) { struct message *msg; + ASSERT_IS_MAIN_THREAD (); + msg = g_new (struct message, 1); msg->type = DISCONNECT; g_async_queue_push (wui_thread_queue, msg); @@ -138,6 +178,8 @@ wui_thread_send_login (const char *username, const char *password) { struct message *msg; + ASSERT_IS_MAIN_THREAD (); + msg = g_new (struct message, 1); msg->type = LOGIN; msg->str1 = g_strdup (username); @@ -151,6 +193,8 @@ wui_thread_send_refresh_vm_list (void) { struct message *msg; + ASSERT_IS_MAIN_THREAD (); + msg = g_new (struct message, 1); msg->type = REFRESH_VM_LIST; g_async_queue_push (wui_thread_queue, msg); @@ -174,6 +218,8 @@ static void set_busy (gboolean); /* The private state of the WUI thread. */ static int secs_between_refresh = 60; +static CURL *curl = NULL; +static char curl_error_buffer[CURL_ERROR_SIZE]; static char *uri = NULL; static char *username = NULL; static char *password = NULL; @@ -194,6 +240,8 @@ wui_thread (gpointer _queue) DEBUG ("WUI thread starting up"); + ASSERT_IS_WUI_THREAD (); + g_async_queue_ref (queue); /* In the thread's loop we check for new instructions from the main @@ -215,7 +263,7 @@ wui_thread (gpointer _queue) msg = (struct message *) _msg; if (msg) { - DEBUG ("received message %d", msg->type); + DEBUG ("received message with msg->type = %d", msg->type); quit = process_message (msg); /* Don't free any strings in the message - we've saved them. */ g_free (msg); @@ -243,25 +291,46 @@ wui_thread (gpointer _queue) static void set_connected (gboolean new_connected) { + ASSERT_IS_WUI_THREAD (); + 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 set_logged_in (gboolean new_logged_in) { + ASSERT_IS_WUI_THREAD (); + 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 set_busy (gboolean new_busy) { + ASSERT_IS_WUI_THREAD (); + 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 @@ -272,6 +341,8 @@ wui_thread_is_connected (void) { gboolean ret; + ASSERT_IS_MAIN_THREAD (); + g_static_mutex_lock (&state_mutex); ret = connected; g_static_mutex_unlock (&state_mutex); @@ -283,6 +354,8 @@ wui_thread_is_logged_in (void) { gboolean ret; + ASSERT_IS_MAIN_THREAD (); + g_static_mutex_lock (&state_mutex); ret = logged_in; g_static_mutex_unlock (&state_mutex); @@ -294,6 +367,8 @@ wui_thread_is_busy (void) { gboolean ret; + ASSERT_IS_MAIN_THREAD (); + g_static_mutex_lock (&state_mutex); ret = busy; g_static_mutex_unlock (&state_mutex); @@ -304,8 +379,12 @@ wui_thread_is_busy (void) static gboolean process_message (struct message *msg) { + ASSERT_IS_WUI_THREAD (); + 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); if (password) g_free (password); @@ -314,6 +393,9 @@ 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); uri = msg->str1; @@ -327,9 +409,15 @@ 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); + uri = NULL; if (username) g_free (username); + username = NULL; if (password) g_free (password); + password = NULL; set_connected (FALSE); set_logged_in (FALSE); break; @@ -340,10 +428,15 @@ process_message (struct message *msg) if (password) g_free (password); password = msg->str2; + /* If we're not connected, this message just updates the + * username and password. Otherwise if we are connected, + * try to login and grab the initial list of VMs. + */ 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); } @@ -351,8 +444,10 @@ process_message (struct message *msg) break; case REFRESH_VM_LIST: - refresh_vm_list (); - secs_between_refresh = 60; + if (connected && logged_in) { + refresh_vm_list (); + secs_between_refresh = 60; + } break; default: @@ -363,27 +458,418 @@ process_message (struct message *msg) return 0; } -/* Try to connect to the current URI. Returns true on success. */ +/* Macro for easy handling of CURL errors. */ +#define CURL_CHECK_ERROR(fn, args) \ + ({ \ + CURLcode __r = fn args; \ + if (__r != CURLE_OK) { \ + fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r)); \ + } \ + __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; + + ASSERT_IS_WUI_THREAD (); + + 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) +{ + ASSERT_IS_WUI_THREAD (); + + 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; + + ASSERT_IS_WUI_THREAD (); + + 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) +{ + ASSERT_IS_WUI_THREAD (); + + 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) +{ + int bytes = size * nmemb; + + ASSERT_IS_WUI_THREAD (); + + return bytes; +} + +/* Called from the message loop to initialize the CURL handle. */ +static void +do_curl_init (void) +{ + DEBUG ("initializing libcurl"); + + ASSERT_IS_WUI_THREAD (); + + curl = curl_easy_init (); + if (!curl) { /* This is probably quite bad, so abort. */ + DEBUG ("curl_easy_init failed"); + abort (); + } + + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_CAINFO, cainfo)); + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0)); + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0)); + + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_WRITEFUNCTION, write_fn)); + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_HEADERFUNCTION, header_fn)); + + /* This enables error messages in curl_easy_perform. */ + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_ERRORBUFFER, curl_error_buffer)); + + /* This enables cookie handling, using an internal cookiejar. */ + CURL_CHECK_ERROR (curl_easy_setopt, + (curl, CURLOPT_COOKIEFILE, "")); +} + +/* Called from the message loop. Try to connect to the current URI. + * Returns true on success. + */ static gboolean do_connect (void) { + long code = 0; + CURLcode r; + char *error_str; + DEBUG ("connecting to uri %s", uri); - return FALSE; + ASSERT_IS_WUI_THREAD (); + + /* Set the URI for libcurl. */ + CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri)); + + /* 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_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) { + /* 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; } -/* Try to login to URI/login with the username and password given. - * Returns true on success. +/* Called from the message loop. Try to login to 'URI/login' with the + * current username and password. Returns true on success. */ 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; + ASSERT_IS_WUI_THREAD (); + + /* 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; + } } -/* Refresh the list of VMs. */ -static void +/* Called from the message loop. Refresh the list of VMs. */ +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"); + ASSERT_IS_WUI_THREAD (); + + /* 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; + + ASSERT_IS_MAIN_THREAD (); + + 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; + + ASSERT_IS_MAIN_THREAD (); + + 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) +{ + + + + + + + }