Incorporate Gtk-VNC widget.
[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   /* This checks wui_gthread global which is actually set in the
247    * main thread.  Of course, it might not be set if the WUI thread
248    * runs first.  Hence we sleep for the main thread to run. (XXX)
249    */
250   g_usleep (100000);
251   ASSERT_IS_WUI_THREAD ();
252
253   g_async_queue_ref (queue);
254
255   /* In the thread's loop we check for new instructions from the main
256    * thread and carry them out.  Also, if we are connected and logged
257    * in then we periodically recheck the list of VMs.
258    */
259   while (!quit) {
260     set_busy (FALSE);
261
262     if (logged_in) {
263       g_get_current_time (&tv);
264       g_time_val_add (&tv, secs_between_refresh * 1000000);
265       _msg = g_async_queue_timed_pop (queue, &tv);
266     } else
267       _msg = g_async_queue_pop (queue);
268
269     set_busy (TRUE);
270
271     msg = (struct message *) _msg;
272
273     if (msg) {
274       DEBUG ("received message with msg->type = %d", msg->type);
275       quit = process_message (msg);
276       /* Don't free any strings in the message - we've saved them. */
277       g_free (msg);
278     } else {
279       /* No message, must have got a timeout instead, which means
280        * we are logged in and we should refresh the list of VMs.
281        * Note it's not an error if we temporarily lose contact
282        * with the WUI.
283        */
284       refresh_vm_list ();
285     }
286   }
287
288   DEBUG ("WUI thread shutting down cleanly");
289
290   g_async_queue_unref (queue);
291   g_thread_exit (NULL);
292   return NULL; /* not reached? */
293 }
294
295 /* The WUI thread calls this to safely update the state variables.
296  * This also updates elements in the UI by setting idle callbacks
297  * which are executed in the context of the main thread.
298  */
299 static void
300 set_connected (gboolean new_connected)
301 {
302   ASSERT_IS_WUI_THREAD ();
303
304   g_static_mutex_lock (&state_mutex);
305   connected = new_connected;
306   g_static_mutex_unlock (&state_mutex);
307
308   if (connected)
309     g_idle_add (main_connected, NULL);
310   else
311     g_idle_add (main_disconnected, NULL);
312 }
313
314 static void
315 set_logged_in (gboolean new_logged_in)
316 {
317   ASSERT_IS_WUI_THREAD ();
318
319   g_static_mutex_lock (&state_mutex);
320   logged_in = new_logged_in;
321   g_static_mutex_unlock (&state_mutex);
322
323   if (logged_in)
324     g_idle_add (main_logged_in, NULL);
325   else
326     g_idle_add (main_logged_out, NULL);
327 }
328
329 static void
330 set_busy (gboolean new_busy)
331 {
332   ASSERT_IS_WUI_THREAD ();
333
334   g_static_mutex_lock (&state_mutex);
335   busy = new_busy;
336   g_static_mutex_unlock (&state_mutex);
337
338   if (busy)
339     g_idle_add (main_busy, NULL);
340   else
341     g_idle_add (main_idle, NULL);
342 }
343
344 /* The main thread should call these functions to get the WUI thread's
345  * current state.
346  */
347 gboolean
348 wui_thread_is_connected (void)
349 {
350   gboolean ret;
351
352   ASSERT_IS_MAIN_THREAD ();
353
354   g_static_mutex_lock (&state_mutex);
355   ret = connected;
356   g_static_mutex_unlock (&state_mutex);
357   return ret;
358 }
359
360 gboolean
361 wui_thread_is_logged_in (void)
362 {
363   gboolean ret;
364
365   ASSERT_IS_MAIN_THREAD ();
366
367   g_static_mutex_lock (&state_mutex);
368   ret = logged_in;
369   g_static_mutex_unlock (&state_mutex);
370   return ret;
371 }
372
373 gboolean
374 wui_thread_is_busy (void)
375 {
376   gboolean ret;
377
378   ASSERT_IS_MAIN_THREAD ();
379
380   g_static_mutex_lock (&state_mutex);
381   ret = busy;
382   g_static_mutex_unlock (&state_mutex);
383   return ret;
384 }
385
386 /* Process a message from the main thread. */
387 static gboolean
388 process_message (struct message *msg)
389 {
390   ASSERT_IS_WUI_THREAD ();
391
392   switch (msg->type) {
393   case QUIT:
394     write_fn_discard_capture_buffer ();
395     if (curl) curl_easy_cleanup (curl);
396     if (uri) g_free (uri);
397     if (username) g_free (username);
398     if (password) g_free (password);
399     set_connected (FALSE);
400     set_logged_in (FALSE);
401     return 1;
402
403   case CONNECT:
404     write_fn_discard_capture_buffer ();
405     if (curl) curl_easy_cleanup (curl);
406     do_curl_init ();
407     if (uri) g_free (uri);
408     uri = msg->str1;
409
410     if (do_connect ()) {
411       set_connected (TRUE);
412     } else {
413       set_connected (FALSE);
414       set_logged_in (FALSE);
415     }
416     break;
417
418   case DISCONNECT:
419     /* This just forgets the state. REST is connectionless. */
420     write_fn_discard_capture_buffer ();
421     if (curl) curl_easy_cleanup (curl);
422     curl = NULL;
423     if (uri) g_free (uri);
424     uri = NULL;
425     if (username) g_free (username);
426     username = NULL;
427     if (password) g_free (password);
428     password = NULL;
429     set_connected (FALSE);
430     set_logged_in (FALSE);
431     break;
432
433   case LOGIN:
434     if (username) g_free (username);
435     username = msg->str1;
436     if (password) g_free (password);
437     password = msg->str2;
438
439     /* If we're not connected, this message just updates the
440      * username and password.  Otherwise if we are connected,
441      * try to login and grab the initial list of VMs.
442      */
443     if (connected) {
444       if (do_login ()) {
445         set_logged_in (TRUE);
446         if (refresh_vm_list ())
447           secs_between_refresh = 60;
448       } else {
449         set_logged_in (FALSE);
450       }
451     }
452     break;
453
454   case REFRESH_VM_LIST:
455     if (connected && logged_in) {
456       refresh_vm_list ();
457       secs_between_refresh = 60;
458     }
459     break;
460
461   default:
462     DEBUG ("unknown message type %d", msg->type);
463     abort ();
464   }
465
466   return 0;
467 }
468
469 /* Macro for easy handling of CURL errors. */
470 #define CURL_CHECK_ERROR(fn, args)                                      \
471   ({                                                                    \
472     CURLcode __r = fn args;                                             \
473     if (__r != CURLE_OK) {                                              \
474       fprintf (stderr, "%s: %s\n", #fn, curl_easy_strerror (__r));      \
475     }                                                                   \
476     __r;                                                                \
477   })
478
479 /* Libcurl has a really crufty method for handling HTTP headers and
480  * data.  We set these functions as callbacks, because the default
481  * callback functions print the data out to stderr.  In order to
482  * capture the data, we have to keep state here.
483  */
484
485 static char *write_fn_buffer = NULL;
486 static ssize_t write_fn_len = -1;
487
488 static size_t
489 write_fn (void *ptr, size_t size, size_t nmemb, void *stream)
490 {
491   int bytes = size * nmemb;
492   int old_start;
493
494   ASSERT_IS_WUI_THREAD ();
495
496   if (write_fn_len >= 0) {      /* We're capturing. */
497     old_start = write_fn_len;
498     write_fn_len += bytes;
499     write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len);
500     memcpy (write_fn_buffer + old_start, ptr, bytes);
501   }
502
503   return bytes;
504 }
505
506 /* Start capturing HTTP response data. */
507 static void
508 write_fn_start_capture (void)
509 {
510   ASSERT_IS_WUI_THREAD ();
511
512   write_fn_discard_capture_buffer ();
513   write_fn_buffer = NULL;
514   write_fn_len = 0;
515 }
516
517 /* Finish capture and return the capture buffer.  Caller must free. */
518 static char *
519 write_fn_finish_capture (void)
520 {
521   char *ret;
522
523   ASSERT_IS_WUI_THREAD ();
524
525   /* Make sure the buffer is NUL-terminated before returning it. */
526   write_fn_buffer = g_realloc (write_fn_buffer, write_fn_len+1);
527   write_fn_buffer[write_fn_len] = '\0';
528   ret = write_fn_buffer;
529
530   write_fn_buffer = NULL;
531   write_fn_len = -1;
532   return ret;
533 }
534
535 /* Stop capturing and discard the capture buffer, if any. */
536 static void
537 write_fn_discard_capture_buffer (void)
538 {
539   ASSERT_IS_WUI_THREAD ();
540
541   g_free (write_fn_buffer);
542   write_fn_buffer = NULL;
543   write_fn_len = -1;
544 }
545
546 static size_t
547 header_fn (void *ptr, size_t size, size_t nmemb, void *stream)
548 {
549   int bytes = size * nmemb;
550
551   ASSERT_IS_WUI_THREAD ();
552
553   return bytes;
554 }
555
556 /* Called from the message loop to initialize the CURL handle. */
557 static void
558 do_curl_init (void)
559 {
560   DEBUG ("initializing libcurl");
561
562   ASSERT_IS_WUI_THREAD ();
563
564   curl = curl_easy_init ();
565   if (!curl) {             /* This is probably quite bad, so abort. */
566     DEBUG ("curl_easy_init failed");
567     abort ();
568   }
569
570   CURL_CHECK_ERROR (curl_easy_setopt,
571                     (curl, CURLOPT_CAINFO, cainfo));
572   CURL_CHECK_ERROR (curl_easy_setopt,
573                     (curl, CURLOPT_SSL_VERIFYHOST, check_cert ? 2 : 0));
574   CURL_CHECK_ERROR (curl_easy_setopt,
575                     (curl, CURLOPT_SSL_VERIFYPEER, check_cert ? 1 : 0));
576
577   CURL_CHECK_ERROR (curl_easy_setopt,
578                     (curl, CURLOPT_WRITEFUNCTION, write_fn));
579   CURL_CHECK_ERROR (curl_easy_setopt,
580                     (curl, CURLOPT_HEADERFUNCTION, header_fn));
581
582   /* This enables error messages in curl_easy_perform. */
583   CURL_CHECK_ERROR (curl_easy_setopt,
584                     (curl, CURLOPT_ERRORBUFFER, curl_error_buffer));
585
586   /* This enables cookie handling, using an internal cookiejar. */
587   CURL_CHECK_ERROR (curl_easy_setopt,
588                     (curl, CURLOPT_COOKIEFILE, ""));
589 }
590
591 /* Called from the message loop.  Try to connect to the current URI.
592  * Returns true on success.
593  */
594 static gboolean
595 do_connect (void)
596 {
597   long code = 0;
598   CURLcode r;
599   char *error_str;
600
601   DEBUG ("connecting to uri %s", uri);
602   ASSERT_IS_WUI_THREAD ();
603
604   /* Set the URI for libcurl. */
605   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, uri));
606
607   /* Try to fetch the URI. */
608   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
609   if (r != CURLE_OK) {
610     /* Signal an error back to the main thread. */
611     error_str = g_strdup (curl_easy_strerror (r));
612     g_idle_add (main_connection_error, error_str);
613     return FALSE;
614   }
615
616   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
617   DEBUG ("HTTP return code is %ld", code);
618   if (code != 200 && code != 302 && code != 401) {
619     /* XXX If only glib had g_asprintf. */
620     error_str = g_strdup ("unexpected HTTP return code from server");
621     g_idle_add (main_connection_error, error_str);
622     return FALSE;
623   }
624
625   return TRUE;
626 }
627
628 /* Called from the message loop.  Try to login to 'URI/login' with the
629  * current username and password.  Returns true on success.
630  */
631 static gboolean
632 do_login (void)
633 {
634   int len;
635   char *login_uri;
636   char *userpwd;
637   char *error_str;
638   CURLcode r;
639   long code = 0;
640
641   DEBUG ("logging in with username %s, password *****", username);
642   ASSERT_IS_WUI_THREAD ();
643
644   /* Generate the login URI from the base URI. */
645   len = strlen (uri) + 6 + 1;
646   login_uri = g_alloca (len);
647   snprintf (login_uri, len, "%s/login", uri);
648
649   DEBUG ("login URI %s", login_uri);
650
651   /* Set the URI for libcurl. */
652   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, login_uri));
653
654   /* Construct the username:password for CURL. */
655   len = strlen (username) + strlen (password) + 2;
656   userpwd = g_alloca (len);
657   snprintf (userpwd, len, "%s:%s", username, password);
658
659   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_USERPWD, userpwd));
660
661   /* HTTP Basic authentication is OK since we should be sending
662    * this only over HTTPS.
663    */
664   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC));
665
666   /* Follow redirects. */
667   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_FOLLOWLOCATION, (long) 1));
668   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_MAXREDIRS, (long) 10));
669
670   /* Try to fetch the URI. */
671   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
672   if (r != CURLE_OK) {
673     /* Signal an error back to the main thread. */
674     error_str = g_strdup (curl_easy_strerror (r));
675     g_idle_add (main_login_error, error_str);
676     return FALSE;
677   }
678
679   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
680   DEBUG ("HTTP return code is %ld", code);
681   switch (code)
682     {
683     case 200:
684       DEBUG ("login was successful");
685       return TRUE;
686
687     case 401:
688       error_str = g_strdup ("server rejected the username or password");
689       g_idle_add (main_login_error, error_str);
690       return FALSE;
691
692     default:
693       /* XXX If only glib had g_asprintf. */
694       error_str = g_strdup ("unexpected HTTP return code from server");
695       g_idle_add (main_login_error, error_str);
696       return FALSE;
697     }
698 }
699
700 /* Called from the message loop.  Refresh the list of VMs. */
701 static gboolean
702 refresh_vm_list (void)
703 {
704   int len;
705   char *vms_uri;
706   char *error_str;
707   CURLcode r;
708   long code = 0;
709   char *xml;
710
711   DEBUG ("refreshing list of VMs");
712   ASSERT_IS_WUI_THREAD ();
713
714   /* Generate the vms URI from the base URI. */
715   len = strlen (uri) + 4 + 1;
716   vms_uri = g_alloca (len);
717   snprintf (vms_uri, len, "%s/vms", uri);
718
719   DEBUG ("vms URI %s", vms_uri);
720
721   /* Set the URI for libcurl. */
722   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
723
724   /* We want to capture the output, so tell our write function
725    * to place the output into a buffer.
726    */
727   write_fn_start_capture ();
728
729   /* Try to fetch the URI. */
730   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
731   if (r != CURLE_OK) {
732     /* Signal an error back to the main thread. */
733     error_str = g_strdup (curl_easy_strerror (r));
734     g_idle_add (main_login_error, error_str);
735     return FALSE;
736   }
737
738   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
739   DEBUG ("HTTP return code is %ld", code);
740   switch (code)
741     {
742     case 200: break;
743
744       /* Hmm - even though we previously logged in, the server is
745        * rejecting our attempts now with an authorization error.
746        * We move to the logged out state.
747        */
748     case 401:
749       set_logged_in (FALSE);
750       error_str = g_strdup ("server rejected the username or password");
751       g_idle_add (main_login_error, error_str);
752       return FALSE;
753
754     default:
755       /* XXX If only glib had g_asprintf. */
756       error_str = g_strdup ("unexpected HTTP return code from server");
757       g_idle_add (main_status_error, error_str);
758       return FALSE;
759     }
760
761   /* If we got here then we appear to have a correct
762    * XML document describing the list of VMs.
763    */
764   secs_between_refresh <<= 1;
765
766   xml = write_fn_finish_capture ();
767
768   parse_vmlist_from_xml (xml);
769   g_free (xml);
770
771   return TRUE;
772 }
773
774 /* Functions to deal with the list of VMs, parsing it from the XML, etc.
775  *
776  * A vmlist is a GSList (glib singly-linked list) of vm structures.
777  * The caller must call free_vmlist to free up this list correctly.
778  */
779
780 static void
781 free_vm (gpointer _vm, gpointer data)
782 {
783   struct vm *vm = (struct vm *) _vm;
784
785   g_free (vm->description);
786   g_free (vm->state);
787   g_free (vm->uuid);
788   g_free (vm->mac_addr);
789   g_free (vm);
790 }
791
792 void
793 free_vmlist (GSList *vmlist)
794 {
795   g_slist_foreach (vmlist, free_vm, NULL);
796   g_slist_free (vmlist);
797 }
798
799 static struct vm *
800 copy_vm (struct vm *vm)
801 {
802   struct vm *vm2;
803
804   vm2 = g_memdup (vm, sizeof (*vm));
805   vm2->description = g_strdup (vm->description);
806   vm2->uuid = g_strdup (vm->uuid);
807   vm2->state = vm->state ? g_strdup (vm->state) : NULL;
808   vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL;
809   return vm2;
810 }
811
812 static int
813 compare_vm (gconstpointer _vm1, gconstpointer _vm2)
814 {
815   const struct vm *vm1 = (const struct vm *) _vm1;
816   const struct vm *vm2 = (const struct vm *) _vm2;
817
818   return strcmp (vm1->description, vm2->description);
819 }
820
821 static GSList *vmlist = NULL;
822 static gboolean vmlist_valid = FALSE;
823 static GStaticMutex vmlist_mutex;
824
825 /* Called from the main thread to find out if we have a valid vmlist. */
826 gboolean
827 wui_thread_has_valid_vmlist (void)
828 {
829   gboolean ret;
830
831   ASSERT_IS_MAIN_THREAD ();
832
833   g_static_mutex_lock (&vmlist_mutex);
834   ret = vmlist_valid;
835   g_static_mutex_unlock (&vmlist_mutex);
836   return ret;
837 }
838
839 /* Called from the main thread to find return the current vmlist.  This
840  * actually returns a deep copy of it that the caller must free.
841  */
842 static void
843 duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
844 {
845   struct vm *vm = (struct vm *) _vm;
846   GSList **ret = (GSList **) _ret;
847
848   *ret = g_slist_prepend (*ret, copy_vm (vm));
849 }
850
851 gboolean
852 wui_thread_get_vmlist (GSList **ret)
853 {
854   gboolean r;
855
856   ASSERT_IS_MAIN_THREAD ();
857
858   r = FALSE;
859   *ret = NULL;
860
861   g_static_mutex_lock (&vmlist_mutex);
862   if (!vmlist_valid) goto done;
863
864   g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
865   *ret = g_slist_sort (*ret, compare_vm);
866
867   r = TRUE;
868  done:
869   g_static_mutex_unlock (&vmlist_mutex);
870   return r;
871 }
872
873 /* Called from the message loop in the WUI thread, with an XML document
874  * which we turn into a list of VM structures, and update the vmlist
875  * if we can.
876  */
877 static void
878 parse_vmlist_from_xml (const char *xml)
879 {
880   xmlDocPtr doc = NULL;
881   xmlNodePtr root;
882   xmlNodePtr node;
883   char *error_str;
884   GSList *new_vmlist = NULL;
885   struct vm *vm;
886   int len;
887
888   /*DEBUG ("XML =\n%s", xml);*/
889   ASSERT_IS_WUI_THREAD ();
890
891   /* We don't really expect that we won't be able to parse the XML ... */
892   len = strlen (xml);
893   doc = xmlReadMemory (xml, len, NULL, NULL, 0);
894
895   if (!doc) {
896     DEBUG ("error parsing XML document, xml =\n%s", xml);
897     error_str = g_strdup ("error parsing XML document from remote server");
898     g_idle_add (main_status_error, error_str);
899     goto done;
900   }
901
902   root = xmlDocGetRootElement (doc);
903   if (!root) {
904     DEBUG ("XML document was empty");
905     error_str = g_strdup ("XML document was empty");
906     g_idle_add (main_status_error, error_str);
907     goto done;
908   }
909
910   /* We expect the root element will be either "nil-classes"
911    * or "vms", with the former indicating an empty list of VMs.
912    */
913   if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) {
914     g_static_mutex_lock (&vmlist_mutex);
915     vmlist_valid = TRUE;
916     free_vmlist (vmlist);
917     vmlist = NULL;
918     g_static_mutex_unlock (&vmlist_mutex);
919
920     /* Signal to the main UI thread that the list has been updated. */
921     g_idle_add (main_vmlist_updated, NULL);
922
923     goto done;
924   }
925
926   if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) {
927     DEBUG ("unexpected root node in XML document, xml =\n%s", xml);
928     error_str = g_strdup ("unexpected root node in XML document");
929     g_idle_add (main_status_error, error_str);
930     goto done;
931   }
932
933   /* The document is <vms> with a list of <vm> elements which
934    * we process in turn.
935    */
936   for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
937     if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) {
938       vm = parse_vm_from_xml (node);
939       if (!vm) {
940         error_str = g_strdup ("could not parse <vm> element");
941         g_idle_add (main_status_error, error_str);
942
943         free_vmlist (new_vmlist);
944         goto done;
945       }
946       new_vmlist = g_slist_prepend (new_vmlist, vm);
947     }
948   }
949
950   /* Successfully parsed all the <vm> nodes, so swap the old and new
951    * vmlists.
952    */
953   g_static_mutex_lock (&vmlist_mutex);
954   vmlist_valid = TRUE;
955   free_vmlist (vmlist);
956   vmlist = new_vmlist;
957   g_static_mutex_unlock (&vmlist_mutex);
958
959   /* Signal that the vmlist has been updated. */
960   g_idle_add (main_vmlist_updated, NULL);
961
962  done:
963   /* Free up XML resources used before returning. */
964   if (doc) xmlFreeDoc (doc);
965 }
966
967 static struct vm *
968 parse_vm_from_xml (xmlNodePtr node)
969 {
970   xmlNodePtr p;
971   struct vm vm, *ret;
972   xmlChar *str;
973
974   memset (&vm, 0, sizeof vm);
975   vm.hostid = -1;
976   vm.id = -1;
977   vm.vnc_port = -1;
978   vm.mem_allocated = -1;
979   vm.mem_used = -1;
980   vm.vcpus_allocated = -1;
981   vm.vcpus_used = -1;
982
983   for (p = node->xmlChildrenNode; p != NULL; p = p->next) {
984     if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) {
985       str = xmlNodeGetContent (p);
986       if (str != NULL) {
987         vm.description = g_strdup ((char *) str);
988         xmlFree (str);
989       }
990     }
991     else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) {
992       str = xmlNodeGetContent (p);
993       if (str != NULL) {
994         vm.hostid = strtol ((char *) str, NULL, 10);
995         xmlFree (str);
996       }
997     }
998     else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) {
999       str = xmlNodeGetContent (p);
1000       if (str != NULL) {
1001         vm.id = strtol ((char *) str, NULL, 10);
1002         xmlFree (str);
1003       }
1004     }
1005     else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) {
1006       str = xmlNodeGetContent (p);
1007       if (str != NULL) {
1008         vm.mem_allocated = strtol ((char *) str, NULL, 10);
1009         xmlFree (str);
1010       }
1011     }
1012     else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) {
1013       str = xmlNodeGetContent (p);
1014       if (str != NULL) {
1015         vm.mem_used = strtol ((char *) str, NULL, 10);
1016         xmlFree (str);
1017       }
1018     }
1019     else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) {
1020       str = xmlNodeGetContent (p);
1021       if (str != NULL) {
1022         vm.vcpus_allocated = strtol ((char *) str, NULL, 10);
1023         xmlFree (str);
1024       }
1025     }
1026     else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) {
1027       str = xmlNodeGetContent (p);
1028       if (str != NULL) {
1029         vm.vcpus_used = strtol ((char *) str, NULL, 10);
1030         xmlFree (str);
1031       }
1032     }
1033     else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) {
1034       str = xmlNodeGetContent (p);
1035       if (str != NULL) {
1036         vm.state = g_strdup ((char *) str);
1037         xmlFree (str);
1038       }
1039     }
1040     else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) {
1041       str = xmlNodeGetContent (p);
1042       if (str != NULL) {
1043         vm.uuid = g_strdup ((char *) str);
1044         xmlFree (str);
1045       }
1046     }
1047     else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) {
1048       str = xmlNodeGetContent (p);
1049       if (str != NULL) {
1050         vm.vnc_port = strtol ((char *) str, NULL, 10);
1051         xmlFree (str);
1052       }
1053     }
1054     else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) {
1055       str = xmlNodeGetContent (p);
1056       if (str != NULL) {
1057         vm.mac_addr = g_strdup ((char *) str);
1058         xmlFree (str);
1059       }
1060     }
1061   }
1062
1063   /* Make sure we've got the required fields. */
1064   ret = NULL;
1065   if (vm.description == NULL)
1066     DEBUG ("required field \"description\" missing from <vm> structure");
1067   else if (vm.hostid == -1)
1068     DEBUG ("required field \"description\" missing from <vm> structure");
1069   else if (vm.id == -1)
1070     DEBUG ("required field \"description\" missing from <vm> structure");
1071   else if (vm.vnc_port == -1)
1072     DEBUG ("required field \"vnc-port\" missing from <vm> structure");
1073   else if (vm.uuid == NULL)
1074     DEBUG ("required field \"uuid\" missing from <vm> structure");
1075   else
1076     ret = g_memdup (&vm, sizeof vm);
1077
1078   return ret;
1079 }