94631b6c35950df3a734f3df3a6f41b37fa81440
[ovirt-viewer.git] / wui_thread.c
1 /* ovirt viewer console application
2  * Copyright (C) 2008 Red Hat Inc.
3  * Written by Richard W.M. Jones <rjones@redhat.com>
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <assert.h>
26
27 #include <glib.h>
28 #include <glib/gprintf.h>
29
30 #include <curl/curl.h>
31
32 #include "internal.h"
33
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);
45
46 /* Messages (main thread -> WUI thread only).
47  *
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).
50  */
51 enum message_type {
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. */
57 };
58
59 struct message {
60   enum message_type type;
61   char *str1;
62   char *str2;
63 };
64
65 /* Start the WUI thread.  See main() for explanation of the threading model. */
66 static GThread *wui_gthread = NULL;
67 static GThread *main_gthread = NULL;
68 static GAsyncQueue *wui_thread_queue = NULL;
69
70 void
71 start_wui_thread (void)
72 {
73   GError *error = NULL;
74
75   DEBUG ("starting the WUI thread");
76
77   assert (wui_gthread == NULL);
78
79   main_gthread = g_thread_self ();
80
81   /* Create the message queue for main -> WUI thread communications. */
82   wui_thread_queue = g_async_queue_new ();
83
84   wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
85   if (error) {
86     g_print ("%s\n", error->message);
87     g_error_free (error);
88     exit (1);
89   }
90 }
91
92 void
93 stop_wui_thread (void)
94 {
95   DEBUG ("stopping the WUI thread");
96
97   assert (wui_gthread != NULL);
98   ASSERT_IS_MAIN_THREAD ();
99
100   /* Send a quit message then wait for the WUI thread to join.
101    *
102    * This "nice" shutdown could cause the UI to hang for an
103    * indefinite period (eg. if the WUI thread is engaged in some
104    * long connection or download from the remote server).  But
105    * I want to keep it this way for the moment so that we can
106    * diagnose problems with the WUI thread.
107    *
108    * (This could be solved with some sort of interruptible
109    * join, but glib doesn't support that AFAICT).
110    */
111   wui_thread_send_quit ();
112   (void) g_thread_join (wui_gthread);
113   g_async_queue_unref (wui_thread_queue);
114   wui_gthread = NULL;
115 }
116
117 void
118 assert_is_wui_thread (const char *filename, int lineno)
119 {
120   if (g_thread_self () != wui_gthread) {
121     fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno);
122     abort ();
123   }
124 }
125
126 void
127 assert_is_main_thread (const char *filename, int lineno)
128 {
129   if (g_thread_self () != main_gthread) {
130     fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno);
131     abort ();
132   }
133 }
134
135 /* Send the quit message to the WUI thread. */
136 static void
137 wui_thread_send_quit (void)
138 {
139   struct message *msg;
140
141   ASSERT_IS_MAIN_THREAD ();
142
143   msg = g_new (struct message, 1);
144   msg->type = QUIT;
145   g_async_queue_push (wui_thread_queue, msg);
146 }
147
148 /* Send the connect message to the WUI thread. */
149 void
150 wui_thread_send_connect (const char *uri)
151 {
152   struct message *msg;
153
154   ASSERT_IS_MAIN_THREAD ();
155
156   msg = g_new (struct message, 1);
157   msg->type = CONNECT;
158   msg->str1 = g_strdup (uri);
159   g_async_queue_push (wui_thread_queue, msg);
160 }
161
162 /* Send the disconnect message to the WUI thread. */
163 void
164 wui_thread_send_disconnect (void)
165 {
166   struct message *msg;
167
168   ASSERT_IS_MAIN_THREAD ();
169
170   msg = g_new (struct message, 1);
171   msg->type = DISCONNECT;
172   g_async_queue_push (wui_thread_queue, msg);
173 }
174
175 /* Send the login message to the WUI thread. */
176 void
177 wui_thread_send_login (const char *username, const char *password)
178 {
179   struct message *msg;
180
181   ASSERT_IS_MAIN_THREAD ();
182
183   msg = g_new (struct message, 1);
184   msg->type = LOGIN;
185   msg->str1 = g_strdup (username);
186   msg->str2 = g_strdup (password);
187   g_async_queue_push (wui_thread_queue, msg);
188 }
189
190 /* Send the refresh VM list message to the WUI thread. */
191 void
192 wui_thread_send_refresh_vm_list (void)
193 {
194   struct message *msg;
195
196   ASSERT_IS_MAIN_THREAD ();
197
198   msg = g_new (struct message, 1);
199   msg->type = REFRESH_VM_LIST;
200   g_async_queue_push (wui_thread_queue, msg);
201 }
202
203 /* The current state.
204  *
205  * For safety, the main thread must lock this before reading, and the
206  * WUI thread must lock this before writing.  However the WUI thread
207  * does not need to lock before reading, because no other thread
208  * can modify it.
209  */
210 static gboolean connected = FALSE;
211 static gboolean logged_in = FALSE;
212 static gboolean busy = FALSE;
213 static GStaticMutex state_mutex;
214
215 static void set_connected (gboolean);
216 static void set_logged_in (gboolean);
217 static void set_busy (gboolean);
218
219 /* The private state of the WUI thread. */
220 static int secs_between_refresh = 60;
221 static CURL *curl = NULL;
222 static char curl_error_buffer[CURL_ERROR_SIZE];
223 static char *uri = NULL;
224 static char *username = NULL;
225 static char *password = NULL;
226
227 static gboolean process_message (struct message *);
228
229 /* The WUI thread.  See main() above for explanation of
230  * the threading model.
231  */
232 static gpointer
233 wui_thread (gpointer _queue)
234 {
235   GAsyncQueue *queue = (GAsyncQueue *) _queue;
236   gboolean quit = FALSE;
237   GTimeVal tv;
238   gpointer _msg;
239   struct message *msg;
240
241   DEBUG ("WUI thread starting up");
242
243   ASSERT_IS_WUI_THREAD ();
244
245   g_async_queue_ref (queue);
246
247   /* In the thread's loop we check for new instructions from the main
248    * thread and carry them out.  Also, if we are connected and logged
249    * in then we periodically recheck the list of VMs.
250    */
251   while (!quit) {
252     set_busy (FALSE);
253
254     if (logged_in) {
255       g_get_current_time (&tv);
256       g_time_val_add (&tv, secs_between_refresh * 1000000);
257       _msg = g_async_queue_timed_pop (queue, &tv);
258     } else
259       _msg = g_async_queue_pop (queue);
260
261     set_busy (TRUE);
262
263     msg = (struct message *) _msg;
264
265     if (msg) {
266       DEBUG ("received message with msg->type = %d", msg->type);
267       quit = process_message (msg);
268       /* Don't free any strings in the message - we've saved them. */
269       g_free (msg);
270     } else {
271       /* No message, must have got a timeout instead, which means
272        * we are logged in and we should refresh the list of VMs.
273        * Note it's not an error if we temporarily lose contact
274        * with the WUI.
275        */
276       refresh_vm_list ();
277     }
278   }
279
280   DEBUG ("WUI thread shutting down cleanly");
281
282   g_async_queue_unref (queue);
283   g_thread_exit (NULL);
284   return NULL; /* not reached? */
285 }
286
287 /* The WUI thread calls this to safely update the state variables.
288  * This also updates elements in the UI by setting idle callbacks
289  * which are executed in the context of the main thread.
290  */
291 static void
292 set_connected (gboolean new_connected)
293 {
294   ASSERT_IS_WUI_THREAD ();
295
296   g_static_mutex_lock (&state_mutex);
297   connected = new_connected;
298   g_static_mutex_unlock (&state_mutex);
299
300   if (connected)
301     g_idle_add (main_connected, NULL);
302   else
303     g_idle_add (main_disconnected, NULL);
304 }
305
306 static void
307 set_logged_in (gboolean new_logged_in)
308 {
309   ASSERT_IS_WUI_THREAD ();
310
311   g_static_mutex_lock (&state_mutex);
312   logged_in = new_logged_in;
313   g_static_mutex_unlock (&state_mutex);
314
315   if (logged_in)
316     g_idle_add (main_logged_in, NULL);
317   else
318     g_idle_add (main_logged_out, NULL);
319 }
320
321 static void
322 set_busy (gboolean new_busy)
323 {
324   ASSERT_IS_WUI_THREAD ();
325
326   g_static_mutex_lock (&state_mutex);
327   busy = new_busy;
328   g_static_mutex_unlock (&state_mutex);
329
330   if (busy)
331     g_idle_add (main_busy, NULL);
332   else
333     g_idle_add (main_idle, NULL);
334 }
335
336 /* The main thread should call these functions to get the WUI thread's
337  * current state.
338  */
339 gboolean
340 wui_thread_is_connected (void)
341 {
342   gboolean ret;
343
344   ASSERT_IS_MAIN_THREAD ();
345
346   g_static_mutex_lock (&state_mutex);
347   ret = connected;
348   g_static_mutex_unlock (&state_mutex);
349   return ret;
350 }
351
352 gboolean
353 wui_thread_is_logged_in (void)
354 {
355   gboolean ret;
356
357   ASSERT_IS_MAIN_THREAD ();
358
359   g_static_mutex_lock (&state_mutex);
360   ret = logged_in;
361   g_static_mutex_unlock (&state_mutex);
362   return ret;
363 }
364
365 gboolean
366 wui_thread_is_busy (void)
367 {
368   gboolean ret;
369
370   ASSERT_IS_MAIN_THREAD ();
371
372   g_static_mutex_lock (&state_mutex);
373   ret = busy;
374   g_static_mutex_unlock (&state_mutex);
375   return ret;
376 }
377
378 /* Process a message from the main thread. */
379 static gboolean
380 process_message (struct message *msg)
381 {
382   ASSERT_IS_WUI_THREAD ();
383
384   switch (msg->type) {
385   case QUIT:
386     write_fn_discard_capture_buffer ();
387     if (curl) curl_easy_cleanup (curl);
388     if (uri) g_free (uri);
389     if (username) g_free (username);
390     if (password) g_free (password);
391     set_connected (FALSE);
392     set_logged_in (FALSE);
393     return 1;
394
395   case CONNECT:
396     write_fn_discard_capture_buffer ();
397     if (curl) curl_easy_cleanup (curl);
398     do_curl_init ();
399     if (uri) g_free (uri);
400     uri = msg->str1;
401
402     if (do_connect ()) {
403       set_connected (TRUE);
404     } else {
405       set_connected (FALSE);
406       set_logged_in (FALSE);
407     }
408     break;
409
410   case DISCONNECT:
411     /* This just forgets the state. REST is connectionless. */
412     write_fn_discard_capture_buffer ();
413     if (curl) curl_easy_cleanup (curl);
414     curl = NULL;
415     if (uri) g_free (uri);
416     uri = NULL;
417     if (username) g_free (username);
418     username = NULL;
419     if (password) g_free (password);
420     password = NULL;
421     set_connected (FALSE);
422     set_logged_in (FALSE);
423     break;
424
425   case LOGIN:
426     if (username) g_free (username);
427     username = msg->str1;
428     if (password) g_free (password);
429     password = msg->str2;
430
431     /* If we're not connected, this message just updates the
432      * username and password.  Otherwise if we are connected,
433      * try to login and grab the initial list of VMs.
434      */
435     if (connected) {
436       if (do_login ()) {
437         set_logged_in (TRUE);
438         if (refresh_vm_list ())
439           secs_between_refresh = 60;
440       } else {
441         set_logged_in (FALSE);
442       }
443     }
444     break;
445
446   case REFRESH_VM_LIST:
447     if (connected && logged_in) {
448       refresh_vm_list ();
449       secs_between_refresh = 60;
450     }
451     break;
452
453   default:
454     DEBUG ("unknown message type %d", msg->type);
455     abort ();
456   }
457
458   return 0;
459 }
460
461 /* Macro for easy handling of CURL errors. */
462 #define CURL_CHECK_ERROR(fn, args)                                      \
463   ({                                                                    \
464     CURLcode __r = fn args;                                             \
465     if (__r != CURLE_OK) {                                              \
466       fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r));      \
467     }                                                                   \
468     __r;                                                                \
469   })
470
471 /* Libcurl has a really crufty method for handling HTTP headers and
472  * data.  We set these functions as callbacks, because the default
473  * callback functions print the data out to stderr.  In order to
474  * capture the data, we have to keep state here.
475  */
476
477 static char *write_fn_buffer = NULL;
478 static ssize_t write_fn_len = -1;
479
480 static size_t
481 write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
482 {
483   int bytes = size * nmemb;
484   int old_start;
485
486   ASSERT_IS_WUI_THREAD ();
487
488   if (write_fn_len >= 0) {      /* We're capturing. */
489     old_start = write_fn_len;
490     write_fn_len += bytes;
491     write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len);
492     memcpy (write_fn_buffer + old_start, ptr, bytes);
493   }
494
495   return bytes;
496 }
497
498 /* Start capturing HTTP response data. */
499 static void
500 write_fn_start_capture (void)
501 {
502   ASSERT_IS_WUI_THREAD ();
503
504   write_fn_discard_capture_buffer ();
505   write_fn_buffer = NULL;
506   write_fn_len = 0;
507 }
508
509 /* Finish capture and return the capture buffer.  Caller must free. */
510 static char *
511 write_fn_finish_capture (void)
512 {
513   char *ret = write_fn_buffer;
514
515   ASSERT_IS_WUI_THREAD ();
516
517   write_fn_buffer = NULL;
518   write_fn_len = -1;
519   return ret;
520 }
521
522 /* Stop capturing and discard the capture buffer, if any. */
523 static void
524 write_fn_discard_capture_buffer (void)
525 {
526   ASSERT_IS_WUI_THREAD ();
527
528   g_free (write_fn_buffer);
529   write_fn_buffer = NULL;
530   write_fn_len = -1;
531 }
532
533 static size_t
534 header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
535 {
536   int bytes = size * nmemb;
537
538   ASSERT_IS_WUI_THREAD ();
539
540   return bytes;
541 }
542
543 /* Called from the message loop to initialize the CURL handle. */
544 static void
545 do_curl_init (void)
546 {
547   DEBUG ("initializing libcurl");
548
549   ASSERT_IS_WUI_THREAD ();
550
551   curl = curl_easy_init ();
552   if (!curl) {             /* This is probably quite bad, so abort. */
553     DEBUG ("curl_easy_init failed");
554     abort ();
555   }
556
557   CURL_CHECK_ERROR (curl_easy_setopt,
558                     (curl, CURLOPT_CAINFO, cainfo));
559   CURL_CHECK_ERROR (curl_easy_setopt,
560                     (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
561   CURL_CHECK_ERROR (curl_easy_setopt,
562                     (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
563
564   CURL_CHECK_ERROR (curl_easy_setopt,
565                     (curl, CURLOPT_WRITEFUNCTION, write_fn));
566   CURL_CHECK_ERROR (curl_easy_setopt,
567                     (curl, CURLOPT_HEADERFUNCTION, header_fn));
568
569   /* This enables error messages in curl_easy_perform. */
570   CURL_CHECK_ERROR (curl_easy_setopt,
571                     (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
572
573   /* This enables cookie handling, using an internal cookiejar. */
574   CURL_CHECK_ERROR (curl_easy_setopt,
575                     (curl, CURLOPT_COOKIEFILE, ""));
576 }
577
578 /* Called from the message loop.  Try to connect to the current URI.
579  * Returns true on success.
580  */
581 static gboolean
582 do_connect (void)
583 {
584   long code = 0;
585   CURLcode r;
586   char *error_str;
587
588   DEBUG ("connecting to uri %s", uri);
589   ASSERT_IS_WUI_THREAD ();
590
591   /* Set the URI for libcurl. */
592   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
593
594   /* Try to fetch the URI. */
595   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
596   if (r != CURLE_OK) {
597     /* Signal an error back to the main thread. */
598     error_str = g_strdup (curl_easy_strerror (r));
599     g_idle_add (main_connection_error, error_str);
600     return FALSE;
601   }
602
603   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
604   DEBUG ("HTTP return code is %ld", code);
605   if (code != 200 && code != 302 && code != 401) {
606     /* XXX If only glib had g_asprintf. */
607     error_str = g_strdup ("unexpected HTTP return code from server");
608     g_idle_add (main_connection_error, error_str);
609     return FALSE;
610   }
611
612   return TRUE;
613 }
614
615 /* Called from the message loop.  Try to login to 'URI/login' with the
616  * current username and password.  Returns true on success.
617  */
618 static gboolean
619 do_login (void)
620 {
621   int len;
622   char *login_uri;
623   char *userpwd;
624   char *error_str;
625   CURLcode r;
626   long code = 0;
627
628   DEBUG ("logging in with username %s, password *****", username);
629   ASSERT_IS_WUI_THREAD ();
630
631   /* Generate the login URI from the base URI. */
632   len = strlen (uri) + 6 + 1;
633   login_uri = g_alloca (len);
634   snprintf (login_uri, len, "%s/login", uri);
635
636   DEBUG ("login URI %s", login_uri);
637
638   /* Set the URI for libcurl. */
639   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri));
640
641   /* Construct the username:password for CURL. */
642   len = strlen (username) + strlen (password) + 2;
643   userpwd = g_alloca (len);
644   snprintf (userpwd, len, "%s:%s", username, password);
645
646   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd));
647
648   /* HTTP Basic authentication is OK since we should be sending
649    * this only over HTTPS.
650    */
651   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC));
652
653   /* Follow redirects. */
654   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1));
655   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10));
656
657   /* Try to fetch the URI. */
658   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
659   if (r != CURLE_OK) {
660     /* Signal an error back to the main thread. */
661     error_str = g_strdup (curl_easy_strerror (r));
662     g_idle_add (main_login_error, error_str);
663     return FALSE;
664   }
665
666   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
667   DEBUG ("HTTP return code is %ld", code);
668   switch (code)
669     {
670     case 200:
671       DEBUG ("login was successful");
672       return TRUE;
673
674     case 401:
675       error_str = g_strdup ("server rejected the username or password");
676       g_idle_add (main_login_error, error_str);
677       return FALSE;
678
679     default:
680       /* XXX If only glib had g_asprintf. */
681       error_str = g_strdup ("unexpected HTTP return code from server");
682       g_idle_add (main_login_error, error_str);
683       return FALSE;
684     }
685 }
686
687 /* Called from the message loop.  Refresh the list of VMs. */
688 static gboolean
689 refresh_vm_list (void)
690 {
691   int len;
692   char *vms_uri;
693   char *error_str;
694   CURLcode r;
695   long code = 0;
696   char *xml;
697
698   DEBUG ("refreshing list of VMs");
699   ASSERT_IS_WUI_THREAD ();
700
701   /* Generate the vms URI from the base URI. */
702   len = strlen (uri) + 4 + 1;
703   vms_uri = g_alloca (len);
704   snprintf (vms_uri, len, "%s/vms", uri);
705
706   DEBUG ("vms URI %s", vms_uri);
707
708   /* Set the URI for libcurl. */
709   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
710
711   /* We want to capture the output, so tell our write function
712    * to place the output into a buffer.
713    */
714   write_fn_start_capture ();
715
716   /* Try to fetch the URI. */
717   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
718   if (r != CURLE_OK) {
719     /* Signal an error back to the main thread. */
720     error_str = g_strdup (curl_easy_strerror (r));
721     g_idle_add (main_login_error, error_str);
722     return FALSE;
723   }
724
725   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
726   DEBUG ("HTTP return code is %ld", code);
727   switch (code)
728     {
729     case 200: break;
730
731       /* Hmm - even though we previously logged in, the server is
732        * rejecting our attempts now with an authorization error.
733        * We move to the logged out state.
734        */
735     case 401:
736       set_logged_in (FALSE);
737       error_str = g_strdup ("server rejected the username or password");
738       g_idle_add (main_login_error, error_str);
739       return FALSE;
740
741     default:
742       /* XXX If only glib had g_asprintf. */
743       error_str = g_strdup ("unexpected HTTP return code from server");
744       g_idle_add (main_status_error, error_str);
745       return FALSE;
746     }
747
748   /* If we got here then we appear to have a correct
749    * XML document describing the list of VMs.
750    */
751   secs_between_refresh <<= 1;
752
753   xml = write_fn_finish_capture ();
754
755   /*DEBUG ("XML from /vms =\n%s", xml);*/
756   parse_vmlist_from_xml (xml);
757   g_free (xml);
758
759   return TRUE;
760 }
761
762 /* Functions to deal with the list of VMs, parsing it from the XML, etc.
763  *
764  * A vmlist is a GSList (glib singly-linked list) of vm structures.
765  * The caller must call free_vmlist to free up this list correctly.
766  */
767
768 static void
769 free_vm (gpointer _vm, gpointer data)
770 {
771   struct vm *vm = (struct vm *) _vm;
772
773   g_free (vm->description);
774   g_free (vm->state);
775   g_free (vm->uuid);
776   g_free (vm->mac_addr);
777   g_free (vm);
778 }
779
780 void
781 free_vmlist (GSList *vmlist)
782 {
783   g_slist_foreach (vmlist, free_vm, NULL);
784   g_slist_free (vmlist);
785 }
786
787 static struct vm *
788 copy_vm (struct vm *vm)
789 {
790   struct vm *vm2;
791
792   vm2 = g_memdup (vm, sizeof (*vm));
793   vm2->description = g_strdup (vm->description);
794   vm2->state = g_strdup (vm->state);
795   vm2->uuid = g_strdup (vm->uuid);
796   vm2->mac_addr = g_strdup (vm->mac_addr);
797   return vm2;
798 }
799
800 static int
801 compare_vm (gconstpointer _vm1, gconstpointer _vm2)
802 {
803   const struct vm *vm1 = (const struct vm *) _vm1;
804   const struct vm *vm2 = (const struct vm *) _vm2;
805
806   return strcmp (vm1->description, vm2->description);
807 }
808
809 static GSList *vmlist = NULL;
810 static gboolean vmlist_valid = FALSE;
811 static GStaticMutex vmlist_mutex;
812
813 /* Called from the main thread to find out if we have a valid vmlist. */
814 gboolean
815 wui_thread_has_valid_vmlist (void)
816 {
817   gboolean ret;
818
819   ASSERT_IS_MAIN_THREAD ();
820
821   g_static_mutex_lock (&vmlist_mutex);
822   ret = vmlist_valid;
823   g_static_mutex_unlock (&vmlist_mutex);
824   return ret;
825 }
826
827 /* Called from the main thread to find return the current vmlist.  This
828  * actually returns a deep copy of it that the caller must free.
829  */
830 static void
831 duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
832 {
833   struct vm *vm = (struct vm *) _vm;
834   GSList **ret = (GSList **) _ret;
835
836   *ret = g_slist_prepend (*ret, copy_vm (vm));
837 }
838
839 gboolean
840 wui_thread_get_vmlist (GSList **ret)
841 {
842   gboolean r;
843
844   ASSERT_IS_MAIN_THREAD ();
845
846   r = FALSE;
847   *ret = NULL;
848
849   g_static_mutex_lock (&vmlist_mutex);
850   if (!vmlist_valid) goto done;
851
852   g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
853   *ret = g_slist_sort (*ret, compare_vm);
854
855   r = TRUE;
856  done:
857   g_static_mutex_unlock (&vmlist_mutex);
858   return r;
859 }
860
861 /* Called from the message loop in the WUI thread, with an XML document
862  * which we turn into a list of VM structures, and update the vmlist
863  * if we can.
864  */
865 static void
866 parse_vmlist_from_xml (const char *xml)
867 {
868   
869
870
871
872
873
874
875 }