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