Parse <vm> structures.
[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 <libxml/parser.h>
31
32 #include <curl/curl.h>
33
34 #include "internal.h"
35
36 /* Private functions. */
37 static gpointer wui_thread (gpointer data);
38 static void wui_thread_send_quit (void);
39 static void do_curl_init (void);
40 static void write_fn_start_capture (void);
41 static char *write_fn_finish_capture (void);
42 static void write_fn_discard_capture_buffer (void);
43 static gboolean do_connect (void);
44 static gboolean do_login (void);
45 static gboolean refresh_vm_list (void);
46 static void parse_vmlist_from_xml (const char *xml);
47 static struct vm *parse_vm_from_xml (xmlNodePtr node);
48
49 /* Messages (main thread -> WUI thread only).
50  *
51  * These are passed by reference.  They are allocated by the sender
52  * (ie. the main thread) and freed by the receiver (ie. the WUI thread).
53  */
54 enum message_type {
55   QUIT,                    /* Tell the WUI thread to quit cleanly. */
56   CONNECT,                 /* Tell to connect (just fetch the URI). */
57   DISCONNECT,              /* Tell to disconnect, forget state. */
58   LOGIN,                   /* Tell to login, and pass credentials. */
59   REFRESH_VM_LIST,         /* Tell to refresh the VM list right away. */
60 };
61
62 struct message {
63   enum message_type type;
64   char *str1;
65   char *str2;
66 };
67
68 /* Start the WUI thread.  See main() for explanation of the threading model. */
69 static GThread *wui_gthread = NULL;
70 static GThread *main_gthread = NULL;
71 static GAsyncQueue *wui_thread_queue = NULL;
72
73 void
74 start_wui_thread (void)
75 {
76   GError *error = NULL;
77
78   DEBUG ("starting the WUI thread");
79
80   assert (wui_gthread == NULL);
81
82   main_gthread = g_thread_self ();
83
84   /* Create the message queue for main -> WUI thread communications. */
85   wui_thread_queue = g_async_queue_new ();
86
87   wui_gthread = g_thread_create (wui_thread, wui_thread_queue, TRUE, &error);
88   if (error) {
89     g_print ("%s\n", error->message);
90     g_error_free (error);
91     exit (1);
92   }
93 }
94
95 void
96 stop_wui_thread (void)
97 {
98   DEBUG ("stopping the WUI thread");
99
100   assert (wui_gthread != NULL);
101   ASSERT_IS_MAIN_THREAD ();
102
103   /* Send a quit message then wait for the WUI thread to join.
104    *
105    * This "nice" shutdown could cause the UI to hang for an
106    * indefinite period (eg. if the WUI thread is engaged in some
107    * long connection or download from the remote server).  But
108    * I want to keep it this way for the moment so that we can
109    * diagnose problems with the WUI thread.
110    *
111    * (This could be solved with some sort of interruptible
112    * join, but glib doesn't support that AFAICT).
113    */
114   wui_thread_send_quit ();
115   (void) g_thread_join (wui_gthread);
116   g_async_queue_unref (wui_thread_queue);
117   wui_gthread = NULL;
118 }
119
120 void
121 assert_is_wui_thread (const char *filename, int lineno)
122 {
123   if (g_thread_self () != wui_gthread) {
124     fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the WUI thread\n", filename, lineno);
125     abort ();
126   }
127 }
128
129 void
130 assert_is_main_thread (const char *filename, int lineno)
131 {
132   if (g_thread_self () != main_gthread) {
133     fprintf (stderr, "%s:%d: internal error: this function should only run in the context of the main thread\n", filename, lineno);
134     abort ();
135   }
136 }
137
138 /* Send the quit message to the WUI thread. */
139 static void
140 wui_thread_send_quit (void)
141 {
142   struct message *msg;
143
144   ASSERT_IS_MAIN_THREAD ();
145
146   msg = g_new (struct message, 1);
147   msg->type = QUIT;
148   g_async_queue_push (wui_thread_queue, msg);
149 }
150
151 /* Send the connect message to the WUI thread. */
152 void
153 wui_thread_send_connect (const char *uri)
154 {
155   struct message *msg;
156
157   ASSERT_IS_MAIN_THREAD ();
158
159   msg = g_new (struct message, 1);
160   msg->type = CONNECT;
161   msg->str1 = g_strdup (uri);
162   g_async_queue_push (wui_thread_queue, msg);
163 }
164
165 /* Send the disconnect message to the WUI thread. */
166 void
167 wui_thread_send_disconnect (void)
168 {
169   struct message *msg;
170
171   ASSERT_IS_MAIN_THREAD ();
172
173   msg = g_new (struct message, 1);
174   msg->type = DISCONNECT;
175   g_async_queue_push (wui_thread_queue, msg);
176 }
177
178 /* Send the login message to the WUI thread. */
179 void
180 wui_thread_send_login (const char *username, const char *password)
181 {
182   struct message *msg;
183
184   ASSERT_IS_MAIN_THREAD ();
185
186   msg = g_new (struct message, 1);
187   msg->type = LOGIN;
188   msg->str1 = g_strdup (username);
189   msg->str2 = g_strdup (password);
190   g_async_queue_push (wui_thread_queue, msg);
191 }
192
193 /* Send the refresh VM list message to the WUI thread. */
194 void
195 wui_thread_send_refresh_vm_list (void)
196 {
197   struct message *msg;
198
199   ASSERT_IS_MAIN_THREAD ();
200
201   msg = g_new (struct message, 1);
202   msg->type = REFRESH_VM_LIST;
203   g_async_queue_push (wui_thread_queue, msg);
204 }
205
206 /* The current state.
207  *
208  * For safety, the main thread must lock this before reading, and the
209  * WUI thread must lock this before writing.  However the WUI thread
210  * does not need to lock before reading, because no other thread
211  * can modify it.
212  */
213 static gboolean connected = FALSE;
214 static gboolean logged_in = FALSE;
215 static gboolean busy = FALSE;
216 static GStaticMutex state_mutex;
217
218 static void set_connected (gboolean);
219 static void set_logged_in (gboolean);
220 static void set_busy (gboolean);
221
222 /* The private state of the WUI thread. */
223 static int secs_between_refresh = 60;
224 static CURL *curl = NULL;
225 static char curl_error_buffer[CURL_ERROR_SIZE];
226 static char *uri = NULL;
227 static char *username = NULL;
228 static char *password = NULL;
229
230 static gboolean process_message (struct message *);
231
232 /* The WUI thread.  See main() above for explanation of
233  * the threading model.
234  */
235 static gpointer
236 wui_thread (gpointer _queue)
237 {
238   GAsyncQueue *queue = (GAsyncQueue *) _queue;
239   gboolean quit = FALSE;
240   GTimeVal tv;
241   gpointer _msg;
242   struct message *msg;
243
244   DEBUG ("WUI thread starting up");
245
246   ASSERT_IS_WUI_THREAD ();
247
248   g_async_queue_ref (queue);
249
250   /* In the thread's loop we check for new instructions from the main
251    * thread and carry them out.  Also, if we are connected and logged
252    * in then we periodically recheck the list of VMs.
253    */
254   while (!quit) {
255     set_busy (FALSE);
256
257     if (logged_in) {
258       g_get_current_time (&tv);
259       g_time_val_add (&tv, secs_between_refresh * 1000000);
260       _msg = g_async_queue_timed_pop (queue, &tv);
261     } else
262       _msg = g_async_queue_pop (queue);
263
264     set_busy (TRUE);
265
266     msg = (struct message *) _msg;
267
268     if (msg) {
269       DEBUG ("received message with msg->type = %d", msg->type);
270       quit = process_message (msg);
271       /* Don't free any strings in the message - we've saved them. */
272       g_free (msg);
273     } else {
274       /* No message, must have got a timeout instead, which means
275        * we are logged in and we should refresh the list of VMs.
276        * Note it's not an error if we temporarily lose contact
277        * with the WUI.
278        */
279       refresh_vm_list ();
280     }
281   }
282
283   DEBUG ("WUI thread shutting down cleanly");
284
285   g_async_queue_unref (queue);
286   g_thread_exit (NULL);
287   return NULL; /* not reached? */
288 }
289
290 /* The WUI thread calls this to safely update the state variables.
291  * This also updates elements in the UI by setting idle callbacks
292  * which are executed in the context of the main thread.
293  */
294 static void
295 set_connected (gboolean new_connected)
296 {
297   ASSERT_IS_WUI_THREAD ();
298
299   g_static_mutex_lock (&state_mutex);
300   connected = new_connected;
301   g_static_mutex_unlock (&state_mutex);
302
303   if (connected)
304     g_idle_add (main_connected, NULL);
305   else
306     g_idle_add (main_disconnected, NULL);
307 }
308
309 static void
310 set_logged_in (gboolean new_logged_in)
311 {
312   ASSERT_IS_WUI_THREAD ();
313
314   g_static_mutex_lock (&state_mutex);
315   logged_in = new_logged_in;
316   g_static_mutex_unlock (&state_mutex);
317
318   if (logged_in)
319     g_idle_add (main_logged_in, NULL);
320   else
321     g_idle_add (main_logged_out, NULL);
322 }
323
324 static void
325 set_busy (gboolean new_busy)
326 {
327   ASSERT_IS_WUI_THREAD ();
328
329   g_static_mutex_lock (&state_mutex);
330   busy = new_busy;
331   g_static_mutex_unlock (&state_mutex);
332
333   if (busy)
334     g_idle_add (main_busy, NULL);
335   else
336     g_idle_add (main_idle, NULL);
337 }
338
339 /* The main thread should call these functions to get the WUI thread's
340  * current state.
341  */
342 gboolean
343 wui_thread_is_connected (void)
344 {
345   gboolean ret;
346
347   ASSERT_IS_MAIN_THREAD ();
348
349   g_static_mutex_lock (&state_mutex);
350   ret = connected;
351   g_static_mutex_unlock (&state_mutex);
352   return ret;
353 }
354
355 gboolean
356 wui_thread_is_logged_in (void)
357 {
358   gboolean ret;
359
360   ASSERT_IS_MAIN_THREAD ();
361
362   g_static_mutex_lock (&state_mutex);
363   ret = logged_in;
364   g_static_mutex_unlock (&state_mutex);
365   return ret;
366 }
367
368 gboolean
369 wui_thread_is_busy (void)
370 {
371   gboolean ret;
372
373   ASSERT_IS_MAIN_THREAD ();
374
375   g_static_mutex_lock (&state_mutex);
376   ret = busy;
377   g_static_mutex_unlock (&state_mutex);
378   return ret;
379 }
380
381 /* Process a message from the main thread. */
382 static gboolean
383 process_message (struct message *msg)
384 {
385   ASSERT_IS_WUI_THREAD ();
386
387   switch (msg->type) {
388   case QUIT:
389     write_fn_discard_capture_buffer ();
390     if (curl) curl_easy_cleanup (curl);
391     if (uri) g_free (uri);
392     if (username) g_free (username);
393     if (password) g_free (password);
394     set_connected (FALSE);
395     set_logged_in (FALSE);
396     return 1;
397
398   case CONNECT:
399     write_fn_discard_capture_buffer ();
400     if (curl) curl_easy_cleanup (curl);
401     do_curl_init ();
402     if (uri) g_free (uri);
403     uri = msg->str1;
404
405     if (do_connect ()) {
406       set_connected (TRUE);
407     } else {
408       set_connected (FALSE);
409       set_logged_in (FALSE);
410     }
411     break;
412
413   case DISCONNECT:
414     /* This just forgets the state. REST is connectionless. */
415     write_fn_discard_capture_buffer ();
416     if (curl) curl_easy_cleanup (curl);
417     curl = NULL;
418     if (uri) g_free (uri);
419     uri = NULL;
420     if (username) g_free (username);
421     username = NULL;
422     if (password) g_free (password);
423     password = NULL;
424     set_connected (FALSE);
425     set_logged_in (FALSE);
426     break;
427
428   case LOGIN:
429     if (username) g_free (username);
430     username = msg->str1;
431     if (password) g_free (password);
432     password = msg->str2;
433
434     /* If we're not connected, this message just updates the
435      * username and password.  Otherwise if we are connected,
436      * try to login and grab the initial list of VMs.
437      */
438     if (connected) {
439       if (do_login ()) {
440         set_logged_in (TRUE);
441         if (refresh_vm_list ())
442           secs_between_refresh = 60;
443       } else {
444         set_logged_in (FALSE);
445       }
446     }
447     break;
448
449   case REFRESH_VM_LIST:
450     if (connected && logged_in) {
451       refresh_vm_list ();
452       secs_between_refresh = 60;
453     }
454     break;
455
456   default:
457     DEBUG ("unknown message type %d", msg->type);
458     abort ();
459   }
460
461   return 0;
462 }
463
464 /* Macro for easy handling of CURL errors. */
465 #define CURL_CHECK_ERROR(fn, args)                                      \
466   ({                                                                    \
467     CURLcode __r = fn args;                                             \
468     if (__r != CURLE_OK) {                                              \
469       fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r));      \
470     }                                                                   \
471     __r;                                                                \
472   })
473
474 /* Libcurl has a really crufty method for handling HTTP headers and
475  * data.  We set these functions as callbacks, because the default
476  * callback functions print the data out to stderr.  In order to
477  * capture the data, we have to keep state here.
478  */
479
480 static char *write_fn_buffer = NULL;
481 static ssize_t write_fn_len = -1;
482
483 static size_t
484 write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
485 {
486   int bytes = size * nmemb;
487   int old_start;
488
489   ASSERT_IS_WUI_THREAD ();
490
491   if (write_fn_len >= 0) {      /* We're capturing. */
492     old_start = write_fn_len;
493     write_fn_len += bytes;
494     write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len);
495     memcpy (write_fn_buffer + old_start, ptr, bytes);
496   }
497
498   return bytes;
499 }
500
501 /* Start capturing HTTP response data. */
502 static void
503 write_fn_start_capture (void)
504 {
505   ASSERT_IS_WUI_THREAD ();
506
507   write_fn_discard_capture_buffer ();
508   write_fn_buffer = NULL;
509   write_fn_len = 0;
510 }
511
512 /* Finish capture and return the capture buffer.  Caller must free. */
513 static char *
514 write_fn_finish_capture (void)
515 {
516   char *ret = write_fn_buffer;
517
518   ASSERT_IS_WUI_THREAD ();
519
520   write_fn_buffer = NULL;
521   write_fn_len = -1;
522   return ret;
523 }
524
525 /* Stop capturing and discard the capture buffer, if any. */
526 static void
527 write_fn_discard_capture_buffer (void)
528 {
529   ASSERT_IS_WUI_THREAD ();
530
531   g_free (write_fn_buffer);
532   write_fn_buffer = NULL;
533   write_fn_len = -1;
534 }
535
536 static size_t
537 header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
538 {
539   int bytes = size * nmemb;
540
541   ASSERT_IS_WUI_THREAD ();
542
543   return bytes;
544 }
545
546 /* Called from the message loop to initialize the CURL handle. */
547 static void
548 do_curl_init (void)
549 {
550   DEBUG ("initializing libcurl");
551
552   ASSERT_IS_WUI_THREAD ();
553
554   curl = curl_easy_init ();
555   if (!curl) {             /* This is probably quite bad, so abort. */
556     DEBUG ("curl_easy_init failed");
557     abort ();
558   }
559
560   CURL_CHECK_ERROR (curl_easy_setopt,
561                     (curl, CURLOPT_CAINFO, cainfo));
562   CURL_CHECK_ERROR (curl_easy_setopt,
563                     (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
564   CURL_CHECK_ERROR (curl_easy_setopt,
565                     (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
566
567   CURL_CHECK_ERROR (curl_easy_setopt,
568                     (curl, CURLOPT_WRITEFUNCTION, write_fn));
569   CURL_CHECK_ERROR (curl_easy_setopt,
570                     (curl, CURLOPT_HEADERFUNCTION, header_fn));
571
572   /* This enables error messages in curl_easy_perform. */
573   CURL_CHECK_ERROR (curl_easy_setopt,
574                     (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
575
576   /* This enables cookie handling, using an internal cookiejar. */
577   CURL_CHECK_ERROR (curl_easy_setopt,
578                     (curl, CURLOPT_COOKIEFILE, ""));
579 }
580
581 /* Called from the message loop.  Try to connect to the current URI.
582  * Returns true on success.
583  */
584 static gboolean
585 do_connect (void)
586 {
587   long code = 0;
588   CURLcode r;
589   char *error_str;
590
591   DEBUG ("connecting to uri %s", uri);
592   ASSERT_IS_WUI_THREAD ();
593
594   /* Set the URI for libcurl. */
595   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
596
597   /* Try to fetch the URI. */
598   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
599   if (r != CURLE_OK) {
600     /* Signal an error back to the main thread. */
601     error_str = g_strdup (curl_easy_strerror (r));
602     g_idle_add (main_connection_error, error_str);
603     return FALSE;
604   }
605
606   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
607   DEBUG ("HTTP return code is %ld", code);
608   if (code != 200 && code != 302 && code != 401) {
609     /* XXX If only glib had g_asprintf. */
610     error_str = g_strdup ("unexpected HTTP return code from server");
611     g_idle_add (main_connection_error, error_str);
612     return FALSE;
613   }
614
615   return TRUE;
616 }
617
618 /* Called from the message loop.  Try to login to 'URI/login' with the
619  * current username and password.  Returns true on success.
620  */
621 static gboolean
622 do_login (void)
623 {
624   int len;
625   char *login_uri;
626   char *userpwd;
627   char *error_str;
628   CURLcode r;
629   long code = 0;
630
631   DEBUG ("logging in with username %s, password *****", username);
632   ASSERT_IS_WUI_THREAD ();
633
634   /* Generate the login URI from the base URI. */
635   len = strlen (uri) + 6 + 1;
636   login_uri = g_alloca (len);
637   snprintf (login_uri, len, "%s/login", uri);
638
639   DEBUG ("login URI %s", login_uri);
640
641   /* Set the URI for libcurl. */
642   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri));
643
644   /* Construct the username:password for CURL. */
645   len = strlen (username) + strlen (password) + 2;
646   userpwd = g_alloca (len);
647   snprintf (userpwd, len, "%s:%s", username, password);
648
649   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd));
650
651   /* HTTP Basic authentication is OK since we should be sending
652    * this only over HTTPS.
653    */
654   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC));
655
656   /* Follow redirects. */
657   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1));
658   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10));
659
660   /* Try to fetch the URI. */
661   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
662   if (r != CURLE_OK) {
663     /* Signal an error back to the main thread. */
664     error_str = g_strdup (curl_easy_strerror (r));
665     g_idle_add (main_login_error, error_str);
666     return FALSE;
667   }
668
669   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
670   DEBUG ("HTTP return code is %ld", code);
671   switch (code)
672     {
673     case 200:
674       DEBUG ("login was successful");
675       return TRUE;
676
677     case 401:
678       error_str = g_strdup ("server rejected the username or password");
679       g_idle_add (main_login_error, error_str);
680       return FALSE;
681
682     default:
683       /* XXX If only glib had g_asprintf. */
684       error_str = g_strdup ("unexpected HTTP return code from server");
685       g_idle_add (main_login_error, error_str);
686       return FALSE;
687     }
688 }
689
690 /* Called from the message loop.  Refresh the list of VMs. */
691 static gboolean
692 refresh_vm_list (void)
693 {
694   int len;
695   char *vms_uri;
696   char *error_str;
697   CURLcode r;
698   long code = 0;
699   char *xml;
700
701   DEBUG ("refreshing list of VMs");
702   ASSERT_IS_WUI_THREAD ();
703
704   /* Generate the vms URI from the base URI. */
705   len = strlen (uri) + 4 + 1;
706   vms_uri = g_alloca (len);
707   snprintf (vms_uri, len, "%s/vms", uri);
708
709   DEBUG ("vms URI %s", vms_uri);
710
711   /* Set the URI for libcurl. */
712   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
713
714   /* We want to capture the output, so tell our write function
715    * to place the output into a buffer.
716    */
717   write_fn_start_capture ();
718
719   /* Try to fetch the URI. */
720   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
721   if (r != CURLE_OK) {
722     /* Signal an error back to the main thread. */
723     error_str = g_strdup (curl_easy_strerror (r));
724     g_idle_add (main_login_error, error_str);
725     return FALSE;
726   }
727
728   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
729   DEBUG ("HTTP return code is %ld", code);
730   switch (code)
731     {
732     case 200: break;
733
734       /* Hmm - even though we previously logged in, the server is
735        * rejecting our attempts now with an authorization error.
736        * We move to the logged out state.
737        */
738     case 401:
739       set_logged_in (FALSE);
740       error_str = g_strdup ("server rejected the username or password");
741       g_idle_add (main_login_error, error_str);
742       return FALSE;
743
744     default:
745       /* XXX If only glib had g_asprintf. */
746       error_str = g_strdup ("unexpected HTTP return code from server");
747       g_idle_add (main_status_error, error_str);
748       return FALSE;
749     }
750
751   /* If we got here then we appear to have a correct
752    * XML document describing the list of VMs.
753    */
754   secs_between_refresh <<= 1;
755
756   xml = write_fn_finish_capture ();
757
758   parse_vmlist_from_xml (xml);
759   g_free (xml);
760
761   return TRUE;
762 }
763
764 /* Functions to deal with the list of VMs, parsing it from the XML, etc.
765  *
766  * A vmlist is a GSList (glib singly-linked list) of vm structures.
767  * The caller must call free_vmlist to free up this list correctly.
768  */
769
770 static void
771 free_vm (gpointer _vm, gpointer data)
772 {
773   struct vm *vm = (struct vm *) _vm;
774
775   g_free (vm->description);
776   g_free (vm->state);
777   g_free (vm->uuid);
778   g_free (vm->mac_addr);
779   g_free (vm);
780 }
781
782 void
783 free_vmlist (GSList *vmlist)
784 {
785   g_slist_foreach (vmlist, free_vm, NULL);
786   g_slist_free (vmlist);
787 }
788
789 static struct vm *
790 copy_vm (struct vm *vm)
791 {
792   struct vm *vm2;
793
794   vm2 = g_memdup (vm, sizeof (*vm));
795   vm2->description = g_strdup (vm->description);
796   vm2->state = vm->state ? g_strdup (vm->state) : NULL;
797   vm2->uuid = vm->uuid ? g_strdup (vm->uuid) : NULL;
798   vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL;
799   return vm2;
800 }
801
802 static int
803 compare_vm (gconstpointer _vm1, gconstpointer _vm2)
804 {
805   const struct vm *vm1 = (const struct vm *) _vm1;
806   const struct vm *vm2 = (const struct vm *) _vm2;
807
808   return strcmp (vm1->description, vm2->description);
809 }
810
811 static GSList *vmlist = NULL;
812 static gboolean vmlist_valid = FALSE;
813 static GStaticMutex vmlist_mutex;
814
815 /* Called from the main thread to find out if we have a valid vmlist. */
816 gboolean
817 wui_thread_has_valid_vmlist (void)
818 {
819   gboolean ret;
820
821   ASSERT_IS_MAIN_THREAD ();
822
823   g_static_mutex_lock (&vmlist_mutex);
824   ret = vmlist_valid;
825   g_static_mutex_unlock (&vmlist_mutex);
826   return ret;
827 }
828
829 /* Called from the main thread to find return the current vmlist.  This
830  * actually returns a deep copy of it that the caller must free.
831  */
832 static void
833 duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
834 {
835   struct vm *vm = (struct vm *) _vm;
836   GSList **ret = (GSList **) _ret;
837
838   *ret = g_slist_prepend (*ret, copy_vm (vm));
839 }
840
841 gboolean
842 wui_thread_get_vmlist (GSList **ret)
843 {
844   gboolean r;
845
846   ASSERT_IS_MAIN_THREAD ();
847
848   r = FALSE;
849   *ret = NULL;
850
851   g_static_mutex_lock (&vmlist_mutex);
852   if (!vmlist_valid) goto done;
853
854   g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
855   *ret = g_slist_sort (*ret, compare_vm);
856
857   r = TRUE;
858  done:
859   g_static_mutex_unlock (&vmlist_mutex);
860   return r;
861 }
862
863 /* Called from the message loop in the WUI thread, with an XML document
864  * which we turn into a list of VM structures, and update the vmlist
865  * if we can.
866  */
867 static void
868 parse_vmlist_from_xml (const char *xml)
869 {
870   xmlDocPtr doc = NULL;
871   xmlNodePtr root;
872   xmlNodePtr node;
873   char *error_str;
874   GSList *new_vmlist = NULL;
875   struct vm *vm;
876
877   /*DEBUG ("XML =\n%s", xml);*/
878   ASSERT_IS_WUI_THREAD ();
879
880   /* We don't really expect that we won't be able to parse the XML ... */
881   doc = xmlParseDoc ((const xmlChar *) xml);
882   if (!doc) {
883     DEBUG ("error parsing XML document, xml =\n%s", xml);
884     error_str = g_strdup ("error parsing XML document from remote server");
885     g_idle_add (main_status_error, error_str);
886     goto done;
887   }
888
889   root = xmlDocGetRootElement (doc);
890   if (!root) {
891     DEBUG ("XML document was empty");
892     error_str = g_strdup ("XML document was empty");
893     g_idle_add (main_status_error, error_str);
894     goto done;
895   }
896
897   /* We expect the root element will be either "nil-classes"
898    * or "vms", with the former indicating an empty list of VMs.
899    */
900   if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) {
901     g_static_mutex_lock (&vmlist_mutex);
902     vmlist_valid = TRUE;
903     free_vmlist (vmlist);
904     vmlist = NULL;
905     g_static_mutex_unlock (&vmlist_mutex);
906
907     /* Signal to the main UI thread that the list has been updated. */
908     g_idle_add (main_vmlist_updated, NULL);
909
910     goto done;
911   }
912
913   if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) {
914     DEBUG ("unexpected root node in XML document, xml =\n%s", xml);
915     error_str = g_strdup ("unexpected root node in XML document");
916     g_idle_add (main_status_error, error_str);
917     goto done;
918   }
919
920   /* The document is <vms> with a list of <vm> elements which
921    * we process in turn.
922    */
923   for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
924     if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) {
925       vm = parse_vm_from_xml (node);
926       if (!vm) {
927         error_str = g_strdup ("could not parse <vm> element");
928         g_idle_add (main_status_error, error_str);
929
930         free_vmlist (new_vmlist);
931         goto done;
932       }
933       new_vmlist = g_slist_prepend (new_vmlist, vm);
934     }
935   }
936
937   /* Successfully parsed all the <vm> nodes, so swap the old and new
938    * vmlists.
939    */
940   g_static_mutex_lock (&vmlist_mutex);
941   vmlist_valid = TRUE;
942   free_vmlist (vmlist);
943   vmlist = new_vmlist;
944   g_static_mutex_unlock (&vmlist_mutex);
945
946   /* Signal that the vmlist has been updated. */
947   g_idle_add (main_vmlist_updated, NULL);
948
949  done:
950   /* Free up XML resources used before returning. */
951   if (doc) xmlFreeDoc (doc);
952 }
953
954 static struct vm *
955 parse_vm_from_xml (xmlNodePtr node)
956 {
957   xmlNodePtr p;
958   struct vm vm, *ret;
959   xmlChar *str;
960
961   memset (&vm, 0, sizeof vm);
962   vm.hostid = -1;
963   vm.id = -1;
964   vm.vnc_port = -1;
965   vm.mem_allocated = -1;
966   vm.mem_used = -1;
967   vm.vcpus_allocated = -1;
968   vm.vcpus_used = -1;
969
970   for (p = node->xmlChildrenNode; p != NULL; p = p->next) {
971     if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) {
972       str = xmlNodeGetContent (p);
973       if (str != NULL) {
974         vm.description = g_strdup ((char *) str);
975         xmlFree (str);
976       }
977     }
978     else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) {
979       str = xmlNodeGetContent (p);
980       if (str != NULL) {
981         vm.hostid = strtol ((char *) str, NULL, 10);
982         xmlFree (str);
983       }
984     }
985     else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) {
986       str = xmlNodeGetContent (p);
987       if (str != NULL) {
988         vm.id = strtol ((char *) str, NULL, 10);
989         xmlFree (str);
990       }
991     }
992     else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) {
993       str = xmlNodeGetContent (p);
994       if (str != NULL) {
995         vm.mem_allocated = strtol ((char *) str, NULL, 10);
996         xmlFree (str);
997       }
998     }
999     else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) {
1000       str = xmlNodeGetContent (p);
1001       if (str != NULL) {
1002         vm.mem_used = strtol ((char *) str, NULL, 10);
1003         xmlFree (str);
1004       }
1005     }
1006     else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) {
1007       str = xmlNodeGetContent (p);
1008       if (str != NULL) {
1009         vm.vcpus_allocated = strtol ((char *) str, NULL, 10);
1010         xmlFree (str);
1011       }
1012     }
1013     else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) {
1014       str = xmlNodeGetContent (p);
1015       if (str != NULL) {
1016         vm.vcpus_used = strtol ((char *) str, NULL, 10);
1017         xmlFree (str);
1018       }
1019     }
1020     else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) {
1021       str = xmlNodeGetContent (p);
1022       if (str != NULL) {
1023         vm.state = g_strdup ((char *) str);
1024         xmlFree (str);
1025       }
1026     }
1027     else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) {
1028       str = xmlNodeGetContent (p);
1029       if (str != NULL) {
1030         vm.uuid = g_strdup ((char *) str);
1031         xmlFree (str);
1032       }
1033     }
1034     else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) {
1035       str = xmlNodeGetContent (p);
1036       if (str != NULL) {
1037         vm.vnc_port = strtol ((char *) str, NULL, 10);
1038         xmlFree (str);
1039       }
1040     }
1041     else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) {
1042       str = xmlNodeGetContent (p);
1043       if (str != NULL) {
1044         vm.mac_addr = g_strdup ((char *) str);
1045         xmlFree (str);
1046       }
1047     }
1048   }
1049
1050   /* Make sure we've got the required fields. */
1051   ret = NULL;
1052   if (vm.description == NULL)
1053     DEBUG ("required field \"description\" missing from <vm> structure");
1054   else if (vm.hostid == -1)
1055     DEBUG ("required field \"description\" missing from <vm> structure");
1056   else if (vm.id == -1)
1057     DEBUG ("required field \"description\" missing from <vm> structure");
1058   else if (vm.vnc_port == -1)
1059     DEBUG ("required field \"vnc-port\" missing from <vm> structure");
1060   else
1061     ret = g_memdup (&vm, sizeof vm);
1062
1063   return ret;
1064 }