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