Add support for pkgsrc, default NetBSD package manager.
[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_LINUX: ret = safe_strdup (g, "linux"); break;
167   case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break;
168   case OS_TYPE_FREEBSD: ret = safe_strdup (g, "freebsd"); break;
169   case OS_TYPE_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
170   }
171
172   return ret;
173 }
174
175 char *
176 guestfs__inspect_get_arch (guestfs_h *g, const char *root)
177 {
178   struct inspect_fs *fs = guestfs___search_for_root (g, root);
179   if (!fs)
180     return NULL;
181
182   return safe_strdup (g, fs->arch ? : "unknown");
183 }
184
185 char *
186 guestfs__inspect_get_distro (guestfs_h *g, const char *root)
187 {
188   struct inspect_fs *fs = guestfs___search_for_root (g, root);
189   if (!fs)
190     return NULL;
191
192   char *ret;
193   switch (fs->distro) {
194   case OS_DISTRO_ARCHLINUX: ret = safe_strdup (g, "archlinux"); break;
195   case OS_DISTRO_CENTOS: ret = safe_strdup (g, "centos"); break;
196   case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break;
197   case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break;
198   case OS_DISTRO_GENTOO: ret = safe_strdup (g, "gentoo"); break;
199   case OS_DISTRO_LINUX_MINT: ret = safe_strdup (g, "linuxmint"); break;
200   case OS_DISTRO_MAGEIA: ret = safe_strdup (g, "mageia"); break;
201   case OS_DISTRO_MANDRIVA: ret = safe_strdup (g, "mandriva"); break;
202   case OS_DISTRO_MEEGO: ret = safe_strdup (g, "meego"); break;
203   case OS_DISTRO_OPENSUSE: ret = safe_strdup (g, "opensuse"); 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_TTYLINUX: ret = safe_strdup (g, "ttylinux"); break;
210   case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break;
211   case OS_DISTRO_UBUNTU: ret = safe_strdup (g, "ubuntu"); break;
212   case OS_DISTRO_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
213   }
214
215   return ret;
216 }
217
218 int
219 guestfs__inspect_get_major_version (guestfs_h *g, const char *root)
220 {
221   struct inspect_fs *fs = guestfs___search_for_root (g, root);
222   if (!fs)
223     return -1;
224
225   return fs->major_version;
226 }
227
228 int
229 guestfs__inspect_get_minor_version (guestfs_h *g, const char *root)
230 {
231   struct inspect_fs *fs = guestfs___search_for_root (g, root);
232   if (!fs)
233     return -1;
234
235   return fs->minor_version;
236 }
237
238 char *
239 guestfs__inspect_get_product_name (guestfs_h *g, const char *root)
240 {
241   struct inspect_fs *fs = guestfs___search_for_root (g, root);
242   if (!fs)
243     return NULL;
244
245   return safe_strdup (g, fs->product_name ? : "unknown");
246 }
247
248 char *
249 guestfs__inspect_get_product_variant (guestfs_h *g, const char *root)
250 {
251   struct inspect_fs *fs = guestfs___search_for_root (g, root);
252   if (!fs)
253     return NULL;
254
255   return safe_strdup (g, fs->product_variant ? : "unknown");
256 }
257
258 char *
259 guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
260 {
261   struct inspect_fs *fs = guestfs___search_for_root (g, root);
262   if (!fs)
263     return NULL;
264
265   if (!fs->windows_systemroot) {
266     error (g, _("not a Windows guest, or systemroot could not be determined"));
267     return NULL;
268   }
269
270   return safe_strdup (g, fs->windows_systemroot);
271 }
272
273 char *
274 guestfs__inspect_get_windows_current_control_set (guestfs_h *g,
275                                                   const char *root)
276 {
277   struct inspect_fs *fs = guestfs___search_for_root (g, root);
278   if (!fs)
279     return NULL;
280
281   if (!fs->windows_current_control_set) {
282     error (g, _("not a Windows guest, or CurrentControlSet could not be determined"));
283     return NULL;
284   }
285
286   return safe_strdup (g, fs->windows_current_control_set);
287 }
288
289 char *
290 guestfs__inspect_get_format (guestfs_h *g, const char *root)
291 {
292   struct inspect_fs *fs = guestfs___search_for_root (g, root);
293   if (!fs)
294     return NULL;
295
296   char *ret;
297   switch (fs->format) {
298   case OS_FORMAT_INSTALLED: ret = safe_strdup (g, "installed"); break;
299   case OS_FORMAT_INSTALLER: ret = safe_strdup (g, "installer"); break;
300   case OS_FORMAT_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
301   }
302
303   return ret;
304 }
305
306 int
307 guestfs__inspect_is_live (guestfs_h *g, const char *root)
308 {
309   struct inspect_fs *fs = guestfs___search_for_root (g, root);
310   if (!fs)
311     return -1;
312
313   return fs->is_live_disk;
314 }
315
316 int
317 guestfs__inspect_is_netinst (guestfs_h *g, const char *root)
318 {
319   struct inspect_fs *fs = guestfs___search_for_root (g, root);
320   if (!fs)
321     return -1;
322
323   return fs->is_netinst_disk;
324 }
325
326 int
327 guestfs__inspect_is_multipart (guestfs_h *g, const char *root)
328 {
329   struct inspect_fs *fs = guestfs___search_for_root (g, root);
330   if (!fs)
331     return -1;
332
333   return fs->is_multipart_disk;
334 }
335
336 char **
337 guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
338 {
339   struct inspect_fs *fs = guestfs___search_for_root (g, root);
340   if (!fs)
341     return NULL;
342
343   char **ret;
344
345   /* If no fstab information (Windows) return just the root. */
346   if (fs->nr_fstab == 0) {
347     ret = calloc (3, sizeof (char *));
348     ret[0] = safe_strdup (g, "/");
349     ret[1] = safe_strdup (g, root);
350     ret[2] = NULL;
351     return ret;
352   }
353
354 #define CRITERION fs->fstab[i].mountpoint[0] == '/'
355   size_t i, count = 0;
356   for (i = 0; i < fs->nr_fstab; ++i)
357     if (CRITERION)
358       count++;
359
360   /* Hashtables have 2N+1 entries. */
361   ret = calloc (2*count+1, sizeof (char *));
362   if (ret == NULL) {
363     perrorf (g, "calloc");
364     return NULL;
365   }
366
367   count = 0;
368   for (i = 0; i < fs->nr_fstab; ++i)
369     if (CRITERION) {
370       ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint);
371       ret[2*count+1] = safe_strdup (g, fs->fstab[i].device);
372       count++;
373     }
374 #undef CRITERION
375
376   return ret;
377 }
378
379 char **
380 guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
381 {
382   struct inspect_fs *fs = guestfs___search_for_root (g, root);
383   if (!fs)
384     return NULL;
385
386   char **ret;
387
388   /* If no fstab information (Windows) return just the root. */
389   if (fs->nr_fstab == 0) {
390     ret = calloc (2, sizeof (char *));
391     ret[0] = safe_strdup (g, root);
392     ret[1] = NULL;
393     return ret;
394   }
395
396   ret = calloc (fs->nr_fstab + 1, sizeof (char *));
397   if (ret == NULL) {
398     perrorf (g, "calloc");
399     return NULL;
400   }
401
402   size_t i;
403   for (i = 0; i < fs->nr_fstab; ++i)
404     ret[i] = safe_strdup (g, fs->fstab[i].device);
405
406   return ret;
407 }
408
409 char **
410 guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root)
411 {
412   char **ret;
413   size_t i, count;
414   struct inspect_fs *fs;
415
416   fs = guestfs___search_for_root (g, root);
417   if (!fs)
418     return NULL;
419
420   /* If no drive mappings, return an empty hashtable. */
421   if (!fs->drive_mappings)
422     count = 0;
423   else {
424     for (count = 0; fs->drive_mappings[count] != NULL; count++)
425       ;
426   }
427
428   ret = calloc (count+1, sizeof (char *));
429   if (ret == NULL) {
430     perrorf (g, "calloc");
431     return NULL;
432   }
433
434   /* We need to make a deep copy of the hashtable since the caller
435    * will free it.
436    */
437   for (i = 0; i < count; ++i)
438     ret[i] = safe_strdup (g, fs->drive_mappings[i]);
439
440   ret[count] = NULL;
441
442   return ret;
443 }
444
445 char *
446 guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
447 {
448   struct inspect_fs *fs = guestfs___search_for_root (g, root);
449   if (!fs)
450     return NULL;
451
452   char *ret;
453   switch (fs->package_format) {
454   case OS_PACKAGE_FORMAT_RPM: ret = safe_strdup (g, "rpm"); break;
455   case OS_PACKAGE_FORMAT_DEB: ret = safe_strdup (g, "deb"); break;
456   case OS_PACKAGE_FORMAT_PACMAN: ret = safe_strdup (g, "pacman"); break;
457   case OS_PACKAGE_FORMAT_EBUILD: ret = safe_strdup (g, "ebuild"); break;
458   case OS_PACKAGE_FORMAT_PISI: ret = safe_strdup (g, "pisi"); break;
459   case OS_PACKAGE_FORMAT_PKGSRC: ret = safe_strdup (g, "pkgsrc"); break;
460   case OS_PACKAGE_FORMAT_UNKNOWN:
461   default:
462     ret = safe_strdup (g, "unknown");
463     break;
464   }
465
466   return ret;
467 }
468
469 char *
470 guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
471 {
472   struct inspect_fs *fs = guestfs___search_for_root (g, root);
473   if (!fs)
474     return NULL;
475
476   char *ret;
477   switch (fs->package_management) {
478   case OS_PACKAGE_MANAGEMENT_YUM: ret = safe_strdup (g, "yum"); break;
479   case OS_PACKAGE_MANAGEMENT_UP2DATE: ret = safe_strdup (g, "up2date"); break;
480   case OS_PACKAGE_MANAGEMENT_APT: ret = safe_strdup (g, "apt"); break;
481   case OS_PACKAGE_MANAGEMENT_PACMAN: ret = safe_strdup (g, "pacman"); break;
482   case OS_PACKAGE_MANAGEMENT_PORTAGE: ret = safe_strdup (g, "portage"); break;
483   case OS_PACKAGE_MANAGEMENT_PISI: ret = safe_strdup (g, "pisi"); break;
484   case OS_PACKAGE_MANAGEMENT_URPMI: ret = safe_strdup (g, "urpmi"); break;
485   case OS_PACKAGE_MANAGEMENT_ZYPPER: ret = safe_strdup (g, "zypper"); break;
486   case OS_PACKAGE_MANAGEMENT_UNKNOWN:
487   default:
488     ret = safe_strdup (g, "unknown");
489     break;
490   }
491
492   return ret;
493 }
494
495 char *
496 guestfs__inspect_get_hostname (guestfs_h *g, const char *root)
497 {
498   struct inspect_fs *fs = guestfs___search_for_root (g, root);
499   if (!fs)
500     return NULL;
501
502   return safe_strdup (g, fs->hostname ? : "unknown");
503 }
504
505 /* Download a guest file to a local temporary file.  The file is
506  * cached in the temporary directory, and is not downloaded again.
507  *
508  * The name of the temporary (downloaded) file is returned.  The
509  * caller must free the pointer, but does *not* need to delete the
510  * temporary file.  It will be deleted when the handle is closed.
511  *
512  * Refuse to download the guest file if it is larger than max_size.
513  * On this and other errors, NULL is returned.
514  *
515  * There is actually one cache per 'struct inspect_fs *' in order
516  * to handle the case of multiple roots.
517  */
518 char *
519 guestfs___download_to_tmp (guestfs_h *g, struct inspect_fs *fs,
520                            const char *filename,
521                            const char *basename, int64_t max_size)
522 {
523   char *r;
524   int fd;
525   char devfd[32];
526   int64_t size;
527
528   /* Make the basename unique by prefixing it with the fs number. */
529   if (asprintf (&r, "%s/%ld-%s", g->tmpdir, fs - g->fses, basename) == -1) {
530     perrorf (g, "asprintf");
531     return NULL;
532   }
533
534   /* If the file has already been downloaded, return. */
535   if (access (r, R_OK) == 0)
536     return r;
537
538   /* Check size of remote file. */
539   size = guestfs_filesize (g, filename);
540   if (size == -1)
541     /* guestfs_filesize failed and has already set error in handle */
542     goto error;
543   if (size > max_size) {
544     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
545            filename, size);
546     goto error;
547   }
548
549   fd = open (r, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0600);
550   if (fd == -1) {
551     perrorf (g, "open: %s", r);
552     goto error;
553   }
554
555   snprintf (devfd, sizeof devfd, "/dev/fd/%d", fd);
556
557   if (guestfs_download (g, filename, devfd) == -1) {
558     unlink (r);
559     close (fd);
560     goto error;
561   }
562
563   if (close (fd) == -1) {
564     perrorf (g, "close: %s", r);
565     unlink (r);
566     goto error;
567   }
568
569   return r;
570
571  error:
572   free (r);
573   return NULL;
574 }
575
576 struct inspect_fs *
577 guestfs___search_for_root (guestfs_h *g, const char *root)
578 {
579   if (g->nr_fses == 0) {
580     error (g, _("no inspection data: call guestfs_inspect_os first"));
581     return NULL;
582   }
583
584   size_t i;
585   struct inspect_fs *fs;
586   for (i = 0; i < g->nr_fses; ++i) {
587     fs = &g->fses[i];
588     if (fs->is_root && STREQ (root, fs->device))
589       return fs;
590   }
591
592   error (g, _("%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"),
593          root);
594   return NULL;
595 }
596
597 #else /* no hivex at compile time */
598
599 /* XXX These functions should be in an optgroup. */
600
601 #define NOT_IMPL(r)                                                     \
602   error (g, _("inspection API not available since this version of libguestfs was compiled without the hivex library")); \
603   return r
604
605 char **
606 guestfs__inspect_os (guestfs_h *g)
607 {
608   NOT_IMPL(NULL);
609 }
610
611 char **
612 guestfs__inspect_get_roots (guestfs_h *g)
613 {
614   NOT_IMPL(NULL);
615 }
616
617 char *
618 guestfs__inspect_get_type (guestfs_h *g, const char *root)
619 {
620   NOT_IMPL(NULL);
621 }
622
623 char *
624 guestfs__inspect_get_arch (guestfs_h *g, const char *root)
625 {
626   NOT_IMPL(NULL);
627 }
628
629 char *
630 guestfs__inspect_get_distro (guestfs_h *g, const char *root)
631 {
632   NOT_IMPL(NULL);
633 }
634
635 int
636 guestfs__inspect_get_major_version (guestfs_h *g, const char *root)
637 {
638   NOT_IMPL(-1);
639 }
640
641 int
642 guestfs__inspect_get_minor_version (guestfs_h *g, const char *root)
643 {
644   NOT_IMPL(-1);
645 }
646
647 char *
648 guestfs__inspect_get_product_name (guestfs_h *g, const char *root)
649 {
650   NOT_IMPL(NULL);
651 }
652
653 char *
654 guestfs__inspect_get_product_variant (guestfs_h *g, const char *root)
655 {
656   NOT_IMPL(NULL);
657 }
658
659 char *
660 guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
661 {
662   NOT_IMPL(NULL);
663 }
664
665 char *
666 guestfs__inspect_get_windows_current_control_set (guestfs_h *g,
667                                                   const char *root)
668 {
669   NOT_IMPL(NULL);
670 }
671
672 char **
673 guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
674 {
675   NOT_IMPL(NULL);
676 }
677
678 char **
679 guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
680 {
681   NOT_IMPL(NULL);
682 }
683
684 char **
685 guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root)
686 {
687   NOT_IMPL(NULL);
688 }
689
690 char *
691 guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
692 {
693   NOT_IMPL(NULL);
694 }
695
696 char *
697 guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
698 {
699   NOT_IMPL(NULL);
700 }
701
702 char *
703 guestfs__inspect_get_hostname (guestfs_h *g, const char *root)
704 {
705   NOT_IMPL(NULL);
706 }
707
708 char *
709 guestfs__inspect_get_format (guestfs_h *g, const char *root)
710 {
711   NOT_IMPL(NULL);
712 }
713
714 int
715 guestfs__inspect_is_live (guestfs_h *g, const char *root)
716 {
717   NOT_IMPL(-1);
718 }
719
720 int
721 guestfs__inspect_is_netinst (guestfs_h *g, const char *root)
722 {
723   NOT_IMPL(-1);
724 }
725
726 int
727 guestfs__inspect_is_multipart (guestfs_h *g, const char *root)
728 {
729   NOT_IMPL(-1);
730 }
731
732 #endif /* no hivex at compile time */
733
734 void
735 guestfs___free_inspect_info (guestfs_h *g)
736 {
737   size_t i;
738   for (i = 0; i < g->nr_fses; ++i) {
739     free (g->fses[i].device);
740     free (g->fses[i].product_name);
741     free (g->fses[i].product_variant);
742     free (g->fses[i].arch);
743     free (g->fses[i].hostname);
744     free (g->fses[i].windows_systemroot);
745     free (g->fses[i].windows_current_control_set);
746     size_t j;
747     for (j = 0; j < g->fses[i].nr_fstab; ++j) {
748       free (g->fses[i].fstab[j].device);
749       free (g->fses[i].fstab[j].mountpoint);
750     }
751     free (g->fses[i].fstab);
752     if (g->fses[i].drive_mappings)
753       guestfs___free_string_list (g->fses[i].drive_mappings);
754   }
755   free (g->fses);
756   g->nr_fses = 0;
757   g->fses = NULL;
758 }
759
760 /* In the Perl code this is a public function. */
761 int
762 guestfs___feature_available (guestfs_h *g, const char *feature)
763 {
764   /* If there's an error we should ignore it, so to do that we have to
765    * temporarily replace the error handler with a null one.
766    */
767   guestfs_error_handler_cb old_error_cb = g->error_cb;
768   g->error_cb = NULL;
769
770   const char *groups[] = { feature, NULL };
771   int r = guestfs_available (g, (char * const *) groups);
772
773   g->error_cb = old_error_cb;
774
775   return r == 0 ? 1 : 0;
776 }