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