add cmd line option for server vnc port
[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   /* Try to fetch the URI. */
673   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
674   if (r != CURLE_OK) {
675     /* Signal an error back to the main thread. */
676     error_str = g_strdup (curl_easy_strerror (r));
677     g_idle_add (main_login_error, error_str);
678     return FALSE;
679   }
680
681   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
682   DEBUG ("HTTP return code is %ld", code);
683   switch (code)
684     {
685     case 200:
686       DEBUG ("login was successful");
687       return TRUE;
688
689     case 401:
690       error_str = g_strdup ("server rejected the username or password");
691       g_idle_add (main_login_error, error_str);
692       return FALSE;
693
694     default:
695       /* XXX If only glib had g_asprintf. */
696       error_str = g_strdup ("unexpected HTTP return code from server");
697       g_idle_add (main_login_error, error_str);
698       return FALSE;
699     }
700 }
701
702 /* Called from the message loop.  Refresh the list of VMs. */
703 static gboolean
704 refresh_vm_list (void)
705 {
706   int len;
707   char *vms_uri;
708   char *error_str;
709   CURLcode r;
710   long code = 0;
711   char *xml;
712
713   DEBUG ("refreshing list of VMs");
714   ASSERT_IS_WUI_THREAD ();
715
716   /* Generate the vms URI from the base URI. */
717   len = strlen (uri) + 4 + 1;
718   vms_uri = g_alloca (len);
719   snprintf (vms_uri, len, "%s/vms", uri);
720
721   DEBUG ("vms URI %s", vms_uri);
722
723   /* Set the URI for libcurl. */
724   CURL_CHECK_ERROR (curl_easy_setopt, (curl, CURLOPT_URL, vms_uri));
725
726   /* We want to capture the output, so tell our write function
727    * to place the output into a buffer.
728    */
729   write_fn_start_capture ();
730
731   /* Try to fetch the URI. */
732   r = CURL_CHECK_ERROR (curl_easy_perform, (curl));
733   if (r != CURLE_OK) {
734     /* Signal an error back to the main thread. */
735     error_str = g_strdup (curl_easy_strerror (r));
736     g_idle_add (main_login_error, error_str);
737     return FALSE;
738   }
739
740   CURL_CHECK_ERROR (curl_easy_getinfo, (curl, CURLINFO_RESPONSE_CODE, &code));
741   DEBUG ("HTTP return code is %ld", code);
742   switch (code)
743     {
744     case 200: break;
745
746       /* Hmm - even though we previously logged in, the server is
747        * rejecting our attempts now with an authorization error.
748        * We move to the logged out state.
749        */
750     case 401:
751       set_logged_in (FALSE);
752       error_str = g_strdup ("server rejected the username or password");
753       g_idle_add (main_login_error, error_str);
754       return FALSE;
755
756     default:
757       /* XXX If only glib had g_asprintf. */
758       error_str = g_strdup ("unexpected HTTP return code from server");
759       g_idle_add (main_status_error, error_str);
760       return FALSE;
761     }
762
763   /* If we got here then we appear to have a correct
764    * XML document describing the list of VMs.
765    */
766   secs_between_refresh <<= 1;
767
768   xml = write_fn_finish_capture ();
769
770   parse_vmlist_from_xml (xml);
771   g_free (xml);
772
773   return TRUE;
774 }
775
776 /* Functions to deal with the list of VMs, parsing it from the XML, etc.
777  *
778  * A vmlist is a GSList (glib singly-linked list) of vm structures.
779  * The caller must call free_vmlist to free up this list correctly.
780  */
781
782 static void
783 free_vm (gpointer _vm, gpointer data)
784 {
785   struct vm *vm = (struct vm *) _vm;
786
787   g_free (vm->description);
788   g_free (vm->state);
789   g_free (vm->uuid);
790   g_free (vm->mac_addr);
791   g_free (vm);
792 }
793
794 void
795 free_vmlist (GSList *vmlist)
796 {
797   g_slist_foreach (vmlist, free_vm, NULL);
798   g_slist_free (vmlist);
799 }
800
801 static struct vm *
802 copy_vm (struct vm *vm)
803 {
804   struct vm *vm2;
805
806   vm2 = g_memdup (vm, sizeof (*vm));
807   vm2->description = g_strdup (vm->description);
808   vm2->uuid = g_strdup (vm->uuid);
809   vm2->state = vm->state ? g_strdup (vm->state) : NULL;
810   vm2->mac_addr = vm->mac_addr ? g_strdup (vm->mac_addr) : NULL;
811   return vm2;
812 }
813
814 static int
815 compare_vm (gconstpointer _vm1, gconstpointer _vm2)
816 {
817   const struct vm *vm1 = (const struct vm *) _vm1;
818   const struct vm *vm2 = (const struct vm *) _vm2;
819
820   return strcmp (vm1->description, vm2->description);
821 }
822
823 static GSList *vmlist = NULL;
824 static gboolean vmlist_valid = FALSE;
825 static GStaticMutex vmlist_mutex;
826
827 /* Called from the main thread to find out if we have a valid vmlist. */
828 gboolean
829 wui_thread_has_valid_vmlist (void)
830 {
831   gboolean ret;
832
833   ASSERT_IS_MAIN_THREAD ();
834
835   g_static_mutex_lock (&vmlist_mutex);
836   ret = vmlist_valid;
837   g_static_mutex_unlock (&vmlist_mutex);
838   return ret;
839 }
840
841 /* Called from the main thread to find return the current vmlist.  This
842  * actually returns a deep copy of it that the caller must free.
843  */
844 static void
845 duplicate_and_insert_vm (gpointer _vm, gpointer _ret)
846 {
847   struct vm *vm = (struct vm *) _vm;
848   GSList **ret = (GSList **) _ret;
849
850   *ret = g_slist_prepend (*ret, copy_vm (vm));
851 }
852
853 gboolean
854 wui_thread_get_vmlist (GSList **ret)
855 {
856   gboolean r;
857
858   ASSERT_IS_MAIN_THREAD ();
859
860   r = FALSE;
861   *ret = NULL;
862
863   g_static_mutex_lock (&vmlist_mutex);
864   if (!vmlist_valid) goto done;
865
866   g_slist_foreach (vmlist, duplicate_and_insert_vm, ret);
867   *ret = g_slist_sort (*ret, compare_vm);
868
869   r = TRUE;
870  done:
871   g_static_mutex_unlock (&vmlist_mutex);
872   return r;
873 }
874
875 /* Called from the message loop in the WUI thread, with an XML document
876  * which we turn into a list of VM structures, and update the vmlist
877  * if we can.
878  */
879 static void
880 parse_vmlist_from_xml (const char *xml)
881 {
882   xmlDocPtr doc = NULL;
883   xmlNodePtr root;
884   xmlNodePtr node;
885   char *error_str;
886   GSList *new_vmlist = NULL;
887   struct vm *vm;
888   int len;
889
890   /*DEBUG ("XML =\n%s", xml);*/
891   ASSERT_IS_WUI_THREAD ();
892
893   /* We don't really expect that we won't be able to parse the XML ... */
894   len = strlen (xml);
895   doc = xmlReadMemory (xml, len, NULL, NULL, 0);
896
897   if (!doc) {
898     DEBUG ("error parsing XML document, xml =\n%s", xml);
899     error_str = g_strdup ("error parsing XML document from remote server");
900     g_idle_add (main_status_error, error_str);
901     goto done;
902   }
903
904   root = xmlDocGetRootElement (doc);
905   if (!root) {
906     DEBUG ("XML document was empty");
907     error_str = g_strdup ("XML document was empty");
908     g_idle_add (main_status_error, error_str);
909     goto done;
910   }
911
912   /* We expect the root element will be either "nil-classes"
913    * or "vms", with the former indicating an empty list of VMs.
914    */
915   if (xmlStrcmp (root->name, (const xmlChar *) "nil-classes") == 0) {
916     g_static_mutex_lock (&vmlist_mutex);
917     vmlist_valid = TRUE;
918     free_vmlist (vmlist);
919     vmlist = NULL;
920     g_static_mutex_unlock (&vmlist_mutex);
921
922     /* Signal to the main UI thread that the list has been updated. */
923     g_idle_add (main_vmlist_updated, NULL);
924
925     goto done;
926   }
927
928   if (xmlStrcmp (root->name, (const xmlChar *) "vms") != 0) {
929     DEBUG ("unexpected root node in XML document, xml =\n%s", xml);
930     error_str = g_strdup ("unexpected root node in XML document");
931     g_idle_add (main_status_error, error_str);
932     goto done;
933   }
934
935   /* The document is <vms> with a list of <vm> elements which
936    * we process in turn.
937    */
938   for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
939     if (xmlStrcmp (node->name, (const xmlChar *) "vm") == 0) {
940       vm = parse_vm_from_xml (node);
941       if (!vm) {
942         error_str = g_strdup ("could not parse <vm> element");
943         g_idle_add (main_status_error, error_str);
944
945         free_vmlist (new_vmlist);
946         goto done;
947       }
948       new_vmlist = g_slist_prepend (new_vmlist, vm);
949     }
950   }
951
952   /* Successfully parsed all the <vm> nodes, so swap the old and new
953    * vmlists.
954    */
955   g_static_mutex_lock (&vmlist_mutex);
956   vmlist_valid = TRUE;
957   free_vmlist (vmlist);
958   vmlist = new_vmlist;
959   g_static_mutex_unlock (&vmlist_mutex);
960
961   /* Signal that the vmlist has been updated. */
962   g_idle_add (main_vmlist_updated, NULL);
963
964  done:
965   /* Free up XML resources used before returning. */
966   if (doc) xmlFreeDoc (doc);
967 }
968
969 static struct vm *
970 parse_vm_from_xml (xmlNodePtr node)
971 {
972   xmlNodePtr p;
973   struct vm vm, *ret;
974   xmlChar *str;
975
976   memset (&vm, 0, sizeof vm);
977   vm.hostid = -1;
978   vm.id = -1;
979   vm.vnc_port = -1;
980   vm.forward_vnc_port = -1;
981   vm.mem_allocated = -1;
982   vm.mem_used = -1;
983   vm.vcpus_allocated = -1;
984   vm.vcpus_used = -1;
985
986   for (p = node->xmlChildrenNode; p != NULL; p = p->next) {
987     if (xmlStrcmp (p->name, (const xmlChar *) "description") == 0) {
988       str = xmlNodeGetContent (p);
989       if (str != NULL) {
990         vm.description = g_strdup ((char *) str);
991         xmlFree (str);
992       }
993     }
994     else if (xmlStrcmp (p->name, (const xmlChar *) "host-id") == 0) {
995       str = xmlNodeGetContent (p);
996       if (str != NULL) {
997         vm.hostid = strtol ((char *) str, NULL, 10);
998         xmlFree (str);
999       }
1000     }
1001     else if (xmlStrcmp (p->name, (const xmlChar *) "id") == 0) {
1002       str = xmlNodeGetContent (p);
1003       if (str != NULL) {
1004         vm.id = strtol ((char *) str, NULL, 10);
1005         xmlFree (str);
1006       }
1007     }
1008     else if (xmlStrcmp (p->name, (const xmlChar *) "memory-allocated") == 0) {
1009       str = xmlNodeGetContent (p);
1010       if (str != NULL) {
1011         vm.mem_allocated = strtol ((char *) str, NULL, 10);
1012         xmlFree (str);
1013       }
1014     }
1015     else if (xmlStrcmp (p->name, (const xmlChar *) "memory-used") == 0) {
1016       str = xmlNodeGetContent (p);
1017       if (str != NULL) {
1018         vm.mem_used = strtol ((char *) str, NULL, 10);
1019         xmlFree (str);
1020       }
1021     }
1022     else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-allocated") == 0) {
1023       str = xmlNodeGetContent (p);
1024       if (str != NULL) {
1025         vm.vcpus_allocated = strtol ((char *) str, NULL, 10);
1026         xmlFree (str);
1027       }
1028     }
1029     else if (xmlStrcmp (p->name, (const xmlChar *) "num-vcpus-used") == 0) {
1030       str = xmlNodeGetContent (p);
1031       if (str != NULL) {
1032         vm.vcpus_used = strtol ((char *) str, NULL, 10);
1033         xmlFree (str);
1034       }
1035     }
1036     else if (xmlStrcmp (p->name, (const xmlChar *) "state") == 0) {
1037       str = xmlNodeGetContent (p);
1038       if (str != NULL) {
1039         vm.state = g_strdup ((char *) str);
1040         xmlFree (str);
1041       }
1042     }
1043     else if (xmlStrcmp (p->name, (const xmlChar *) "uuid") == 0) {
1044       str = xmlNodeGetContent (p);
1045       if (str != NULL) {
1046         vm.uuid = g_strdup ((char *) str);
1047         xmlFree (str);
1048       }
1049     }
1050     else if (xmlStrcmp (p->name, (const xmlChar *) "vnc-port") == 0) {
1051       str = xmlNodeGetContent (p);
1052       if (str != NULL) {
1053         vm.vnc_port = strtol ((char *) str, NULL, 10);
1054         xmlFree (str);
1055       }
1056     }
1057     else if (xmlStrcmp (p->name, (const xmlChar *) "forward-vnc-port") == 0) {
1058       str = xmlNodeGetContent (p);
1059       if (str != NULL) {
1060         vm.forward_vnc_port = strtol ((char *) str, NULL, 10);
1061         xmlFree (str);
1062       }
1063     }
1064     else if (xmlStrcmp (p->name, (const xmlChar *) "vnic-mac-addr") == 0) {
1065       str = xmlNodeGetContent (p);
1066       if (str != NULL) {
1067         vm.mac_addr = g_strdup ((char *) str);
1068         xmlFree (str);
1069       }
1070     }
1071   }
1072
1073   /* Make sure we've got the required fields. */
1074   ret = NULL;
1075   if (vm.description == NULL)
1076     DEBUG ("required field \"description\" missing from <vm> structure");
1077   else if (vm.hostid == -1)
1078     DEBUG ("required field \"description\" missing from <vm> structure");
1079   else if (vm.id == -1)
1080     DEBUG ("required field \"description\" missing from <vm> structure");
1081   else if (vm.vnc_port == -1)
1082     DEBUG ("required field \"vnc-port\" missing from <vm> structure");
1083   else if (vm.forward_vnc_port == -1)
1084     DEBUG ("required field \"forward-vnc-port\" missing from <vm> structure");
1085   else if (vm.uuid == NULL)
1086     DEBUG ("required field \"uuid\" missing from <vm> structure");
1087   else
1088     ret = g_memdup (&vm, sizeof vm);
1089
1090   return ret;
1091 }
1092
1093 gboolean
1094 main_vmlist_has_running_vm(struct vm* _vm)
1095 {
1096   // TODO ? get list and wait to be retreived
1097   // wui_thread_send_refresh_vm_list();
1098
1099   // find vm in list
1100   GSList* res = g_slist_find_custom (vmlist, _vm, compare_vm);
1101
1102   // return true if running
1103   if(res != NULL) return STREQ (((struct vm*) res->data)->state, "running");
1104
1105   return FALSE;
1106 }