91b84cb17bd0130d306c43fab9f9dafe78707091
[libguestfs.git] / src / inspect.c
1 /* libguestfs
2  * Copyright (C) 2010-2011 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 <stdint.h>
24 #include <inttypes.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30 #include <endian.h>
31
32 #include <pcre.h>
33
34 #ifdef HAVE_HIVEX
35 #include <hivex.h>
36 #endif
37
38 #include "c-ctype.h"
39 #include "ignore-value.h"
40 #include "xstrtol.h"
41
42 #include "guestfs.h"
43 #include "guestfs-internal.h"
44 #include "guestfs-internal-actions.h"
45 #include "guestfs_protocol.h"
46
47 #if defined(HAVE_HIVEX)
48
49 /* The main inspection code. */
50 char **
51 guestfs__inspect_os (guestfs_h *g)
52 {
53   /* Remove any information previously stored in the handle. */
54   guestfs___free_inspect_info (g);
55
56   if (guestfs_umount_all (g) == -1)
57     return NULL;
58
59   /* Iterate over all possible devices.  Try to mount each
60    * (read-only).  Examine ones which contain filesystems and add that
61    * information to the handle.
62    */
63   /* Look to see if any devices directly contain filesystems (RHBZ#590167). */
64   char **devices;
65   devices = guestfs_list_devices (g);
66   if (devices == NULL)
67     return NULL;
68
69   size_t i;
70   for (i = 0; devices[i] != NULL; ++i) {
71     if (guestfs___check_for_filesystem_on (g, devices[i], 1, 0) == -1) {
72       guestfs___free_string_list (devices);
73       guestfs___free_inspect_info (g);
74       return NULL;
75     }
76   }
77   guestfs___free_string_list (devices);
78
79   /* Look at all partitions. */
80   char **partitions;
81   partitions = guestfs_list_partitions (g);
82   if (partitions == NULL) {
83     guestfs___free_inspect_info (g);
84     return NULL;
85   }
86
87   for (i = 0; partitions[i] != NULL; ++i) {
88     if (guestfs___check_for_filesystem_on (g, partitions[i], 0, i+1) == -1) {
89       guestfs___free_string_list (partitions);
90       guestfs___free_inspect_info (g);
91       return NULL;
92     }
93   }
94   guestfs___free_string_list (partitions);
95
96   /* Look at all LVs. */
97   if (guestfs___feature_available (g, "lvm2")) {
98     char **lvs;
99     lvs = guestfs_lvs (g);
100     if (lvs == NULL) {
101       guestfs___free_inspect_info (g);
102       return NULL;
103     }
104
105     for (i = 0; lvs[i] != NULL; ++i) {
106       if (guestfs___check_for_filesystem_on (g, lvs[i], 0, 0) == -1) {
107         guestfs___free_string_list (lvs);
108         guestfs___free_inspect_info (g);
109         return NULL;
110       }
111     }
112     guestfs___free_string_list (lvs);
113   }
114
115   /* At this point we have, in the handle, a list of all filesystems
116    * found and data about each one.  Now we assemble the list of
117    * filesystems which are root devices and return that to the user.
118    * Fall through to guestfs__inspect_get_roots to do that.
119    */
120   char **ret = guestfs__inspect_get_roots (g);
121   if (ret == NULL)
122     guestfs___free_inspect_info (g);
123   return ret;
124 }
125
126 char **
127 guestfs__inspect_get_roots (guestfs_h *g)
128 {
129   /* NB. Doesn't matter if g->nr_fses == 0.  We just return an empty
130    * list in this case.
131    */
132
133   size_t i;
134   size_t count = 0;
135   for (i = 0; i < g->nr_fses; ++i)
136     if (g->fses[i].is_root)
137       count++;
138
139   char **ret = calloc (count+1, sizeof (char *));
140   if (ret == NULL) {
141     perrorf (g, "calloc");
142     return NULL;
143   }
144
145   count = 0;
146   for (i = 0; i < g->nr_fses; ++i) {
147     if (g->fses[i].is_root) {
148       ret[count] = safe_strdup (g, g->fses[i].device);
149       count++;
150     }
151   }
152   ret[count] = NULL;
153
154   return ret;
155 }
156
157 char *
158 guestfs__inspect_get_type (guestfs_h *g, const char *root)
159 {
160   struct inspect_fs *fs = guestfs___search_for_root (g, root);
161   if (!fs)
162     return NULL;
163
164   char *ret;
165   switch (fs->type) {
166   case OS_TYPE_FREEBSD: ret = safe_strdup (g, "freebsd"); break;
167   case OS_TYPE_LINUX: ret = safe_strdup (g, "linux"); break;
168   case OS_TYPE_NETBSD: ret = safe_strdup (g, "netbsd"); break;
169   case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break;
170   case OS_TYPE_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
171   }
172
173   return ret;
174 }
175
176 char *
177 guestfs__inspect_get_arch (guestfs_h *g, const char *root)
178 {
179   struct inspect_fs *fs = guestfs___search_for_root (g, root);
180   if (!fs)
181     return NULL;
182
183   return safe_strdup (g, fs->arch ? : "unknown");
184 }
185
186 char *
187 guestfs__inspect_get_distro (guestfs_h *g, const char *root)
188 {
189   struct inspect_fs *fs = guestfs___search_for_root (g, root);
190   if (!fs)
191     return NULL;
192
193   char *ret;
194   switch (fs->distro) {
195   case OS_DISTRO_ARCHLINUX: ret = safe_strdup (g, "archlinux"); break;
196   case OS_DISTRO_CENTOS: ret = safe_strdup (g, "centos"); break;
197   case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break;
198   case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break;
199   case OS_DISTRO_GENTOO: ret = safe_strdup (g, "gentoo"); break;
200   case OS_DISTRO_LINUX_MINT: ret = safe_strdup (g, "linuxmint"); break;
201   case OS_DISTRO_MAGEIA: ret = safe_strdup (g, "mageia"); break;
202   case OS_DISTRO_MANDRIVA: ret = safe_strdup (g, "mandriva"); break;
203   case OS_DISTRO_MEEGO: ret = safe_strdup (g, "meego"); break;
204   case OS_DISTRO_OPENSUSE: ret = safe_strdup (g, "opensuse"); break;
205   case OS_DISTRO_PARDUS: ret = safe_strdup (g, "pardus"); break;
206   case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break;
207   case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break;
208   case OS_DISTRO_SCIENTIFIC_LINUX: ret = safe_strdup (g, "scientificlinux"); break;
209   case OS_DISTRO_SLACKWARE: ret = safe_strdup (g, "slackware"); break;
210   case OS_DISTRO_TTYLINUX: ret = safe_strdup (g, "ttylinux"); break;
211   case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break;
212   case OS_DISTRO_UBUNTU: ret = safe_strdup (g, "ubuntu"); break;
213   case OS_DISTRO_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
214   }
215
216   return ret;
217 }
218
219 int
220 guestfs__inspect_get_major_version (guestfs_h *g, const char *root)
221 {
222   struct inspect_fs *fs = guestfs___search_for_root (g, root);
223   if (!fs)
224     return -1;
225
226   return fs->major_version;
227 }
228
229 int
230 guestfs__inspect_get_minor_version (guestfs_h *g, const char *root)
231 {
232   struct inspect_fs *fs = guestfs___search_for_root (g, root);
233   if (!fs)
234     return -1;
235
236   return fs->minor_version;
237 }
238
239 char *
240 guestfs__inspect_get_product_name (guestfs_h *g, const char *root)
241 {
242   struct inspect_fs *fs = guestfs___search_for_root (g, root);
243   if (!fs)
244     return NULL;
245
246   return safe_strdup (g, fs->product_name ? : "unknown");
247 }
248
249 char *
250 guestfs__inspect_get_product_variant (guestfs_h *g, const char *root)
251 {
252   struct inspect_fs *fs = guestfs___search_for_root (g, root);
253   if (!fs)
254     return NULL;
255
256   return safe_strdup (g, fs->product_variant ? : "unknown");
257 }
258
259 char *
260 guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
261 {
262   struct inspect_fs *fs = guestfs___search_for_root (g, root);
263   if (!fs)
264     return NULL;
265
266   if (!fs->windows_systemroot) {
267     error (g, _("not a Windows guest, or systemroot could not be determined"));
268     return NULL;
269   }
270
271   return safe_strdup (g, fs->windows_systemroot);
272 }
273
274 char *
275 guestfs__inspect_get_windows_current_control_set (guestfs_h *g,
276                                                   const char *root)
277 {
278   struct inspect_fs *fs = guestfs___search_for_root (g, root);
279   if (!fs)
280     return NULL;
281
282   if (!fs->windows_current_control_set) {
283     error (g, _("not a Windows guest, or CurrentControlSet could not be determined"));
284     return NULL;
285   }
286
287   return safe_strdup (g, fs->windows_current_control_set);
288 }
289
290 char *
291 guestfs__inspect_get_format (guestfs_h *g, const char *root)
292 {
293   struct inspect_fs *fs = guestfs___search_for_root (g, root);
294   if (!fs)
295     return NULL;
296
297   char *ret;
298   switch (fs->format) {
299   case OS_FORMAT_INSTALLED: ret = safe_strdup (g, "installed"); break;
300   case OS_FORMAT_INSTALLER: ret = safe_strdup (g, "installer"); break;
301   case OS_FORMAT_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
302   }
303
304   return ret;
305 }
306
307 int
308 guestfs__inspect_is_live (guestfs_h *g, const char *root)
309 {
310   struct inspect_fs *fs = guestfs___search_for_root (g, root);
311   if (!fs)
312     return -1;
313
314   return fs->is_live_disk;
315 }
316
317 int
318 guestfs__inspect_is_netinst (guestfs_h *g, const char *root)
319 {
320   struct inspect_fs *fs = guestfs___search_for_root (g, root);
321   if (!fs)
322     return -1;
323
324   return fs->is_netinst_disk;
325 }
326
327 int
328 guestfs__inspect_is_multipart (guestfs_h *g, const char *root)
329 {
330   struct inspect_fs *fs = guestfs___search_for_root (g, root);
331   if (!fs)
332     return -1;
333
334   return fs->is_multipart_disk;
335 }
336
337 char **
338 guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
339 {
340   struct inspect_fs *fs = guestfs___search_for_root (g, root);
341   if (!fs)
342     return NULL;
343
344   char **ret;
345
346   /* If no fstab information (Windows) return just the root. */
347   if (fs->nr_fstab == 0) {
348     ret = calloc (3, sizeof (char *));
349     ret[0] = safe_strdup (g, "/");
350     ret[1] = safe_strdup (g, root);
351     ret[2] = NULL;
352     return ret;
353   }
354
355 #define CRITERION fs->fstab[i].mountpoint[0] == '/'
356   size_t i, count = 0;
357   for (i = 0; i < fs->nr_fstab; ++i)
358     if (CRITERION)
359       count++;
360
361   /* Hashtables have 2N+1 entries. */
362   ret = calloc (2*count+1, sizeof (char *));
363   if (ret == NULL) {
364     perrorf (g, "calloc");
365     return NULL;
366   }
367
368   count = 0;
369   for (i = 0; i < fs->nr_fstab; ++i)
370     if (CRITERION) {
371       ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint);
372       ret[2*count+1] = safe_strdup (g, fs->fstab[i].device);
373       count++;
374     }
375 #undef CRITERION
376
377   return ret;
378 }
379
380 char **
381 guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
382 {
383   struct inspect_fs *fs = guestfs___search_for_root (g, root);
384   if (!fs)
385     return NULL;
386
387   char **ret;
388
389   /* If no fstab information (Windows) return just the root. */
390   if (fs->nr_fstab == 0) {
391     ret = calloc (2, sizeof (char *));
392     ret[0] = safe_strdup (g, root);
393     ret[1] = NULL;
394     return ret;
395   }
396
397   ret = calloc (fs->nr_fstab + 1, sizeof (char *));
398   if (ret == NULL) {
399     perrorf (g, "calloc");
400     return NULL;
401   }
402
403   size_t i;
404   for (i = 0; i < fs->nr_fstab; ++i)
405     ret[i] = safe_strdup (g, fs->fstab[i].device);
406
407   return ret;
408 }
409
410 char **
411 guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root)
412 {
413   char **ret;
414   size_t i, count;
415   struct inspect_fs *fs;
416
417   fs = guestfs___search_for_root (g, root);
418   if (!fs)
419     return NULL;
420
421   /* If no drive mappings, return an empty hashtable. */
422   if (!fs->drive_mappings)
423     count = 0;
424   else {
425     for (count = 0; fs->drive_mappings[count] != NULL; count++)
426       ;
427   }
428
429   ret = calloc (count+1, sizeof (char *));
430   if (ret == NULL) {
431     perrorf (g, "calloc");
432     return NULL;
433   }
434
435   /* We need to make a deep copy of the hashtable since the caller
436    * will free it.
437    */
438   for (i = 0; i < count; ++i)
439     ret[i] = safe_strdup (g, fs->drive_mappings[i]);
440
441   ret[count] = NULL;
442
443   return ret;
444 }
445
446 char *
447 guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
448 {
449   struct inspect_fs *fs = guestfs___search_for_root (g, root);
450   if (!fs)
451     return NULL;
452
453   char *ret;
454   switch (fs->package_format) {
455   case OS_PACKAGE_FORMAT_RPM: ret = safe_strdup (g, "rpm"); break;
456   case OS_PACKAGE_FORMAT_DEB: ret = safe_strdup (g, "deb"); break;
457   case OS_PACKAGE_FORMAT_PACMAN: ret = safe_strdup (g, "pacman"); break;
458   case OS_PACKAGE_FORMAT_EBUILD: ret = safe_strdup (g, "ebuild"); break;
459   case OS_PACKAGE_FORMAT_PISI: ret = safe_strdup (g, "pisi"); break;
460   case OS_PACKAGE_FORMAT_PKGSRC: ret = safe_strdup (g, "pkgsrc"); break;
461   case OS_PACKAGE_FORMAT_UNKNOWN:
462   default:
463     ret = safe_strdup (g, "unknown");
464     break;
465   }
466
467   return ret;
468 }
469
470 char *
471 guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
472 {
473   struct inspect_fs *fs = guestfs___search_for_root (g, root);
474   if (!fs)
475     return NULL;
476
477   char *ret;
478   switch (fs->package_management) {
479   case OS_PACKAGE_MANAGEMENT_YUM: ret = safe_strdup (g, "yum"); break;
480   case OS_PACKAGE_MANAGEMENT_UP2DATE: ret = safe_strdup (g, "up2date"); break;
481   case OS_PACKAGE_MANAGEMENT_APT: ret = safe_strdup (g, "apt"); break;
482   case OS_PACKAGE_MANAGEMENT_PACMAN: ret = safe_strdup (g, "pacman"); break;
483   case OS_PACKAGE_MANAGEMENT_PORTAGE: ret = safe_strdup (g, "portage"); break;
484   case OS_PACKAGE_MANAGEMENT_PISI: ret = safe_strdup (g, "pisi"); break;
485   case OS_PACKAGE_MANAGEMENT_URPMI: ret = safe_strdup (g, "urpmi"); break;
486   case OS_PACKAGE_MANAGEMENT_ZYPPER: ret = safe_strdup (g, "zypper"); break;
487   case OS_PACKAGE_MANAGEMENT_UNKNOWN:
488   default:
489     ret = safe_strdup (g, "unknown");
490     break;
491   }
492
493   return ret;
494 }
495
496 char *
497 guestfs__inspect_get_hostname (guestfs_h *g, const char *root)
498 {
499   struct inspect_fs *fs = guestfs___search_for_root (g, root);
500   if (!fs)
501     return NULL;
502
503   return safe_strdup (g, fs->hostname ? : "unknown");
504 }
505
506 /* Download a guest file to a local temporary file.  The file is
507  * cached in the temporary directory, and is not downloaded again.
508  *
509  * The name of the temporary (downloaded) file is returned.  The
510  * caller must free the pointer, but does *not* need to delete the
511  * temporary file.  It will be deleted when the handle is closed.
512  *
513  * Refuse to download the guest file if it is larger than max_size.
514  * On this and other errors, NULL is returned.
515  *
516  * There is actually one cache per 'struct inspect_fs *' in order
517  * to handle the case of multiple roots.
518  */
519 char *
520 guestfs___download_to_tmp (guestfs_h *g, struct inspect_fs *fs,
521                            const char *filename,
522                            const char *basename, int64_t max_size)
523 {
524   char *r;
525   int fd;
526   char devfd[32];
527   int64_t size;
528
529   /* Make the basename unique by prefixing it with the fs number. */
530   if (asprintf (&r, "%s/%ld-%s", g->tmpdir, fs - g->fses, basename) == -1) {
531     perrorf (g, "asprintf");
532     return NULL;
533   }
534
535   /* If the file has already been downloaded, return. */
536   if (access (r, R_OK) == 0)
537     return r;
538
539   /* Check size of remote file. */
540   size = guestfs_filesize (g, filename);
541   if (size == -1)
542     /* guestfs_filesize failed and has already set error in handle */
543     goto error;
544   if (size > max_size) {
545     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
546            filename, size);
547     goto error;
548   }
549
550   fd = open (r, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0600);
551   if (fd == -1) {
552     perrorf (g, "open: %s", r);
553     goto error;
554   }
555
556   snprintf (devfd, sizeof devfd, "/dev/fd/%d", fd);
557
558   if (guestfs_download (g, filename, devfd) == -1) {
559     unlink (r);
560     close (fd);
561     goto error;
562   }
563
564   if (close (fd) == -1) {
565     perrorf (g, "close: %s", r);
566     unlink (r);
567     goto error;
568   }
569
570   return r;
571
572  error:
573   free (r);
574   return NULL;
575 }
576
577 struct inspect_fs *
578 guestfs___search_for_root (guestfs_h *g, const char *root)
579 {
580   if (g->nr_fses == 0) {
581     error (g, _("no inspection data: call guestfs_inspect_os first"));
582     return NULL;
583   }
584
585   size_t i;
586   struct inspect_fs *fs;
587   for (i = 0; i < g->nr_fses; ++i) {
588     fs = &g->fses[i];
589     if (fs->is_root && STREQ (root, fs->device))
590       return fs;
591   }
592
593   error (g, _("%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"),
594          root);
595   return NULL;
596 }
597
598 #else /* no hivex at compile time */
599
600 /* XXX These functions should be in an optgroup. */
601
602 #define NOT_IMPL(r)                                                     \
603   error (g, _("inspection API not available since this version of libguestfs was compiled without the hivex library")); \
604   return r
605
606 char **
607 guestfs__inspect_os (guestfs_h *g)
608 {
609   NOT_IMPL(NULL);
610 }
611
612 char **
613 guestfs__inspect_get_roots (guestfs_h *g)
614 {
615   NOT_IMPL(NULL);
616 }
617
618 char *
619 guestfs__inspect_get_type (guestfs_h *g, const char *root)
620 {
621   NOT_IMPL(NULL);
622 }
623
624 char *
625 guestfs__inspect_get_arch (guestfs_h *g, const char *root)
626 {
627   NOT_IMPL(NULL);
628 }
629
630 char *
631 guestfs__inspect_get_distro (guestfs_h *g, const char *root)
632 {
633   NOT_IMPL(NULL);
634 }
635
636 int
637 guestfs__inspect_get_major_version (guestfs_h *g, const char *root)
638 {
639   NOT_IMPL(-1);
640 }
641
642 int
643 guestfs__inspect_get_minor_version (guestfs_h *g, const char *root)
644 {
645   NOT_IMPL(-1);
646 }
647
648 char *
649 guestfs__inspect_get_product_name (guestfs_h *g, const char *root)
650 {
651   NOT_IMPL(NULL);
652 }
653
654 char *
655 guestfs__inspect_get_product_variant (guestfs_h *g, const char *root)
656 {
657   NOT_IMPL(NULL);
658 }
659
660 char *
661 guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
662 {
663   NOT_IMPL(NULL);
664 }
665
666 char *
667 guestfs__inspect_get_windows_current_control_set (guestfs_h *g,
668                                                   const char *root)
669 {
670   NOT_IMPL(NULL);
671 }
672
673 char **
674 guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
675 {
676   NOT_IMPL(NULL);
677 }
678
679 char **
680 guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
681 {
682   NOT_IMPL(NULL);
683 }
684
685 char **
686 guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root)
687 {
688   NOT_IMPL(NULL);
689 }
690
691 char *
692 guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
693 {
694   NOT_IMPL(NULL);
695 }
696
697 char *
698 guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
699 {
700   NOT_IMPL(NULL);
701 }
702
703 char *
704 guestfs__inspect_get_hostname (guestfs_h *g, const char *root)
705 {
706   NOT_IMPL(NULL);
707 }
708
709 char *
710 guestfs__inspect_get_format (guestfs_h *g, const char *root)
711 {
712   NOT_IMPL(NULL);
713 }
714
715 int
716 guestfs__inspect_is_live (guestfs_h *g, const char *root)
717 {
718   NOT_IMPL(-1);
719 }
720
721 int
722 guestfs__inspect_is_netinst (guestfs_h *g, const char *root)
723 {
724   NOT_IMPL(-1);
725 }
726
727 int
728 guestfs__inspect_is_multipart (guestfs_h *g, const char *root)
729 {
730   NOT_IMPL(-1);
731 }
732
733 #endif /* no hivex at compile time */
734
735 void
736 guestfs___free_inspect_info (guestfs_h *g)
737 {
738   size_t i;
739   for (i = 0; i < g->nr_fses; ++i) {
740     free (g->fses[i].device);
741     free (g->fses[i].product_name);
742     free (g->fses[i].product_variant);
743     free (g->fses[i].arch);
744     free (g->fses[i].hostname);
745     free (g->fses[i].windows_systemroot);
746     free (g->fses[i].windows_current_control_set);
747     size_t j;
748     for (j = 0; j < g->fses[i].nr_fstab; ++j) {
749       free (g->fses[i].fstab[j].device);
750       free (g->fses[i].fstab[j].mountpoint);
751     }
752     free (g->fses[i].fstab);
753     if (g->fses[i].drive_mappings)
754       guestfs___free_string_list (g->fses[i].drive_mappings);
755   }
756   free (g->fses);
757   g->nr_fses = 0;
758   g->fses = NULL;
759 }
760
761 /* In the Perl code this is a public function. */
762 int
763 guestfs___feature_available (guestfs_h *g, const char *feature)
764 {
765   /* If there's an error we should ignore it, so to do that we have to
766    * temporarily replace the error handler with a null one.
767    */
768   guestfs_error_handler_cb old_error_cb = g->error_cb;
769   g->error_cb = NULL;
770
771   const char *groups[] = { feature, NULL };
772   int r = guestfs_available (g, (char * const *) groups);
773
774   g->error_cb = old_error_cb;
775
776   return r == 0 ? 1 : 0;
777 }