ab38c36d20ebee7d49fa555478ff8cedbb562564
[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 #include "guestfs.h"
37 #include "guestfs-internal.h"
38 #include "guestfs-internal-actions.h"
39 #include "guestfs_protocol.h"
40
41 #if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2)
42
43 static void init_libxml2 (void) __attribute__((constructor));
44
45 static void
46 init_libxml2 (void)
47 {
48   /* I am told that you don't really need to call virInitialize ... */
49
50   xmlInitParser ();
51   LIBXML_TEST_VERSION;
52 }
53
54 struct guestfs___add_libvirt_dom_argv {
55   uint64_t bitmask;
56 #define GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK (UINT64_C(1)<<0)
57   int readonly;
58 #define GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK (UINT64_C(1)<<1)
59   const char *iface;
60 };
61
62 static int guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs);
63
64 int
65 guestfs__add_domain (guestfs_h *g, const char *domain_name,
66                      const struct guestfs_add_domain_argv *optargs)
67 {
68   virErrorPtr err;
69   virConnectPtr conn = NULL;
70   virDomainPtr dom = NULL;
71   int r = -1;
72   const char *libvirturi;
73   int readonly;
74   const char *iface;
75   struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 };
76
77   libvirturi = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIBVIRTURI_BITMASK
78                ? optargs->libvirturi : NULL;
79   readonly = optargs->bitmask & GUESTFS_ADD_DOMAIN_READONLY_BITMASK
80              ? optargs->readonly : 0;
81   iface = optargs->bitmask & GUESTFS_ADD_DOMAIN_IFACE_BITMASK
82           ? optargs->iface : NULL;
83
84   /* Connect to libvirt, find the domain. */
85   conn = virConnectOpenReadOnly (libvirturi);
86   if (!conn) {
87     err = virGetLastError ();
88     error (g, _("could not connect to libvirt (code %d, domain %d): %s"),
89            err->code, err->domain, err->message);
90     goto cleanup;
91   }
92
93   dom = virDomainLookupByName (conn, domain_name);
94   if (!dom) {
95     err = virGetLastError ();
96     error (g, _("no libvirt domain called '%s': %s"),
97            domain_name, err->message);
98     goto cleanup;
99   }
100
101   if (readonly) {
102     optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK;
103     optargs2.readonly = readonly;
104   }
105   if (iface) {
106     optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK;
107     optargs2.iface = iface;
108   }
109
110   r = guestfs___add_libvirt_dom (g, dom, &optargs2);
111
112  cleanup:
113   if (dom) virDomainFree (dom);
114   if (conn) virConnectClose (conn);
115
116   return r;
117 }
118
119 /* This was proposed as an external API, but it's not quite baked yet. */
120 static int
121 guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
122                            const struct guestfs___add_libvirt_dom_argv *optargs)
123 {
124   int r = -1, nr_added = 0, i;
125   virErrorPtr err;
126   xmlDocPtr doc = NULL;
127   xmlXPathContextPtr xpathCtx = NULL;
128   xmlXPathObjectPtr xpathObj = NULL;
129   char *xml = NULL;
130   int readonly;
131   const char *iface;
132   int cmdline_pos;
133
134   cmdline_pos = guestfs___checkpoint_cmdline (g);
135
136   readonly = optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK
137              ? optargs->readonly : 0;
138   iface = optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK
139           ? optargs->iface : NULL;
140
141   if (!readonly) {
142     virDomainInfo info;
143     if (virDomainGetInfo (dom, &info) == -1) {
144       err = virGetLastError ();
145       error (g, _("error getting domain info: %s"), err->message);
146       goto cleanup;
147     }
148     if (info.state != VIR_DOMAIN_SHUTOFF) {
149       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."));
150       goto cleanup;
151     }
152   }
153
154   /* Domain XML. */
155   xml = virDomainGetXMLDesc (dom, 0);
156
157   if (!xml) {
158     err = virGetLastError ();
159     error (g, _("error reading libvirt XML information: %s"),
160            err->message);
161     goto cleanup;
162   }
163
164   /* Now the horrible task of parsing out the fields we need from the XML.
165    * http://www.xmlsoft.org/examples/xpath1.c
166    */
167   doc = xmlParseMemory (xml, strlen (xml));
168   if (doc == NULL) {
169     error (g, _("unable to parse XML information returned by libvirt"));
170     goto cleanup;
171   }
172
173   xpathCtx = xmlXPathNewContext (doc);
174   if (xpathCtx == NULL) {
175     error (g, _("unable to create new XPath context"));
176     goto cleanup;
177   }
178
179   /* This gives us a set of all the <disk> nodes. */
180   xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk", xpathCtx);
181   if (xpathObj == NULL) {
182     error (g, _("unable to evaluate XPath expression"));
183     goto cleanup;
184   }
185
186   xmlNodeSetPtr nodes = xpathObj->nodesetval;
187   for (i = 0; i < nodes->nodeNr; ++i) {
188     xmlXPathObjectPtr xptype;
189
190     /* Change the context to the current <disk> node.
191      * DV advises to reset this before each search since older versions of
192      * libxml2 might overwrite it.
193      */
194     xpathCtx->node = nodes->nodeTab[i];
195
196     /* Filename can be in <source dev=..> or <source file=..> attribute.
197      * Check the <disk type=..> attribute first to find out which one.
198      */
199     xptype = xmlXPathEvalExpression (BAD_CAST "./@type", xpathCtx);
200     if (xptype == NULL ||
201         xptype->nodesetval == NULL ||
202         xptype->nodesetval->nodeNr == 0) {
203       xmlXPathFreeObject (xptype);
204       continue;                 /* no type attribute, skip it */
205     }
206     assert (xptype->nodesetval->nodeTab[0]);
207     assert (xptype->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
208     xmlAttrPtr attr = (xmlAttrPtr) xptype->nodesetval->nodeTab[0];
209     xmlXPathFreeObject (xptype);
210     char *type = (char *) xmlNodeListGetString (doc, attr->children, 1);
211
212     xmlXPathObjectPtr xpfilename;
213
214     if (STREQ (type, "file")) { /* type = "file" so look at source/@file */
215       free (type);
216
217       xpathCtx->node = nodes->nodeTab[i];
218       xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@file", xpathCtx);
219       if (xpfilename == NULL ||
220           xpfilename->nodesetval == NULL ||
221           xpfilename->nodesetval->nodeNr == 0) {
222         xmlXPathFreeObject (xpfilename);
223         continue;             /* disk filename not found, skip this */
224       }
225     } else if (STREQ (type, "block")) { /* type = "block", use source/@dev */
226       free (type);
227
228       xpathCtx->node = nodes->nodeTab[i];
229       xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@dev", xpathCtx);
230       if (xpfilename == NULL ||
231           xpfilename->nodesetval == NULL ||
232           xpfilename->nodesetval->nodeNr == 0) {
233         xmlXPathFreeObject (xpfilename);
234         continue;             /* disk filename not found, skip this */
235       }
236     } else {
237       free (type);
238       continue;               /* type <> "file" or "block", skip it */
239     }
240
241     assert (xpfilename->nodesetval->nodeTab[0]);
242     assert (xpfilename->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
243     attr = (xmlAttrPtr) xpfilename->nodesetval->nodeTab[0];
244     char *filename = (char *) xmlNodeListGetString (doc, attr->children, 1);
245
246     /* Get the disk format (may not be set). */
247     xmlXPathObjectPtr xpformat;
248
249     xpathCtx->node = nodes->nodeTab[i];
250     xpformat = xmlXPathEvalExpression (BAD_CAST "./driver/@type", xpathCtx);
251     char *format = NULL;
252     if (xpformat != NULL &&
253         xpformat->nodesetval &&
254         xpformat->nodesetval->nodeNr > 0) {
255       assert (xpformat->nodesetval->nodeTab[0]);
256       assert (xpformat->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
257       attr = (xmlAttrPtr) xpformat->nodesetval->nodeTab[0];
258       format = (char *) xmlNodeListGetString (doc, attr->children, 1);
259     }
260
261     /* Add the disk, with optional format. */
262     struct guestfs_add_drive_opts_argv optargs2 = { .bitmask = 0 };
263     if (readonly) {
264       optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
265       optargs2.readonly = readonly;
266     }
267     if (format) {
268       optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
269       optargs2.format = format;
270     }
271     if (iface) {
272       optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK;
273       optargs2.iface = iface;
274     }
275
276     int t = guestfs__add_drive_opts (g, filename, &optargs2);
277
278     xmlFree (filename);
279     xmlFree (format);
280     xmlXPathFreeObject (xpfilename);
281     xmlXPathFreeObject (xpformat);
282
283     if (t == -1)
284       goto cleanup;
285
286     nr_added++;
287   }
288
289   if (nr_added == 0) {
290     error (g, _("libvirt domain has no disks"));
291     goto cleanup;
292   }
293
294   /* Successful. */
295   r = nr_added;
296
297  cleanup:
298   if (r == -1) guestfs___rollback_cmdline (g, cmdline_pos);
299   free (xml);
300   if (xpathObj) xmlXPathFreeObject (xpathObj);
301   if (xpathCtx) xmlXPathFreeContext (xpathCtx);
302   if (doc) xmlFreeDoc (doc);
303
304   return r;
305 }
306
307 #else /* no libvirt or libxml2 at compile time */
308
309 #define NOT_IMPL(r)                                                     \
310   error (g, _("add-domain API not available since this version of libguestfs was compiled without libvirt or libxml2")); \
311   return r
312
313 int
314 guestfs__add_domain (guestfs_h *g, const char *dom,
315                      const struct guestfs_add_domain_argv *optargs)
316 {
317   NOT_IMPL(-1);
318 }
319
320 #endif /* no libvirt or libxml2 at compile time */