protocol: Handle progress notification messages during FileIn.
[libguestfs.git] / src / virt.c
1 /* libguestfs
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <assert.h>
24
25 #ifdef HAVE_LIBVIRT
26 #include <libvirt/libvirt.h>
27 #include <libvirt/virterror.h>
28 #endif
29
30 #ifdef HAVE_LIBXML2
31 #include <libxml/xpath.h>
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #endif
35
36 #define GUESTFS_PRIVATE_FOR_EACH_DISK 1
37
38 #include "guestfs.h"
39 #include "guestfs-internal.h"
40 #include "guestfs-internal-actions.h"
41 #include "guestfs_protocol.h"
42
43 #if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2)
44
45 static void init_libxml2 (void) __attribute__((constructor));
46
47 static void
48 init_libxml2 (void)
49 {
50   /* I am told that you don't really need to call virInitialize ... */
51
52   xmlInitParser ();
53   LIBXML_TEST_VERSION;
54 }
55
56 struct guestfs___add_libvirt_dom_argv {
57   uint64_t bitmask;
58 #define GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK (UINT64_C(1)<<0)
59   int readonly;
60 #define GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK (UINT64_C(1)<<1)
61   const char *iface;
62 };
63
64 static int guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs);
65
66 int
67 guestfs__add_domain (guestfs_h *g, const char *domain_name,
68                      const struct guestfs_add_domain_argv *optargs)
69 {
70   virErrorPtr err;
71   virConnectPtr conn = NULL;
72   virDomainPtr dom = NULL;
73   int r = -1;
74   const char *libvirturi;
75   int readonly;
76   const char *iface;
77   struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 };
78
79   libvirturi = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIBVIRTURI_BITMASK
80                ? optargs->libvirturi : NULL;
81   readonly = optargs->bitmask & GUESTFS_ADD_DOMAIN_READONLY_BITMASK
82              ? optargs->readonly : 0;
83   iface = optargs->bitmask & GUESTFS_ADD_DOMAIN_IFACE_BITMASK
84           ? optargs->iface : NULL;
85
86   /* Connect to libvirt, find the domain. */
87   conn = virConnectOpenReadOnly (libvirturi);
88   if (!conn) {
89     err = virGetLastError ();
90     error (g, _("could not connect to libvirt (code %d, domain %d): %s"),
91            err->code, err->domain, err->message);
92     goto cleanup;
93   }
94
95   dom = virDomainLookupByName (conn, domain_name);
96   if (!dom) {
97     err = virGetLastError ();
98     error (g, _("no libvirt domain called '%s': %s"),
99            domain_name, err->message);
100     goto cleanup;
101   }
102
103   if (readonly) {
104     optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK;
105     optargs2.readonly = readonly;
106   }
107   if (iface) {
108     optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK;
109     optargs2.iface = iface;
110   }
111
112   r = guestfs___add_libvirt_dom (g, dom, &optargs2);
113
114  cleanup:
115   if (dom) virDomainFree (dom);
116   if (conn) virConnectClose (conn);
117
118   return r;
119 }
120
121 /* This function is also used in virt-df to avoid having all that
122  * stupid XPath code repeated.  This is something that libvirt should
123  * really provide.
124  *
125  * The callback function 'f' is called once for each disk.
126  *
127  * Returns number of disks, or -1 if there was an error.
128  */
129 int
130 guestfs___for_each_disk (guestfs_h *g,
131                          virDomainPtr dom,
132                          int (*f) (guestfs_h *g,
133                                    const char *filename, const char *format,
134                                    void *data),
135                          void *data)
136 {
137   int i, nr_added = 0, r = -1;
138   virErrorPtr err;
139   xmlDocPtr doc = NULL;
140   xmlXPathContextPtr xpathCtx = NULL;
141   xmlXPathObjectPtr xpathObj = NULL;
142   char *xml = NULL;
143
144   /* Domain XML. */
145   xml = virDomainGetXMLDesc (dom, 0);
146
147   if (!xml) {
148     err = virGetLastError ();
149     error (g, _("error reading libvirt XML information: %s"),
150            err->message);
151     goto cleanup;
152   }
153
154   /* Now the horrible task of parsing out the fields we need from the XML.
155    * http://www.xmlsoft.org/examples/xpath1.c
156    */
157   doc = xmlParseMemory (xml, strlen (xml));
158   if (doc == NULL) {
159     error (g, _("unable to parse XML information returned by libvirt"));
160     goto cleanup;
161   }
162
163   xpathCtx = xmlXPathNewContext (doc);
164   if (xpathCtx == NULL) {
165     error (g, _("unable to create new XPath context"));
166     goto cleanup;
167   }
168
169   /* This gives us a set of all the <disk> nodes. */
170   xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk", xpathCtx);
171   if (xpathObj == NULL) {
172     error (g, _("unable to evaluate XPath expression"));
173     goto cleanup;
174   }
175
176   xmlNodeSetPtr nodes = xpathObj->nodesetval;
177   for (i = 0; i < nodes->nodeNr; ++i) {
178     xmlXPathObjectPtr xptype;
179
180     /* Change the context to the current <disk> node.
181      * DV advises to reset this before each search since older versions of
182      * libxml2 might overwrite it.
183      */
184     xpathCtx->node = nodes->nodeTab[i];
185
186     /* Filename can be in <source dev=..> or <source file=..> attribute.
187      * Check the <disk type=..> attribute first to find out which one.
188      */
189     xptype = xmlXPathEvalExpression (BAD_CAST "./@type", xpathCtx);
190     if (xptype == NULL ||
191         xptype->nodesetval == NULL ||
192         xptype->nodesetval->nodeNr == 0) {
193       xmlXPathFreeObject (xptype);
194       continue;                 /* no type attribute, skip it */
195     }
196     assert (xptype->nodesetval->nodeTab[0]);
197     assert (xptype->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
198     xmlAttrPtr attr = (xmlAttrPtr) xptype->nodesetval->nodeTab[0];
199     xmlXPathFreeObject (xptype);
200     char *type = (char *) xmlNodeListGetString (doc, attr->children, 1);
201
202     xmlXPathObjectPtr xpfilename;
203
204     if (STREQ (type, "file")) { /* type = "file" so look at source/@file */
205       free (type);
206
207       xpathCtx->node = nodes->nodeTab[i];
208       xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@file", xpathCtx);
209       if (xpfilename == NULL ||
210           xpfilename->nodesetval == NULL ||
211           xpfilename->nodesetval->nodeNr == 0) {
212         xmlXPathFreeObject (xpfilename);
213         continue;             /* disk filename not found, skip this */
214       }
215     } else if (STREQ (type, "block")) { /* type = "block", use source/@dev */
216       free (type);
217
218       xpathCtx->node = nodes->nodeTab[i];
219       xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@dev", xpathCtx);
220       if (xpfilename == NULL ||
221           xpfilename->nodesetval == NULL ||
222           xpfilename->nodesetval->nodeNr == 0) {
223         xmlXPathFreeObject (xpfilename);
224         continue;             /* disk filename not found, skip this */
225       }
226     } else {
227       free (type);
228       continue;               /* type <> "file" or "block", skip it */
229     }
230
231     assert (xpfilename->nodesetval->nodeTab[0]);
232     assert (xpfilename->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
233     attr = (xmlAttrPtr) xpfilename->nodesetval->nodeTab[0];
234     char *filename = (char *) xmlNodeListGetString (doc, attr->children, 1);
235
236     /* Get the disk format (may not be set). */
237     xmlXPathObjectPtr xpformat;
238
239     xpathCtx->node = nodes->nodeTab[i];
240     xpformat = xmlXPathEvalExpression (BAD_CAST "./driver/@type", xpathCtx);
241     char *format = NULL;
242     if (xpformat != NULL &&
243         xpformat->nodesetval &&
244         xpformat->nodesetval->nodeNr > 0) {
245       assert (xpformat->nodesetval->nodeTab[0]);
246       assert (xpformat->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
247       attr = (xmlAttrPtr) xpformat->nodesetval->nodeTab[0];
248       format = (char *) xmlNodeListGetString (doc, attr->children, 1);
249     }
250
251     int t;
252     if (f)
253       t = f (g, filename, format, data);
254     else
255       t = 0;
256
257     xmlFree (filename);
258     xmlFree (format);
259     xmlXPathFreeObject (xpfilename);
260     xmlXPathFreeObject (xpformat);
261
262     if (t == -1)
263       goto cleanup;
264
265     nr_added++;
266   }
267
268   if (nr_added == 0) {
269     error (g, _("libvirt domain has no disks"));
270     goto cleanup;
271   }
272
273   /* Successful. */
274   r = nr_added;
275
276  cleanup:
277   free (xml);
278   if (xpathObj) xmlXPathFreeObject (xpathObj);
279   if (xpathCtx) xmlXPathFreeContext (xpathCtx);
280   if (doc) xmlFreeDoc (doc);
281
282   return r;
283 }
284
285 static int
286 add_disk (guestfs_h *g, const char *filename, const char *format,
287           void *optargs_vp)
288 {
289   struct guestfs_add_drive_opts_argv *optargs = optargs_vp;
290
291   if (format) {
292     optargs->bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
293     optargs->format = format;
294   } else
295     optargs->bitmask &= ~GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
296
297   return guestfs__add_drive_opts (g, filename, optargs);
298 }
299
300 /* This was proposed as an external API, but it's not quite baked yet. */
301 static int
302 guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
303                            const struct guestfs___add_libvirt_dom_argv *optargs)
304 {
305   int r = -1;
306   virErrorPtr err;
307   int cmdline_pos;
308
309   cmdline_pos = guestfs___checkpoint_cmdline (g);
310
311   int readonly =
312     optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK
313     ? optargs->readonly : 0;
314   const char *iface =
315     optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK
316     ? optargs->iface : NULL;
317
318   if (!readonly) {
319     virDomainInfo info;
320     if (virDomainGetInfo (dom, &info) == -1) {
321       err = virGetLastError ();
322       error (g, _("error getting domain info: %s"), err->message);
323       goto cleanup;
324     }
325     if (info.state != VIR_DOMAIN_SHUTOFF) {
326       error (g, _("error: domain is a live virtual machine.\nYou must use readonly access because write access to a running virtual machine\ncan cause disk corruption."));
327       goto cleanup;
328     }
329   }
330
331   /* Add the disks. */
332   struct guestfs_add_drive_opts_argv optargs2 = { .bitmask = 0 };
333   if (readonly) {
334     optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
335     optargs2.readonly = readonly;
336   }
337   if (iface) {
338     optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK;
339     optargs2.iface = iface;
340   }
341
342   r = guestfs___for_each_disk (g, dom, add_disk, &optargs2);
343
344  cleanup:
345   if (r == -1) guestfs___rollback_cmdline (g, cmdline_pos);
346   return r;
347 }
348
349 #else /* no libvirt or libxml2 at compile time */
350
351 #define NOT_IMPL(r)                                                     \
352   error (g, _("add-domain API not available since this version of libguestfs was compiled without libvirt or libxml2")); \
353   return r
354
355 int
356 guestfs__add_domain (guestfs_h *g, const char *dom,
357                      const struct guestfs_add_domain_argv *optargs)
358 {
359   NOT_IMPL(-1);
360 }
361
362 #endif /* no libvirt or libxml2 at compile time */