1 /* ovirt viewer console application
2 * Copyright (C) 2008 Red Hat Inc.
3 * Written by Richard W.M. Jones <rjones@redhat.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include <glib/gprintf.h>
30 #include <curl/curl.h>
34 /* Private functions. */
35 static gpointer wui_thread (gpointer data);
36 static void wui_thread_send_quit (void);
37 static void do_curl_init (void);
38 static void write_fn_start_capture (void);
39 static char *write_fn_finish_capture (void);
40 static void write_fn_discard_capture_buffer (void);
41 static gboolean do_connect (void);
42 static gboolean do_login (void);
43 static gboolean refresh_vm_list (void);
44 static void parse_vmlist_from_xml (const char *xml);
46 /* Messages (main thread -> WUI thread only).
48 * These are passed by reference. They are allocated by the sender
49 * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
52 QUIT, /* Tell the WUI thread to quit cleanly. */
53 CONNECT, /* Tell to connect (just fetch the URI). */
54 DISCONNECT, /* Tell to disconnect, forget state. */
55 LOGIN, /* Tell to login, and pass credentials. */
56 REFRESH_VM_LIST, /* Tell to refresh the VM list right away. */
60 enum message_type type;
65 /* Start the WUI thread. See main() for explanation of the threading model. */
66 static GThread *wui_gthread = NULL;
67 static GAsyncQueue *wui_thread_queue = NULL;
70 start_wui_thread (void)
74 DEBUG ("starting the WUI thread");
76 /* Create the message queue for main -> WUI thread communications. */
77 wui_thread_queue = g_async_queue_new ();
79 wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
81 g_print ("%s\n", error->message);
88 stop_wui_thread (void)
90 DEBUG ("stopping the WUI thread");
93 /* Send a quit message then wait for the WUI thread to join.
95 * This "nice" shutdown could cause the UI to hang for an
96 * indefinite period (eg. if the WUI thread is engaged in some
97 * long connection or download from the remote server). But
98 * I want to keep it this way for the moment so that we can
99 * diagnose problems with the WUI thread.
101 * (This could be solved with some sort of interruptible
102 * join, but glib doesn't support that AFAICT).
104 wui_thread_send_quit ();
105 (void) g_thread_join (wui_gthread);
106 g_async_queue_unref (wui_thread_queue);
110 /* Send the quit message to the WUI thread. */
112 wui_thread_send_quit (void)
116 msg = g_new (struct message, 1);
118 g_async_queue_push (wui_thread_queue, msg);
121 /* Send the connect message to the WUI thread. */
123 wui_thread_send_connect (const char *uri)
127 msg = g_new (struct message, 1);
129 msg->str1 = g_strdup (uri);
130 g_async_queue_push (wui_thread_queue, msg);
133 /* Send the disconnect message to the WUI thread. */
135 wui_thread_send_disconnect (void)
139 msg = g_new (struct message, 1);
140 msg->type = DISCONNECT;
141 g_async_queue_push (wui_thread_queue, msg);
144 /* Send the login message to the WUI thread. */
146 wui_thread_send_login (const char *username, const char *password)
150 msg = g_new (struct message, 1);
152 msg->str1 = g_strdup (username);
153 msg->str2 = g_strdup (password);
154 g_async_queue_push (wui_thread_queue, msg);
157 /* Send the refresh VM list message to the WUI thread. */
159 wui_thread_send_refresh_vm_list (void)
163 msg = g_new (struct message, 1);
164 msg->type = REFRESH_VM_LIST;
165 g_async_queue_push (wui_thread_queue, msg);
168 /* The current state.
170 * For safety, the main thread must lock this before reading, and the
171 * WUI thread must lock this before writing. However the WUI thread
172 * does not need to lock before reading, because no other thread
175 static gboolean connected = FALSE;
176 static gboolean logged_in = FALSE;
177 static gboolean busy = FALSE;
178 static GStaticMutex state_mutex;
180 static void set_connected (gboolean);
181 static void set_logged_in (gboolean);
182 static void set_busy (gboolean);
184 /* The private state of the WUI thread. */
185 static int secs_between_refresh = 60;
186 static CURL *curl = NULL;
187 static char curl_error_buffer[CURL_ERROR_SIZE];
188 static char *uri = NULL;
189 static char *username = NULL;
190 static char *password = NULL;
192 static gboolean process_message (struct message *);
194 /* The WUI thread. See main() above for explanation of
195 * the threading model.
198 wui_thread (gpointer _queue)
200 GAsyncQueue *queue = (GAsyncQueue *) _queue;
201 gboolean quit = FALSE;
206 DEBUG ("WUI thread starting up");
208 g_async_queue_ref (queue);
210 /* In the thread's loop we check for new instructions from the main
211 * thread and carry them out. Also, if we are connected and logged
212 * in then we periodically recheck the list of VMs.
218 g_get_current_time (&tv);
219 g_time_val_add (&tv, secs_between_refresh * 1000000);
220 _msg = g_async_queue_timed_pop (queue, &tv);
222 _msg = g_async_queue_pop (queue);
226 msg = (struct message *) _msg;
229 DEBUG ("received message with msg->type = %d", msg->type);
230 quit = process_message (msg);
231 /* Don't free any strings in the message - we've saved them. */
234 /* No message, must have got a timeout instead, which means
235 * we are logged in and we should refresh the list of VMs.
236 * Note it's not an error if we temporarily lose contact
243 DEBUG ("WUI thread shutting down cleanly");
245 g_async_queue_unref (queue);
246 g_thread_exit (NULL);
247 return NULL; /* not reached? */
250 /* The WUI thread calls this to safely update the state variables.
251 * This also updates elements in the UI by setting idle callbacks
252 * which are executed in the context of the main thread.
255 set_connected (gboolean new_connected)
257 g_static_mutex_lock (&state_mutex);
258 connected = new_connected;
259 g_static_mutex_unlock (&state_mutex);
262 g_idle_add (main_connected, NULL);
264 g_idle_add (main_disconnected, NULL);
268 set_logged_in (gboolean new_logged_in)
270 g_static_mutex_lock (&state_mutex);
271 logged_in = new_logged_in;
272 g_static_mutex_unlock (&state_mutex);
275 g_idle_add (main_logged_in, NULL);
277 g_idle_add (main_logged_out, NULL);
281 set_busy (gboolean new_busy)
283 g_static_mutex_lock (&state_mutex);
285 g_static_mutex_unlock (&state_mutex);
288 g_idle_add (main_busy, NULL);
290 g_idle_add (main_idle, NULL);
293 /* The main thread should call these functions to get the WUI thread's
297 wui_thread_is_connected (void)
301 g_static_mutex_lock (&state_mutex);
303 g_static_mutex_unlock (&state_mutex);
308 wui_thread_is_logged_in (void)
312 g_static_mutex_lock (&state_mutex);
314 g_static_mutex_unlock (&state_mutex);
319 wui_thread_is_busy (void)
323 g_static_mutex_lock (&state_mutex);
325 g_static_mutex_unlock (&state_mutex);
329 /* Process a message from the main thread. */
331 process_message (struct message *msg)
335 write_fn_discard_capture_buffer ();
336 if (curl) curl_easy_cleanup (curl);
337 if (uri) g_free (uri);
338 if (username) g_free (username);
339 if (password) g_free (password);
340 set_connected (FALSE);
341 set_logged_in (FALSE);
345 write_fn_discard_capture_buffer ();
346 if (curl) curl_easy_cleanup (curl);
348 if (uri) g_free (uri);
352 set_connected (TRUE);
354 set_connected (FALSE);
355 set_logged_in (FALSE);
360 /* This just forgets the state. REST is connectionless. */
361 write_fn_discard_capture_buffer ();
362 if (curl) curl_easy_cleanup (curl);
364 if (uri) g_free (uri);
366 if (username) g_free (username);
368 if (password) g_free (password);
370 set_connected (FALSE);
371 set_logged_in (FALSE);
375 if (username) g_free (username);
376 username = msg->str1;
377 if (password) g_free (password);
378 password = msg->str2;
380 /* If we're not connected, this message just updates the
381 * username and password. Otherwise if we are connected,
382 * try to login and grab the initial list of VMs.
386 set_logged_in (TRUE);
387 if (refresh_vm_list ())
388 secs_between_refresh = 60;
390 set_logged_in (FALSE);
395 case REFRESH_VM_LIST:
396 if (connected && logged_in) {
398 secs_between_refresh = 60;
403 DEBUG ("unknown message type %d", msg->type);
410 /* Macro for easy handling of CURL errors. */
411 #define CURL_CHECK_ERROR(fn, args) \
413 CURLcode __r = fn args; \
414 if (__r != CURLE_OK) { \
415 fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r)); \
420 /* Libcurl has a really crufty method for handling HTTP headers and
421 * data. We set these functions as callbacks, because the default
422 * callback functions print the data out to stderr. In order to
423 * capture the data, we have to keep state here.
426 static char *write_fn_buffer = NULL;
427 static ssize_t write_fn_len = -1;
430 write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
432 int bytes = size * nmemb;
435 if (write_fn_len >= 0) { /* We're capturing. */
436 old_start = write_fn_len;
437 write_fn_len += bytes;
438 write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len);
439 memcpy (write_fn_buffer + old_start, ptr, bytes);
445 /* Start capturing HTTP response data. */
447 write_fn_start_capture (void)
449 write_fn_discard_capture_buffer ();
450 write_fn_buffer = NULL;
454 /* Finish capture and return the capture buffer. Caller must free. */
456 write_fn_finish_capture (void)
458 char *ret = write_fn_buffer;
460 write_fn_buffer = NULL;
465 /* Stop capturing and discard the capture buffer, if any. */
467 write_fn_discard_capture_buffer (void)
469 g_free (write_fn_buffer);
470 write_fn_buffer = NULL;
475 header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
477 int bytes = size * nmemb;
481 /* Called from the message loop to initialize the CURL handle. */
485 DEBUG ("initializing libcurl");
487 curl = curl_easy_init ();
488 if (!curl) { /* This is probably quite bad, so abort. */
489 DEBUG ("curl_easy_init failed");
493 CURL_CHECK_ERROR (curl_easy_setopt,
494 (curl, CURLOPT_CAINFO, cainfo));
495 CURL_CHECK_ERROR (curl_easy_setopt,
496 (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
497 CURL_CHECK_ERROR (curl_easy_setopt,
498 (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
500 CURL_CHECK_ERROR (curl_easy_setopt,
501 (curl, CURLOPT_WRITEFUNCTION, write_fn));
502 CURL_CHECK_ERROR (curl_easy_setopt,
503 (curl, CURLOPT_HEADERFUNCTION, header_fn));
505 /* This enables error messages in curl_easy_perform. */
506 CURL_CHECK_ERROR (curl_easy_setopt,
507 (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
509 /* This enables cookie handling, using an internal cookiejar. */
510 CURL_CHECK_ERROR (curl_easy_setopt,
511 (curl, CURLOPT_COOKIEFILE, ""));
514 /* Called from the message loop. Try to connect to the current URI.
515 * Returns true on success.
524 DEBUG ("connecting to uri %s", uri);
526 /* Set the URI for libcurl. */
527 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
529 /* Try to fetch the URI. */
530 r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
532 /* Signal an error back to the main thread. */
533 error_str = g_strdup (curl_easy_strerror (r));
534 g_idle_add (main_connection_error, error_str);
538 CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
539 DEBUG ("HTTP return code is %ld", code);
540 if (code != 200 && code != 302 && code != 401) {
541 /* XXX If only glib had g_asprintf. */
542 error_str = g_strdup ("unexpected HTTP return code from server");
543 g_idle_add (main_connection_error, error_str);
550 /* Called from the message loop. Try to login to 'URI/login' with the
551 * current username and password. Returns true on success.
563 DEBUG ("logging in with username %s, password *****", username);
565 /* Generate the login URI from the base URI. */
566 len = strlen (uri) + 6 + 1;
567 login_uri = g_alloca (len);
568 snprintf (login_uri, len, "%s/login", uri);
570 DEBUG ("login URI %s", login_uri);
572 /* Set the URI for libcurl. */
573 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri));
575 /* Construct the username:password for CURL. */
576 len = strlen (username) + strlen (password) + 2;
577 userpwd = g_alloca (len);
578 snprintf (userpwd, len, "%s:%s", username, password);
580 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd));
582 /* HTTP Basic authentication is OK since we should be sending
583 * this only over HTTPS.
585 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC));
587 /* Follow redirects. */
588 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1));
589 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10));
591 /* Try to fetch the URI. */
592 r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
594 /* Signal an error back to the main thread. */
595 error_str = g_strdup (curl_easy_strerror (r));
596 g_idle_add (main_login_error, error_str);
600 CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
601 DEBUG ("HTTP return code is %ld", code);
605 DEBUG ("login was successful");
609 error_str = g_strdup ("server rejected the username or password");
610 g_idle_add (main_login_error, error_str);
614 /* XXX If only glib had g_asprintf. */
615 error_str = g_strdup ("unexpected HTTP return code from server");
616 g_idle_add (main_login_error, error_str);
621 /* Called from the message loop. Refresh the list of VMs. */
623 refresh_vm_list (void)
632 DEBUG ("refreshing list of VMs");
634 /* Generate the vms URI from the base URI. */
635 len = strlen (uri) + 4 + 1;
636 vms_uri = g_alloca (len);
637 snprintf (vms_uri, len, "%s/vms", uri);
639 DEBUG ("vms URI %s", vms_uri);
641 /* Set the URI for libcurl. */
642 CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
644 /* We want to capture the output, so tell our write function
645 * to place the output into a buffer.
647 write_fn_start_capture ();
649 /* Try to fetch the URI. */
650 r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
652 /* Signal an error back to the main thread. */
653 error_str = g_strdup (curl_easy_strerror (r));
654 g_idle_add (main_login_error, error_str);
658 CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
659 DEBUG ("HTTP return code is %ld", code);
664 /* Hmm - even though we previously logged in, the server is
665 * rejecting our attempts now with an authorization error.
666 * We move to the logged out state.
669 set_logged_in (FALSE);
670 error_str = g_strdup ("server rejected the username or password");
671 g_idle_add (main_login_error, error_str);
675 /* XXX If only glib had g_asprintf. */
676 error_str = g_strdup ("unexpected HTTP return code from server");
677 g_idle_add (main_status_error, error_str);
681 /* If we got here then we appear to have a correct
682 * XML document describing the list of VMs.
684 secs_between_refresh <<= 1;
686 xml = write_fn_finish_capture ();
688 /*DEBUG ("XML from /vms =\n%s", xml);*/
689 parse_vmlist_from_xml (xml);
695 /* Functions to deal with the list of VMs, parsing it from the XML, etc.
697 * A vmlist is a GSList (glib singly-linked list) of vm structures.
698 * The caller must call free_vmlist to free up this list correctly.
702 free_vm (gpointer _vm, gpointer data)
704 struct vm *vm = (struct vm *) _vm;
706 g_free (vm->description);
709 g_free (vm->mac_addr);
714 free_vmlist (GSList *vmlist)
716 g_slist_foreach (vmlist, free_vm, NULL);
717 g_slist_free (vmlist);
721 copy_vm (struct vm *vm)
725 vm2 = g_memdup (vm, sizeof (*vm));
726 vm2->description = g_strdup (vm->description);
727 vm2->state = g_strdup (vm->state);
728 vm2->uuid = g_strdup (vm->uuid);
729 vm2->mac_addr = g_strdup (vm->mac_addr);
734 compare_vm (gconstpointer _vm1, gconstpointer _vm2)
736 const struct vm *vm1 = (const struct vm *) _vm1;
737 const struct vm *vm2 = (const struct vm *) _vm2;
739 return strcmp (vm1->description, vm2->description);
742 static GSList *vmlist = NULL;
743 static gboolean vmlist_valid = FALSE;
744 static GStaticMutex vmlist_mutex;
746 /* Called from the main thread to find out if we have a valid vmlist. */
748 wui_thread_has_valid_vmlist (void)
752 g_static_mutex_lock (&vmlist_mutex);
754 g_static_mutex_unlock (&vmlist_mutex);
758 /* Called from the main thread to find return the current vmlist. This
759 * actually returns a deep copy of it that the caller must free.
762 duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
764 struct vm *vm = (struct vm *) _vm;
765 GSList **ret = (GSList **) _ret;
767 *ret = g_slist_prepend (*ret, copy_vm (vm));
771 wui_thread_get_vmlist (GSList **ret)
778 g_static_mutex_lock (&vmlist_mutex);
779 if (!vmlist_valid) goto done;
781 g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
782 *ret = g_slist_sort (*ret, compare_vm);
786 g_static_mutex_unlock (&vmlist_mutex);
790 /* Called from the message loop in the WUI thread, with an XML document
791 * which we turn into a list of VM structures, and update the vmlist
795 parse_vmlist_from_xml (const char *xml)