The first three patches were posted previously: https://www.redhat.com/archives/libguestfs/2010-July/msg00082.html The last two patches in this series change guestfish -i to use this new code. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
Richard W.M. Jones
2010-Aug-02 17:12 UTC
[Libguestfs] [PATCH v3 1/5] New API: file-architecture
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 647be56b2d8b171a33608b36e0b7557d94db3c96 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 28 Jul 2010 15:38:57 +0100 Subject: [PATCH 1/5] New API: file-architecture This change simply converts the existing Perl-only function file_architecture into a core API call. The core API call is written in C and available in all languages and from guestfish. --- README | 2 + configure.ac | 9 ++ perl/lib/Sys/Guestfs/Lib.pm | 147 +----------------------- perl/t/510-lib-file-arch.t | 70 ----------- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/generator.ml | 128 +++++++++++++++++++++ src/inspect.c | 266 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 411 insertions(+), 215 deletions(-) delete mode 100644 perl/t/510-lib-file-arch.t create mode 100644 src/inspect.c diff --git a/README b/README index ea1da1f..e542073 100644 --- a/README +++ b/README @@ -48,6 +48,8 @@ Requirements - XDR, rpcgen (on Linux these are provided by glibc) +- pcre (Perl Compatible Regular Expressions C library) + - squashfs-tools (mksquashfs only) - genisoimage / mkisofs diff --git a/configure.ac b/configure.ac index a14dfd9..9cb2278 100644 --- a/configure.ac +++ b/configure.ac @@ -185,6 +185,15 @@ AC_ARG_ENABLE([appliance], AM_CONDITIONAL([ENABLE_APPLIANCE],[test "x$enable_appliance" = "xyes"]) AC_MSG_RESULT([$enable_appliance]) +dnl Check for PCRE. +AC_CHECK_LIB([pcre],[pcre_compile], + [AC_SUBST([LIBPCRE], ["-lpcre"])], + [AC_MSG_FAILURE( + [Perl Compatible Regular Expressions library (PCRE) is required])]) +AC_CHECK_HEADER([pcre.h],[], + [AC_MSG_FAILURE( + [Perl Compatible Regular Expressions library (PCRE) header file pcre.h is required])]) + dnl Check for rpcgen and XDR library. rpcgen is optional. AC_CHECK_PROG([RPCGEN],[rpcgen],[rpcgen],[no]) AM_CONDITIONAL([HAVE_RPCGEN],[test "x$RPCGEN" != "xno"]) diff --git a/perl/lib/Sys/Guestfs/Lib.pm b/perl/lib/Sys/Guestfs/Lib.pm index bdc788e..bb97506 100644 --- a/perl/lib/Sys/Guestfs/Lib.pm +++ b/perl/lib/Sys/Guestfs/Lib.pm @@ -347,159 +347,18 @@ sub resolve_windows_path =head2 file_architecture - $arch = file_architecture ($g, $path) +Deprecated function. Replace any calls to this function with: -The C<file_architecture> function lets you get the architecture for a -particular binary or library in the guest. By "architecture" we mean -what processor it is compiled for (eg. C<i586> or C<x86_64>). - -The function works on at least the following types of files: - -=over 4 - -=item * - -many types of Un*x binary - -=item * - -many types of Un*x shared library - -=item * - -Windows Win32 and Win64 binaries - -=item * - -Windows Win32 and Win64 DLLs - -Win32 binaries and DLLs return C<i386>. - -Win64 binaries and DLLs return C<x86_64>. - -=item * - -Linux kernel modules - -=item * - -Linux new-style initrd images - -=item * - -some non-x86 Linux vmlinuz kernels - -=back - -What it can't do currently: - -=over 4 - -=item * - -static libraries (libfoo.a) - -=item * - -Linux old-style initrd as compressed ext2 filesystem (RHEL 3) - -=item * - -x86 Linux vmlinuz kernels - -x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and -compressed code, and are horribly hard to unpack. If you want to find -the architecture of a kernel, use the architecture of the associated -initrd or kernel module(s) instead. - -=back + $g->file_architecture ($path); =cut -sub _elf_arch_to_canonical -{ - local $_ = shift; - - if ($_ eq "Intel 80386") { - return "i386"; - } elsif ($_ eq "Intel 80486") { - return "i486"; # probably not in the wild - } elsif ($_ eq "x86-64") { - return "x86_64"; - } elsif ($_ eq "AMD x86-64") { - return "x86_64"; - } elsif (/SPARC32/) { - return "sparc"; - } elsif (/SPARC V9/) { - return "sparc64"; - } elsif ($_ eq "IA-64") { - return "ia64"; - } elsif (/64.*PowerPC/) { - return "ppc64"; - } elsif (/PowerPC/) { - return "ppc"; - } else { - warn __x("returning non-canonical architecture type '{arch}'", - arch => $_); - return $_; - } -} - -my @_initrd_binaries = ("nash", "modprobe", "sh", "bash"); - sub file_architecture { - local $_; my $g = shift; my $path = shift; - # Our basic tool is 'file' ... - my $file = $g->file ($path); - - if ($file =~ /ELF.*(?:executable|shared object|relocatable), (.+?),/) { - # ELF executable or shared object. We need to convert - # what file(1) prints into the canonical form. - return _elf_arch_to_canonical ($1); - } elsif ($file =~ /PE32 executable/) { - return "i386"; # Win32 executable or DLL - } elsif ($file =~ /PE32\+ executable/) { - return "x86_64"; # Win64 executable or DLL - } - - elsif ($file =~ /cpio archive/) { - # Probably an initrd. - my $zcat = "cat"; - if ($file =~ /gzip/) { - $zcat = "zcat"; - } elsif ($file =~ /bzip2/) { - $zcat = "bzcat"; - } - - # Download and unpack it to find a binary file. - my $dir = tempdir (CLEANUP => 1); - $g->download ($path, "$dir/initrd"); - - my $bins = join " ", map { "bin/$_" } @_initrd_binaries; - my $cmd = "cd $dir && $zcat initrd | cpio --quiet -id $bins"; - my $r = system ($cmd); - die __x("cpio command failed: {error}", error => $?) - unless $r == 0; - - foreach my $bin (@_initrd_binaries) { - if (-f "$dir/bin/$bin") { - $_ = `file $dir/bin/$bin`; - if (/ELF.*executable, (.+?),/) { - return _elf_arch_to_canonical ($1); - } - } - } - - die __x("file_architecture: no known binaries found in initrd image: {path}", - path => $path); - } - - die __x("file_architecture: unknown architecture: {path}", - path => $path); + return $g->file_architecture ($path); } =head1 OPERATING SYSTEM INSPECTION FUNCTIONS diff --git a/perl/t/510-lib-file-arch.t b/perl/t/510-lib-file-arch.t deleted file mode 100644 index dfe32bc..0000000 --- a/perl/t/510-lib-file-arch.t +++ /dev/null @@ -1,70 +0,0 @@ -# libguestfs Perl bindings -*- perl -*- -# Copyright (C) 2009 Red Hat Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -use strict; -use warnings; - -BEGIN { - use Test::More; - eval "use Locale::TextDomain";; - if (exists $INC{"Locale/TextDomain.pm"}) { - plan tests => 16; - } else { - plan skip_all => "no perl-libintl module"; - exit 0; - } -} - -use Sys::Guestfs; -use Sys::Guestfs::Lib; - -my $h = Sys::Guestfs->new (); -ok ($h); - -$h->add_drive_ro ("../images/test.iso"); -ok (1); - -$h->launch (); -ok (1); - -$h->mount_ro ("/dev/sda", "/"); -ok (1); - -is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-i586-dynamic"), - "i386"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-sparc-dynamic"), - "sparc"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-win32.exe"), - "i386"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-win64.exe"), - "x86_64"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-x86_64-dynamic"), - "x86_64"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-i586.so"), - "i386"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-sparc.so"), - "sparc"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-win32.dll"), - "i386"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-win64.dll"), - "x86_64"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-x86_64.so"), - "x86_64"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/initrd-x86_64.img"), - "x86_64"); -is (Sys::Guestfs::Lib::file_architecture ($h, "/initrd-x86_64.img.gz"), - "x86_64"); diff --git a/po/POTFILES.in b/po/POTFILES.in index fdc2b70..bf066ea 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -102,6 +102,7 @@ ruby/ext/guestfs/_guestfs.c src/actions.c src/bindtests.c src/guestfs.c +src/inspect.c src/launch.c src/proto.c test-tool/helper.c diff --git a/src/Makefile.am b/src/Makefile.am index 4135c8c..9582c78 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -126,11 +126,12 @@ libguestfs_la_SOURCES = \ gettext.h \ actions.c \ bindtests.c \ + inspect.c \ launch.c \ proto.c \ libguestfs.syms -libguestfs_la_LIBADD = $(LTLIBTHREAD) ../gnulib/lib/libgnu.la +libguestfs_la_LIBADD = $(LIBPCRE) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la # Make libguestfs include the convenience library. noinst_LTLIBRARIES = libprotocol.la diff --git a/src/generator.ml b/src/generator.ml index 52e7aba..2b9bc13 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -940,6 +940,134 @@ to specify the QEMU interface emulation to use at run time."); This is the same as C<guestfs_add_drive_ro> but it allows you to specify the QEMU interface emulation to use at run time."); + ("file_architecture", (RString "arch", [Pathname "filename"]), -1, [], + [InitISOFS, Always, TestOutput ( + [["file_architecture"; "/bin-i586-dynamic"]], "i386"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/bin-sparc-dynamic"]], "sparc"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/bin-win32.exe"]], "i386"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/bin-win64.exe"]], "x86_64"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/bin-x86_64-dynamic"]], "x86_64"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/lib-i586.so"]], "i386"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/lib-sparc.so"]], "sparc"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/lib-win32.dll"]], "i386"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/lib-win64.dll"]], "x86_64"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/lib-x86_64.so"]], "x86_64"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/initrd-x86_64.img"]], "x86_64"); + InitISOFS, Always, TestOutput ( + [["file_architecture"; "/initrd-x86_64.img.gz"]], "x86_64");], + "detect the architecture of a binary file", + "\ +This detects the architecture of the binary C<filename>, +and returns it if known. + +Currently defined architectures are: + +=over 4 + +=item \"i386\" + +This string is returned for all 32 bit i386, i486, i586, i686 binaries +irrespective of the precise processor requirements of the binary. + +=item \"x86_64\" + +64 bit x86-64. + +=item \"sparc\" + +32 bit SPARC. + +=item \"sparc64\" + +64 bit SPARC V9 and above. + +=item \"ia64\" + +Intel Itanium. + +=item \"ppc\" + +32 bit Power PC. + +=item \"ppc64\" + +64 bit Power PC. + +=back + +Libguestfs may return other architecture strings in future. + +The function works on at least the following types of files: + +=over 4 + +=item * + +many types of Un*x and Linux binary + +=item * + +many types of Un*x and Linux shared library + +=item * + +Windows Win32 and Win64 binaries + +=item * + +Windows Win32 and Win64 DLLs + +Win32 binaries and DLLs return C<i386>. + +Win64 binaries and DLLs return C<x86_64>. + +=item * + +Linux kernel modules + +=item * + +Linux new-style initrd images + +=item * + +some non-x86 Linux vmlinuz kernels + +=back + +What it can't do currently: + +=over 4 + +=item * + +static libraries (libfoo.a) + +=item * + +Linux old-style initrd as compressed ext2 filesystem (RHEL 3) + +=item * + +x86 Linux vmlinuz kernels + +x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and +compressed code, and are horribly hard to unpack. If you want to find +the architecture of a kernel, use the architecture of the associated +initrd or kernel module(s) instead. + +=back"); + ] (* daemon_functions are any functions which cause some action diff --git a/src/inspect.c b/src/inspect.c new file mode 100644 index 0000000..8e28d8a --- /dev/null +++ b/src/inspect.c @@ -0,0 +1,266 @@ +/* libguestfs + * Copyright (C) 2010 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <unistd.h> +#include <string.h> +#include <sys/stat.h> + +#include <pcre.h> + +#include "ignore-value.h" + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "guestfs-internal-actions.h" +#include "guestfs_protocol.h" + +/* Compile all the regular expressions once when the shared library is + * loaded. PCRE is thread safe so we're supposedly OK here if + * multiple threads call into the libguestfs API functions below + * simultaneously. + */ +static pcre *re_file_elf; +static pcre *re_file_win64; +static pcre *re_elf_ppc64; + +static void compile_regexps (void) __attribute__((constructor)); +static void +compile_regexps (void) +{ + const char *err; + int offset; + +#define COMPILE(re,pattern,options) \ + do { \ + re = pcre_compile ((pattern), (options), &err, &offset, NULL); \ + if (re == NULL) { \ + ignore_value (write (2, err, strlen (err))); \ + abort (); \ + } \ + } while (0) + + COMPILE (re_file_elf, + "ELF.*(?:executable|shared object|relocatable), (.+?),", 0); + COMPILE (re_file_win64, "PE32\\+ executable", 0); + COMPILE (re_elf_ppc64, "64.*PowerPC", 0); +} + +/* Match a regular expression which contains no captures. Returns + * true if it matches or false if it doesn't. + */ +static int +match (const char *str, const pcre *re) +{ + size_t len = strlen (str); + int vec[30], r; + + r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30); + if (r == PCRE_ERROR_NOMATCH) + return 0; + if (r != 1) { + /* Internal error -- should not happen. */ + fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + __FILE__, __func__, r, str); + return 0; + } + + return 1; +} + +/* Match a regular expression which contains exactly one capture. If + * the string matches, return the capture, otherwise return NULL. The + * caller must free the result. + */ +static char * +match1 (const char *str, const pcre *re) +{ + size_t len = strlen (str); + int vec[30], r; + + r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30); + if (r == PCRE_ERROR_NOMATCH) + return NULL; + if (r != 2) { + /* Internal error -- should not happen. */ + fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + __FILE__, __func__, r, str); + return NULL; + } + + return strndup (&str[vec[2]], vec[3]-vec[2]); +} + +/* Convert output from 'file' command on ELF files to the canonical + * architecture string. Caller must free the result. + */ +static char * +canonical_elf_arch (guestfs_h *g, const char *elf_arch) +{ + const char *r; + + if (strstr (elf_arch, "Intel 80386")) + r = "i386"; + else if (strstr (elf_arch, "Intel 80486")) + r = "i486"; + else if (strstr (elf_arch, "x86-64")) + r = "x86_64"; + else if (strstr (elf_arch, "AMD x86-64")) + r = "x86_64"; + else if (strstr (elf_arch, "SPARC32")) + r = "sparc"; + else if (strstr (elf_arch, "SPARC V9")) + r = "sparc64"; + else if (strstr (elf_arch, "IA-64")) + r = "ia64"; + else if (match (elf_arch, re_elf_ppc64)) + r = "ppc64"; + else if (strstr (elf_arch, "PowerPC")) + r = "ppc"; + else + r = elf_arch; + + char *ret = safe_strdup (g, r); + return ret; +} + +static int +is_regular_file (const char *filename) +{ + struct stat statbuf; + + return lstat (filename, &statbuf) == 0 && S_ISREG (statbuf.st_mode); +} + +/* Download and uncompress the cpio file to find binaries within. */ +#define INITRD_BINARIES1 "bin/ls bin/rm bin/modprobe sbin/modprobe bin/sh bin/bash bin/dash bin/nash" +#define INITRD_BINARIES2 {"bin/ls", "bin/rm", "bin/modprobe", "sbin/modprobe", "bin/sh", "bin/bash", "bin/dash", "bin/nash"} + +static char * +cpio_arch (guestfs_h *g, const char *file, const char *path) +{ + char *ret = NULL; + + const char *method; + if (strstr (file, "gzip")) + method = "zcat"; + else if (strstr (file, "bzip2")) + method = "bzcat"; + else + method = "cat"; + + char dir[] = "/tmp/initrd.XXXXXX"; +#define dir_len 18 + if (mkdtemp (dir) == NULL) { + perrorf (g, "mkdtemp"); + goto out; + } + + char dir_initrd[dir_len + 16]; + snprintf (dir_initrd, dir_len + 16, "%s/initrd", dir); + if (guestfs_download (g, path, dir_initrd) == -1) + goto out; + + char cmd[dir_len + 256]; + snprintf (cmd, dir_len + 256, + "cd %s && %s initrd | cpio --quiet -id " INITRD_BINARIES1, + dir, method); + int r = system (cmd); + if (r == -1 || WEXITSTATUS (r) != 0) { + perrorf (g, "cpio command failed"); + goto out; + } + + char bin[dir_len + 32]; + const char *bins[] = INITRD_BINARIES2; + size_t i; + for (i = 0; i < sizeof bins / sizeof bins[0]; ++i) { + snprintf (bin, dir_len + 32, "%s/%s", dir, bins[i]); + + if (is_regular_file (bin)) { + snprintf (cmd, dir_len + 256, "file %s", bin); + FILE *fp = popen (cmd, "r"); + if (!fp) { + perrorf (g, "popen: %s", cmd); + goto out; + } + + char line[1024]; + if (!fgets (line, sizeof line, fp)) { + perrorf (g, "fgets"); + fclose (fp); + goto out; + } + pclose (fp); + + char *elf_arch; + if ((elf_arch = match1 (line, re_file_elf)) != NULL) { + ret = canonical_elf_arch (g, elf_arch); + free (elf_arch); + goto out; + } + } + } + error (g, "file_architecture: could not determine architecture of cpio archive"); + + out: + /* Free up the temporary directory. Note the directory name cannot + * contain shell meta-characters because of the way it was + * constructed above. + */ + snprintf (cmd, dir_len + 256, "rm -rf %s", dir); + ignore_value (system (cmd)); + + return ret; +#undef dir_len +} + +char * +guestfs__file_architecture (guestfs_h *g, const char *path) +{ + char *file = NULL; + char *elf_arch = NULL; + char *ret = NULL; + + /* Get the output of the "file" command. Note that because this + * runs in the daemon, LANG=C so it's in English. + */ + file = guestfs_file (g, path); + if (file == NULL) + return NULL; + + if ((elf_arch = match1 (file, re_file_elf)) != NULL) + ret = canonical_elf_arch (g, elf_arch); + else if (strstr (file, "PE32 executable")) + ret = safe_strdup (g, "i386"); + else if (match (file, re_file_win64)) + ret = safe_strdup (g, "x86_64"); + else if (strstr (file, "cpio archive")) + ret = cpio_arch (g, file, path); + else + error (g, "file_architecture: unknown architecture: %s", path); + + free (file); + free (elf_arch); + return ret; /* caller frees */ +} -- 1.7.1
Richard W.M. Jones
2010-Aug-02 17:13 UTC
[Libguestfs] [PATCH v3 2/5] New APIs: findfs-label and findfs-uuid
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top -------------- next part -------------->From 8d6e43c1cceaf8fbbe26f0c81ecdd98255b5b9d7 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 28 Jul 2010 23:11:38 +0100 Subject: [PATCH 2/5] New APIs: findfs-label and findfs-uuid These two calls wrap up the /sbin/findfs command, allowing you to find a filesystem by only knowing its label or UUID. This is especially useful when resolving LABEL=... or UUID=... entries in /etc/fstab. Sample guestfish session:><fs> vfs-uuid /dev/vda1277dd61c-bf34-4253-a8dc-df500a05e7df><fs> findfs-uuid 277dd61c-bf34-4253-a8dc-df500a05e7df/dev/vda1><fs> vfs-label /dev/vda1/boot><fs> findfs-label /boot/dev/vda1><fs> vfs-uuid /dev/VolGroup00/LogVol0040ce7c36-82ce-4a12-a99d-48f5e054162c><fs> findfs-uuid 40ce7c36-82ce-4a12-a99d-48f5e054162c/dev/mapper/VolGroup00-LogVol00><fs> findfs-uuid 12345678libguestfs: error: findfs_uuid: findfs: unable to resolve 'UUID=12345678' --- daemon/Makefile.am | 1 + daemon/findfs.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + src/MAX_PROC_NR | 2 +- src/generator.ml | 28 ++++++++++++++++++- 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 daemon/findfs.c diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 27fca2a..0c8be08 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -84,6 +84,7 @@ guestfsd_SOURCES = \ ext2.c \ fallocate.c \ file.c \ + findfs.c \ fill.c \ find.c \ fsck.c \ diff --git a/daemon/findfs.c b/daemon/findfs.c new file mode 100644 index 0000000..0520f18 --- /dev/null +++ b/daemon/findfs.c @@ -0,0 +1,72 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2010 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "daemon.h" +#include "actions.h" + +static char * +findfs (const char *tag, const char *label_or_uuid) +{ + /* Kill the cache file, forcing blkid to reread values from the + * original filesystems. In blkid there is a '-p' option which is + * supposed to do this, but (a) it doesn't work and (b) that option + * is not supported in RHEL 5. + */ + unlink ("/etc/blkid/blkid.tab"); + + size_t len = strlen (tag) + strlen (label_or_uuid) + 2; + char arg[len]; + snprintf (arg, len, "%s=%s", tag, label_or_uuid); + + char *out, *err; + int r = command (&out, &err, "findfs", arg, NULL); + if (r == -1) { + reply_with_error ("%s", err); + free (out); + free (err); + return NULL; + } + + free (err); + + /* Trim trailing \n if present. */ + len = strlen (out); + if (len > 0 && out[len-1] == '\n') + out[len-1] = '\0'; + + return out; /* caller frees */ +} + +char * +do_findfs_uuid (const char *uuid) +{ + return findfs ("UUID", uuid); +} + +char * +do_findfs_label (const char *label) +{ + return findfs ("LABEL", label); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index bf066ea..8ce5c97 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -21,6 +21,7 @@ daemon/fallocate.c daemon/file.c daemon/fill.c daemon/find.c +daemon/findfs.c daemon/fsck.c daemon/glob.c daemon/grep.c diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index 10b0c0d..c1d1ffb 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -264 +266 diff --git a/src/generator.ml b/src/generator.ml index 2b9bc13..13de698 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -4952,7 +4952,9 @@ a file in the host and attach it as a device."); This returns the filesystem label of the filesystem on C<device>. -If the filesystem is unlabeled, this returns the empty string."); +If the filesystem is unlabeled, this returns the empty string. + +To find a filesystem from the label, use C<guestfs_findfs_label>."); ("vfs_uuid", (RString "uuid", [Device "device"]), 254, [], (let uuid = uuidgen () in @@ -4964,7 +4966,9 @@ If the filesystem is unlabeled, this returns the empty string."); This returns the filesystem UUID of the filesystem on C<device>. -If the filesystem does not have a UUID, this returns the empty string."); +If the filesystem does not have a UUID, this returns the empty string. + +To find a filesystem from the UUID, use C<guestfs_findfs_uuid>."); ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"], (* Can't be tested with the current framework because @@ -5091,6 +5095,26 @@ I<other> keys."); This command tests whether C<device> is a logical volume, and returns true iff this is the case."); + ("findfs_uuid", (RString "device", [String "uuid"]), 265, [], + [], + "find a filesystem by UUID", + "\ +This command searches the filesystems and returns the one +which has the given UUID. An error is returned if no such +filesystem can be found. + +To find the UUID of a filesystem, use C<guestfs_vfs_uuid>."); + + ("findfs_label", (RString "device", [String "label"]), 266, [], + [], + "find a filesystem by label", + "\ +This command searches the filesystems and returns the one +which has the given label. An error is returned if no such +filesystem can be found. + +To find the label of a filesystem, use C<guestfs_vfs_label>."); + ] let all_functions = non_daemon_functions @ daemon_functions -- 1.7.1
Richard W.M. Jones
2010-Aug-02 17:13 UTC
[Libguestfs] [PATCH v3 3/5] New APIs for guest inspection.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 7c6e57ea9bf1aa3cfb921ea17ba32e09853ac930 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 28 Jul 2010 15:40:42 +0100 Subject: [PATCH 3/5] New APIs for guest inspection. This commit converts (some of) the Perl inspection code to C and makes it available through core APIs. The new APIs are: inspect-os - Does the inspection, returns list of OSes inspect-get-* - Get results of the inspection where '*' is one of: type - 'windows' or 'linux' distro - Linux distro arch - architecture product-name - long product name string major-version minor-version - major.minor version of OS mountpoints - get a list of the mountpoints filesystems - get all filesystems associated with the OS This works for all existing supported Linux and Windows OSes. --- src/Makefile.am | 3 +- src/generator.ml | 221 +++++++++++ src/guestfs-internal.h | 54 +++ src/guestfs.c | 2 + src/guestfs.pod | 68 +++- src/inspect.c | 995 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1338 insertions(+), 5 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 9582c78..6b6239c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -131,7 +131,7 @@ libguestfs_la_SOURCES = \ proto.c \ libguestfs.syms -libguestfs_la_LIBADD = $(LIBPCRE) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la +libguestfs_la_LIBADD = $(HIVEX_LIBS) $(LIBPCRE) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la # Make libguestfs include the convenience library. noinst_LTLIBRARIES = libprotocol.la @@ -139,6 +139,7 @@ libguestfs_la_LIBADD += libprotocol.la libguestfs_la_CFLAGS = \ -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \ + $(HIVEX_CFLAGS) \ $(WARN_CFLAGS) $(WERROR_CFLAGS) libguestfs_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib diff --git a/src/generator.ml b/src/generator.ml index 13de698..215df30 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -1068,6 +1068,227 @@ initrd or kernel module(s) instead. =back"); + ("inspect_os", (RStringList "roots", []), -1, [], + [], + "inspect disk and return list of operating systems found", + "\ +This function uses other libguestfs functions and certain +heuristics to inspect the disk(s) (usually disks belonging to +a virtual machine), looking for operating systems. + +The list returned is empty if no operating systems were found. + +If one operating system was found, then this returns a list with +a single element, which is the name of the root filesystem of +this operating system. It is also possible for this function +to return a list containing more than one element, indicating +a dual-boot or multi-boot virtual machine, with each element being +the root filesystem of one of the operating systems. + +You can pass the root string(s) returned to other +C<guestfs_inspect_get_*> functions in order to query further +information about each operating system, such as the name +and version. + +This function uses other libguestfs features such as +C<guestfs_mount_ro> and C<guestfs_umount_all> in order to mount +and unmount filesystems and look at the contents. This should +be called with no disks currently mounted. The function may also +use Augeas, so any existing Augeas handle will be closed. + +This function cannot decrypt encrypted disks. The caller +must do that first (supplying the necessary keys) if the +disk is encrypted. + +Please read L<guestfs(3)/INSPECTION> for more details."); + + ("inspect_get_type", (RString "name", [Device "root"]), -1, [], + [], + "get type of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns the type of the inspected operating system. +Currently defined types are: + +=over 4 + +=item \"linux\" + +Any Linux-based operating system. + +=item \"windows\" + +Any Microsoft Windows operating system. + +=item \"unknown\" + +The operating system type could not be determined. + +=back + +Future versions of libguestfs may return other strings here. +The caller should be prepared to handle any string. + +Please read L<guestfs(3)/INSPECTION> for more details."); + + ("inspect_get_arch", (RString "arch", [Device "root"]), -1, [], + [], + "get architecture of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns the architecture of the inspected operating system. +The possible return values are listed under +C<guestfs_file_architecture>. + +If the architecture could not be determined, then the +string C<unknown> is returned. + +Please read L<guestfs(3)/INSPECTION> for more details."); + + ("inspect_get_distro", (RString "distro", [Device "root"]), -1, [], + [], + "get distro of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns the distro (distribution) of the inspected operating +system. + +Currently defined distros are: + +=over 4 + +=item \"debian\" + +Debian or a Debian-derived distro such as Ubuntu. + +=item \"fedora\" + +Fedora. + +=item \"redhat-based\" + +Some Red Hat-derived distro. + +=item \"rhel\" + +Red Hat Enterprise Linux and some derivatives. + +=item \"windows\" + +Windows does not have distributions. This string is +returned if the OS type is Windows. + +=item \"unknown\" + +The distro could not be determined. + +=back + +Future versions of libguestfs may return other strings here. +The caller should be prepared to handle any string. + +Please read L<guestfs(3)/INSPECTION> for more details."); + + ("inspect_get_major_version", (RInt "major", [Device "root"]), -1, [], + [], + "get major version of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns the major version number of the inspected operating +system. + +Windows uses a consistent versioning scheme which is I<not> +reflected in the popular public names used by the operating system. +Notably the operating system known as \"Windows 7\" is really +version 6.1 (ie. major = 6, minor = 1). You can find out the +real versions corresponding to releases of Windows by consulting +Wikipedia or MSDN. + +If the version could not be determined, then C<0> is returned. + +Please read L<guestfs(3)/INSPECTION> for more details."); + + ("inspect_get_minor_version", (RInt "minor", [Device "root"]), -1, [], + [], + "get minor version of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns the minor version number of the inspected operating +system. + +If the version could not be determined, then C<0> is returned. + +Please read L<guestfs(3)/INSPECTION> for more details. +See also C<guestfs_inspect_get_major_version>."); + + ("inspect_get_product_name", (RString "product", [Device "root"]), -1, [], + [], + "get product name of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns the product name of the inspected operating +system. The product name is generally some freeform string +which can be displayed to the user, but should not be +parsed by programs. + +If the product name could not be determined, then the +string C<unknown> is returned. + +Please read L<guestfs(3)/INSPECTION> for more details."); + + ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"]), -1, [], + [], + "get mountpoints of inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns a hash of where we think the filesystems +associated with this operating system should be mounted. +Callers should note that this is at best an educated guess +made by reading configuration files such as C</etc/fstab>. + +Each element in the returned hashtable has a key which +is the path of the mountpoint (eg. C</boot>) and a value +which is the filesystem that would be mounted there +(eg. C</dev/sda1>). + +Non-mounted devices such as swap devices are I<not> +returned in this list. + +Please read L<guestfs(3)/INSPECTION> for more details. +See also C<guestfs_inspect_get_filesystems>."); + + ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"]), -1, [], + [], + "get filesystems associated with inspected operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +This returns a list of all the filesystems that we think +are associated with this operating system. This includes +the root filesystem, other ordinary filesystems, and +non-mounted devices like swap partitions. + +In the case of a multi-boot virtual machine, it is possible +for a filesystem to be shared between operating systems. + +Please read L<guestfs(3)/INSPECTION> for more details. +See also C<guestfs_inspect_get_mountpoints>."); + ] (* daemon_functions are any functions which cause some action diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 12ca0ec..a4ef215 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -131,6 +131,59 @@ struct guestfs_h void * close_cb_data; int msg_next_serial; + + /* Information gathered by inspect_os. Must be freed by calling + * guestfs___free_inspect_info. + */ + struct inspect_fs *fses; + size_t nr_fses; +}; + +/* Per-filesystem data stored for inspect_os. */ +enum inspect_fs_content { + FS_CONTENT_UNKNOWN = 0, + FS_CONTENT_LINUX_ROOT, + FS_CONTENT_WINDOWS_ROOT, + FS_CONTENT_LINUX_BOOT, + FS_CONTENT_LINUX_USR, + FS_CONTENT_LINUX_USR_LOCAL, + FS_CONTENT_LINUX_VAR, +}; + +enum inspect_os_type { + OS_TYPE_UNKNOWN = 0, + OS_TYPE_LINUX, + OS_TYPE_WINDOWS, +}; + +enum inspect_os_distro { + OS_DISTRO_UNKNOWN = 0, + OS_DISTRO_DEBIAN, + OS_DISTRO_FEDORA, + OS_DISTRO_REDHAT_BASED, + OS_DISTRO_RHEL, + OS_DISTRO_WINDOWS, +}; + +struct inspect_fs { + int is_root; + char *device; + int is_mountable; + int is_swap; + enum inspect_fs_content content; + enum inspect_os_type type; + enum inspect_os_distro distro; + char *product_name; + int major_version; + int minor_version; + char *arch; + struct inspect_fstab_entry *fstab; + size_t nr_fstab; +}; + +struct inspect_fstab_entry { + char *device; + char *mountpoint; }; struct guestfs_message_header; @@ -142,6 +195,7 @@ extern void guestfs_perrorf (guestfs_h *g, const char *fs, ...) extern void *guestfs_safe_realloc (guestfs_h *g, void *ptr, int nbytes); extern char *guestfs_safe_strdup (guestfs_h *g, const char *str); extern void *guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size); +extern void guestfs___free_inspect_info (guestfs_h *g); extern int guestfs___set_busy (guestfs_h *g); extern int guestfs___end_busy (guestfs_h *g); extern int guestfs___send (guestfs_h *g, int proc_nr, xdrproc_t xdrp, char *args); diff --git a/src/guestfs.c b/src/guestfs.c index c54462d..b12940d 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -184,6 +184,8 @@ guestfs_close (guestfs_h *g) if (g->close_cb) g->close_cb (g, g->close_cb_data); + guestfs___free_inspect_info (g); + /* Try to sync if autosync flag is set. */ if (g->autosync && g->state == READY) { guestfs_umount_all (g); diff --git a/src/guestfs.pod b/src/guestfs.pod index 5a2e7a5..5deccb5 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -160,9 +160,10 @@ you have to find out. Libguestfs can do that too: use L</guestfs_list_partitions> and L</guestfs_lvs> to list possible partitions and LVs, and either try mounting each to see what is mountable, or else examine them with L</guestfs_vfs_type> or -L</guestfs_file>. But you might find it easier to look at higher level -programs built on top of libguestfs, in particular -L<virt-inspector(1)>. +L</guestfs_file>. Libguestfs also has a set of APIs for inspection of +disk images (see L</INSPECTION> below). But you might find it easier +to look at higher level programs built on top of libguestfs, in +particular L<virt-inspector(1)>. To mount a disk image read-only, use L</guestfs_mount_ro>. There are several other variations of the C<guestfs_mount_*> call. @@ -481,6 +482,65 @@ Then close the mapper device by calling L</guestfs_luks_close> on the C</dev/mapper/mapname> device (I<not> the underlying encrypted block device). +=head2 INSPECTION + +Libguestfs has APIs for inspecting an unknown disk image to find out +if it contains operating systems. (These APIs used to be in a +separate Perl-only library called L<Sys::Guestfs::Lib(3)> but since +version 1.5.3 the most frequently used part of this library has been +rewritten in C and moved into the core code). + +Add all disks belonging to the unknown virtual machine and call +L</guestfs_launch> in the usual way. + +Then call L</guestfs_inspect_os>. This function uses other libguestfs +calls and certain heuristics, and returns a list of operating systems +that were found. An empty list means none were found. A single +element is the root filesystem of the operating system. For dual- or +multi-boot guests, multiple roots can be returned, each one +corresponding to a separate operating system. (Multi-boot virtual +machines are extremely rare in the world of virtualization, but since +this scenario can happen, we have built libguestfs to deal with it.) + +For each root, you can then call various C<guestfs_inspect_get_*> +functions to get additional details about that operating system. For +example, call L</guestfs_inspect_get_type> to return the string +C<windows> or C<linux> for Windows and Linux-based operating systems +respectively. + +Un*x-like and Linux-based operating systems usually consist of several +filesystems which are mounted at boot time (for example, a separate +boot partition mounted on C</boot>). The inspection rules are able to +detect how filesystems correspond to mount points. Call +C<guestfs_inspect_get_mountpoints> to get this mapping. It might +return a hash table like this example: + + /boot => /dev/sda1 + / => /dev/vg_guest/lv_root + /usr => /dev/vg_guest/lv_usr + +The caller can then make calls to L</guestfs_mount_options> to +mount the filesystems as suggested. + +Be careful to mount filesystems in the right order (eg. C</> before +C</usr>). Sorting the keys of the hash by length, shortest first, +should work. + +Inspection currently only works for some common operating systems. +Contributors are welcome to send patches for other operating systems +that we currently cannot detect. + +Encrypted disks must be opened before inspection. See +L</ENCRYPTED DISKS> for more details. The L</guestfs_inspect_os> +function just ignores any encrypted devices. + +A note on the implementation: The call L</guestfs_inspect_os> performs +inspection and caches the results in the guest handle. Subsequent +calls to C<guestfs_inspect_get_*> return this cached information, but +I<do not> re-read the disks. If you change the content of the guest +disks, you can redo inspection by calling L</guestfs_inspect_os> +again. + =head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS Libguestfs can mount NTFS partitions. It does this using the @@ -495,7 +555,7 @@ that directory might be referred to as C</WINDOWS/System32>. Drive letter mappings are outside the scope of libguestfs. You have to use libguestfs to read the appropriate Windows Registry and configuration files, to determine yourself how drives are mapped (see -also L<virt-inspector(1)>). +also L<hivex(3)> and L<virt-inspector(1)>). Replacing backslash characters with forward slash characters is also outside the scope of libguestfs, but something that you can easily do. diff --git a/src/inspect.c b/src/inspect.c index 8e28d8a..e60c48f 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -27,8 +27,11 @@ #include <sys/stat.h> #include <pcre.h> +#include <hivex.h> +#include <augeas.h> #include "ignore-value.h" +#include "xstrtol.h" #include "guestfs.h" #include "guestfs-internal.h" @@ -43,6 +46,14 @@ static pcre *re_file_elf; static pcre *re_file_win64; static pcre *re_elf_ppc64; +static pcre *re_fedora; +static pcre *re_rhel_old; +static pcre *re_rhel; +static pcre *re_rhel_no_minor; +static pcre *re_debian; +static pcre *re_aug_seq; +static pcre *re_xdev; +static pcre *re_windows_version; static void compile_regexps (void) __attribute__((constructor)); static void @@ -64,6 +75,17 @@ compile_regexps (void) "ELF.*(?:executable|shared object|relocatable), (.+?),", 0); COMPILE (re_file_win64, "PE32\\+ executable", 0); COMPILE (re_elf_ppc64, "64.*PowerPC", 0); + COMPILE (re_fedora, "Fedora release (\\d+)", 0); + COMPILE (re_rhel_old, + "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+).*Update (\\d+)", 0); + COMPILE (re_rhel, + "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)", 0); + COMPILE (re_rhel_no_minor, + "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)", 0); + COMPILE (re_debian, "(\\d+)\\.(\\d+)", 0); + COMPILE (re_aug_seq, "/\\d+$", 0); + COMPILE (re_xdev, "^/dev/(?:h|s|v|xv)d([a-z]\\d*)$", 0); + COMPILE (re_windows_version, "^(\\d+)\\.(\\d+)", 0); } /* Match a regular expression which contains no captures. Returns @@ -111,6 +133,29 @@ match1 (const char *str, const pcre *re) return strndup (&str[vec[2]], vec[3]-vec[2]); } +/* Match a regular expression which contains exactly two captures. */ +static int +match2 (const char *str, const pcre *re, char **ret1, char **ret2) +{ + size_t len = strlen (str); + int vec[30], r; + + r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30); + if (r == PCRE_ERROR_NOMATCH) + return 0; + if (r != 3) { + /* Internal error -- should not happen. */ + fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + __FILE__, __func__, r, str); + return 0; + } + + *ret1 = strndup (&str[vec[2]], vec[3]-vec[2]); + *ret2 = strndup (&str[vec[4]], vec[5]-vec[4]); + + return 1; +} + /* Convert output from 'file' command on ELF files to the canonical * architecture string. Caller must free the result. */ @@ -264,3 +309,953 @@ guestfs__file_architecture (guestfs_h *g, const char *path) free (elf_arch); return ret; /* caller frees */ } + +/* The main inspection code. */ +static void free_string_list (char **); +static int check_for_filesystem_on (guestfs_h *g, const char *device); + +char ** +guestfs__inspect_os (guestfs_h *g) +{ + /* Remove any information previously stored in the handle. */ + guestfs___free_inspect_info (g); + + if (guestfs_umount_all (g) == -1) + return NULL; + + /* Iterate over all possible devices. Try to mount each + * (read-only). Examine ones which contain filesystems and add that + * information to the handle. + */ + /* Look to see if any devices directly contain filesystems (RHBZ#590167). */ + char **devices; + devices = guestfs_list_devices (g); + if (devices == NULL) + return NULL; + + size_t i; + for (i = 0; devices[i] != NULL; ++i) { + if (check_for_filesystem_on (g, devices[i]) == -1) { + free_string_list (devices); + guestfs___free_inspect_info (g); + return NULL; + } + } + free_string_list (devices); + + /* Look at all partitions. */ + char **partitions; + partitions = guestfs_list_partitions (g); + if (partitions == NULL) { + guestfs___free_inspect_info (g); + return NULL; + } + + for (i = 0; partitions[i] != NULL; ++i) { + if (check_for_filesystem_on (g, partitions[i]) == -1) { + free_string_list (partitions); + guestfs___free_inspect_info (g); + return NULL; + } + } + free_string_list (partitions); + + /* Look at all LVs. */ + char **lvs; + lvs = guestfs_lvs (g); + if (lvs == NULL) { + guestfs___free_inspect_info (g); + return NULL; + } + + for (i = 0; lvs[i] != NULL; ++i) { + if (check_for_filesystem_on (g, lvs[i]) == -1) { + free_string_list (lvs); + guestfs___free_inspect_info (g); + return NULL; + } + } + free_string_list (lvs); + + /* At this point we have, in the handle, a list of all filesystems + * found and data about each one. Now we assemble the list of + * filesystems which are root devices and return that to the user. + */ + size_t count = 0; + for (i = 0; i < g->nr_fses; ++i) + if (g->fses[i].is_root) + count++; + + char **ret = calloc (count+1, sizeof (char *)); + if (ret == NULL) { + perrorf (g, "calloc"); + guestfs___free_inspect_info (g); + return NULL; + } + + count = 0; + for (i = 0; i < g->nr_fses; ++i) { + if (g->fses[i].is_root) { + ret[count] = safe_strdup (g, g->fses[i].device); + count++; + } + } + ret[count] = NULL; + + return ret; +} + +void +guestfs___free_inspect_info (guestfs_h *g) +{ + size_t i; + for (i = 0; i < g->nr_fses; ++i) { + free (g->fses[i].device); + free (g->fses[i].product_name); + free (g->fses[i].arch); + size_t j; + for (j = 0; j < g->fses[i].nr_fstab; ++j) { + free (g->fses[i].fstab[j].device); + free (g->fses[i].fstab[j].mountpoint); + } + free (g->fses[i].fstab); + } + free (g->fses); + g->nr_fses = 0; + g->fses = NULL; +} + +static void +free_string_list (char **argv) +{ + size_t i; + for (i = 0; argv[i] != NULL; ++i) + free (argv[i]); + free (argv); +} + +/* Find out if 'device' contains a filesystem. If it does, add + * another entry in g->fses. + */ +static int check_filesystem (guestfs_h *g, const char *device); +static int check_linux_root (guestfs_h *g, struct inspect_fs *fs); +static int check_fstab (guestfs_h *g, struct inspect_fs *fs); +static int check_windows_root (guestfs_h *g, struct inspect_fs *fs); +static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs, + const char *systemroot); +static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs, + const char *systemroot); +static char *resolve_windows_path_silently (guestfs_h *g, const char *); +static int extend_fses (guestfs_h *g); +static int parse_unsigned_int (guestfs_h *g, const char *str); +static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, + const char *spec, const char *mp); +static char *resolve_fstab_device (guestfs_h *g, const char *spec); + +static int +check_for_filesystem_on (guestfs_h *g, const char *device) +{ + /* Get vfs-type in order to check if it's a Linux(?) swap device. + * If there's an error we should ignore it, so to do that we have to + * temporarily replace the error handler with a null one. + */ + guestfs_error_handler_cb old_error_cb = g->error_cb; + g->error_cb = NULL; + char *vfs_type = guestfs_vfs_type (g, device); + g->error_cb = old_error_cb; + + int is_swap = vfs_type && STREQ (vfs_type, "swap"); + + if (g->verbose) + fprintf (stderr, "check_for_filesystem_on: %s (%s)\n", + device, vfs_type ? vfs_type : "failed to get vfs type"); + + if (is_swap) { + free (vfs_type); + if (extend_fses (g) == -1) + return -1; + g->fses[g->nr_fses-1].is_swap = 1; + return 0; + } + + /* Try mounting the device. As above, ignore errors. */ + g->error_cb = NULL; + int r = guestfs_mount_ro (g, device, "/"); + if (r == -1 && vfs_type && STREQ (vfs_type, "ufs")) /* Hack for the *BSDs. */ + r = guestfs_mount_vfs (g, "ro,ufstype=ufs2", "ufs", device, "/"); + free (vfs_type); + g->error_cb = old_error_cb; + if (r == -1) + return 0; + + /* Do the rest of the checks. */ + r = check_filesystem (g, device); + + /* Unmount the filesystem. */ + if (guestfs_umount_all (g) == -1) + return -1; + + return r; +} + +static int +check_filesystem (guestfs_h *g, const char *device) +{ + if (extend_fses (g) == -1) + return -1; + + struct inspect_fs *fs = &g->fses[g->nr_fses-1]; + + fs->device = safe_strdup (g, device); + fs->is_mountable = 1; + + /* Grub /boot? */ + if (guestfs_is_file (g, "/grub/menu.lst") > 0 || + guestfs_is_file (g, "/grub/grub.conf") > 0) + fs->content = FS_CONTENT_LINUX_BOOT; + /* Linux root? */ + else if (guestfs_is_dir (g, "/etc") > 0 && + guestfs_is_dir (g, "/bin") > 0 && + guestfs_is_file (g, "/etc/fstab") > 0) { + fs->is_root = 1; + fs->content = FS_CONTENT_LINUX_ROOT; + if (check_linux_root (g, fs) == -1) + return -1; + } + /* Linux /usr/local? */ + else if (guestfs_is_dir (g, "/etc") > 0 && + guestfs_is_dir (g, "/bin") > 0 && + guestfs_is_dir (g, "/share") > 0 && + guestfs_exists (g, "/local") == 0 && + guestfs_is_file (g, "/etc/fstab") == 0) + fs->content = FS_CONTENT_LINUX_USR_LOCAL; + /* Linux /usr? */ + else if (guestfs_is_dir (g, "/etc") > 0 && + guestfs_is_dir (g, "/bin") > 0 && + guestfs_is_dir (g, "/share") > 0 && + guestfs_exists (g, "/local") > 0 && + guestfs_is_file (g, "/etc/fstab") == 0) + fs->content = FS_CONTENT_LINUX_USR; + /* Linux /var? */ + else if (guestfs_is_dir (g, "/log") > 0 && + guestfs_is_dir (g, "/run") > 0 && + guestfs_is_dir (g, "/spool") > 0) + fs->content = FS_CONTENT_LINUX_VAR; + /* Windows root? */ + else if (guestfs_is_file (g, "/AUTOEXEC.BAT") > 0 || + guestfs_is_file (g, "/autoexec.bat") > 0 || + guestfs_is_dir (g, "/Program Files") > 0 || + guestfs_is_dir (g, "/WINDOWS") > 0 || + guestfs_is_dir (g, "/Windows") > 0 || + guestfs_is_dir (g, "/windows") > 0 || + guestfs_is_dir (g, "/WIN32") > 0 || + guestfs_is_dir (g, "/Win32") > 0 || + guestfs_is_dir (g, "/WINNT") > 0 || + guestfs_is_file (g, "/boot.ini") > 0 || + guestfs_is_file (g, "/ntldr") > 0) { + fs->is_root = 1; + fs->content = FS_CONTENT_WINDOWS_ROOT; + if (check_windows_root (g, fs) == -1) + return -1; + } + + return 0; +} + +/* The currently mounted device is known to be a Linux root. Try to + * determine from this the distro, version, etc. Also parse + * /etc/fstab to determine the arrangement of mountpoints and + * associated devices. + */ +static int +check_linux_root (guestfs_h *g, struct inspect_fs *fs) +{ + fs->type = OS_TYPE_LINUX; + + if (guestfs_exists (g, "/etc/redhat-release") > 0) { + fs->distro = OS_DISTRO_REDHAT_BASED; /* Something generic Red Hat-like. */ + + char **product_name = guestfs_head_n (g, 1, "/etc/redhat-release"); + if (product_name == NULL) + return -1; + if (product_name[0] == NULL) { + error (g, "/etc/redhat-release file is empty"); + free_string_list (product_name); + return -1; + } + + /* Note that this string becomes owned by the handle and will + * be freed by guestfs___free_inspect_info. + */ + fs->product_name = product_name[0]; + free (product_name); + + char *major, *minor; + if ((major = match1 (fs->product_name, re_fedora)) != NULL) { + fs->distro = OS_DISTRO_FEDORA; + fs->major_version = parse_unsigned_int (g, major); + free (major); + if (fs->major_version == -1) + return -1; + } + else if (match2 (fs->product_name, re_rhel_old, &major, &minor) || + match2 (fs->product_name, re_rhel, &major, &minor)) { + fs->distro = OS_DISTRO_RHEL; + fs->major_version = parse_unsigned_int (g, major); + free (major); + if (fs->major_version == -1) { + free (minor); + return -1; + } + fs->minor_version = parse_unsigned_int (g, minor); + free (minor); + if (fs->minor_version == -1) + return -1; + } + else if ((major = match1 (fs->product_name, re_rhel_no_minor)) != NULL) { + fs->distro = OS_DISTRO_RHEL; + fs->major_version = parse_unsigned_int (g, major); + free (major); + if (fs->major_version == -1) + return -1; + fs->minor_version = 0; + } + } + else if (guestfs_exists (g, "/etc/debian_version") > 0) { + fs->distro = OS_DISTRO_DEBIAN; + + char **product_name = guestfs_head_n (g, 1, "/etc/debian_version"); + if (product_name == NULL) + return -1; + if (product_name[0] == NULL) { + error (g, "/etc/debian_version file is empty"); + free_string_list (product_name); + return -1; + } + + /* Note that this string becomes owned by the handle and will + * be freed by guestfs___free_inspect_info. + */ + fs->product_name = product_name[0]; + free (product_name); + + char *major, *minor; + if (match2 (fs->product_name, re_debian, &major, &minor)) { + fs->major_version = parse_unsigned_int (g, major); + free (major); + if (fs->major_version == -1) { + free (minor); + return -1; + } + fs->minor_version = parse_unsigned_int (g, minor); + free (minor); + if (fs->minor_version == -1) + return -1; + } + } + + /* Determine the architecture. */ + const char *binaries[] + { "/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh" }; + size_t i; + for (i = 0; i < sizeof binaries / sizeof binaries[0]; ++i) { + if (guestfs_is_file (g, binaries[i]) > 0) { + /* Ignore errors from file_architecture call. */ + guestfs_error_handler_cb old_error_cb = g->error_cb; + g->error_cb = NULL; + char *arch = guestfs_file_architecture (g, binaries[i]); + g->error_cb = old_error_cb; + + if (arch) { + /* String will be owned by handle, freed by + * guestfs___free_inspect_info. + */ + fs->arch = arch; + break; + } + } + } + + /* We already know /etc/fstab exists because it's part of the test + * for Linux root above. We must now parse this file to determine + * which filesystems are used by the operating system and how they + * are mounted. + */ + if (guestfs_aug_init (g, "/", AUG_NO_LOAD|AUG_SAVE_NOOP) == -1) + return -1; + + /* Tell Augeas to only load /etc/fstab (thanks Rapha?l Pinson). */ + guestfs_aug_rm (g, "/augeas/load//incl[. != \"/etc/fstab\"]"); + guestfs_aug_load (g); + + int r = check_fstab (g, fs); + guestfs_aug_close (g); + if (r == -1) + return -1; + + return 0; +} + +static int +check_fstab (guestfs_h *g, struct inspect_fs *fs) +{ + char **lines = guestfs_aug_ls (g, "/files/etc/fstab"); + if (lines == NULL) + return -1; + + if (lines[0] == NULL) { + error (g, "could not parse /etc/fstab or empty file"); + free_string_list (lines); + return -1; + } + + size_t i; + char augpath[256]; + for (i = 0; lines[i] != NULL; ++i) { + /* Ignore comments. Only care about sequence lines which + * match m{/\d+$}. + */ + if (match (lines[i], re_aug_seq)) { + snprintf (augpath, sizeof augpath, "%s/spec", lines[i]); + char *spec = guestfs_aug_get (g, augpath); + if (spec == NULL) { + free_string_list (lines); + return -1; + } + + snprintf (augpath, sizeof augpath, "%s/file", lines[i]); + char *mp = guestfs_aug_get (g, augpath); + if (mp == NULL) { + free_string_list (lines); + free (spec); + return -1; + } + + int r = add_fstab_entry (g, fs, spec, mp); + free (spec); + free (mp); + + if (r == -1) { + free_string_list (lines); + return -1; + } + } + } + + free_string_list (lines); + return 0; +} + +/* Add a filesystem and possibly a mountpoint entry for + * the root filesystem 'fs'. + * + * 'spec' is the fstab spec field, which might be a device name or a + * pseudodevice or 'UUID=...' or 'LABEL=...'. + * + * 'mp' is the mount point, which could also be 'swap' or 'none'. + */ +static int +add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, + const char *spec, const char *mp) +{ + /* Ignore certain mountpoints. */ + if (STRPREFIX (mp, "/dev/") || + STREQ (mp, "/dev") || + STRPREFIX (mp, "/media/") || + STRPREFIX (mp, "/proc/") || + STREQ (mp, "/proc") || + STRPREFIX (mp, "/selinux/") || + STREQ (mp, "/selinux") || + STRPREFIX (mp, "/sys/") || + STREQ (mp, "/sys")) + return 0; + + /* Resolve UUID= and LABEL= to the actual device. */ + char *device = NULL; + if (STRPREFIX (spec, "UUID=")) + device = guestfs_findfs_uuid (g, &spec[5]); + else if (STRPREFIX (spec, "LABEL=")) + device = guestfs_findfs_label (g, &spec[6]); + /* Resolve guest block device names. */ + else if (spec[0] == '/') + device = resolve_fstab_device (g, spec); + /* Also ignore pseudo-devices completely, like spec == "tmpfs". + * If we haven't resolved the device successfully by this point, + * we don't care, just ignore it. + */ + if (device == NULL) + return 0; + + char *mountpoint = safe_strdup (g, mp); + + /* Add this to the fstab entry in 'fs'. + * Note these are further filtered by guestfs_inspect_get_mountpoints + * and guestfs_inspect_get_filesystems. + */ + size_t n = fs->nr_fstab + 1; + struct inspect_fstab_entry *p; + + p = realloc (fs->fstab, n * sizeof (struct inspect_fstab_entry)); + if (p == NULL) { + perrorf (g, "realloc"); + free (device); + free (mountpoint); + return -1; + } + + fs->fstab = p; + fs->nr_fstab = n; + + /* These are owned by the handle and freed by guestfs___free_inspect_info. */ + fs->fstab[n-1].device = device; + fs->fstab[n-1].mountpoint = mountpoint; + + if (g->verbose) + fprintf (stderr, "fstab: device=%s mountpoint=%s\n", device, mountpoint); + + return 0; +} + +/* Resolve block device name to the libguestfs device name, eg. + * /dev/xvdb1 => /dev/vdb1. This assumes that disks were added in the + * same order as they appear to the real VM, which is a reasonable + * assumption to make. Return things like LV names unchanged (or + * anything we don't recognize). + */ +static char * +resolve_fstab_device (guestfs_h *g, const char *spec) +{ + char **devices = guestfs_list_devices (g); + if (devices == NULL) + return NULL; + + size_t count; + for (count = 0; devices[count] != NULL; count++) + ; + + char *device = NULL; + char *a1 = match1 (spec, re_xdev); + if (a1) { + size_t i = a1[0] - 'a'; /* a1[0] is always [a-z] because of regex. */ + if (i < count) { + size_t len = strlen (devices[i]) + strlen (a1) + 16; + device = safe_malloc (g, len); + snprintf (device, len, "%s%s", devices[i], &a1[1]); + } + } else { + /* Didn't match device pattern, return original spec unchanged. */ + device = safe_strdup (g, spec); + } + + free (a1); + free_string_list (devices); + + return device; +} + +/* XXX Handling of boot.ini in the Perl version was pretty broken. It + * essentially didn't do anything for modern Windows guests. + * Therefore I've omitted all that code. + */ +static int +check_windows_root (guestfs_h *g, struct inspect_fs *fs) +{ + fs->type = OS_TYPE_WINDOWS; + fs->distro = OS_DISTRO_WINDOWS; + + /* Try to find Windows systemroot using some common locations. */ + const char *systemroots[] + { "/windows", "/winnt", "/win32", "/win" }; + size_t i; + char *systemroot = NULL; + for (i = 0; + systemroot == NULL && i < sizeof systemroots / sizeof systemroots[0]; + ++i) { + systemroot = resolve_windows_path_silently (g, systemroots[i]); + } + + if (!systemroot) { + error (g, _("cannot resolve Windows %%SYSTEMROOT%%")); + return -1; + } + + /* XXX There is a case for exposing systemroot and many variables + * from the registry through the libguestfs API. + */ + + if (g->verbose) + fprintf (stderr, "windows %%SYSTEMROOT%% = %s", systemroot); + + if (check_windows_arch (g, fs, systemroot) == -1) { + free (systemroot); + return -1; + } + + if (check_windows_registry (g, fs, systemroot) == -1) { + free (systemroot); + return -1; + } + + free (systemroot); + return 0; +} + +static int +check_windows_arch (guestfs_h *g, struct inspect_fs *fs, + const char *systemroot) +{ + size_t len = strlen (systemroot) + 32; + char cmd_exe[len]; + snprintf (cmd_exe, len, "%s/system32/cmd.exe", systemroot); + + char *cmd_exe_path = resolve_windows_path_silently (g, cmd_exe); + if (!cmd_exe_path) + return 0; + + char *arch = guestfs_file_architecture (g, cmd_exe_path); + free (cmd_exe_path); + + if (arch) + fs->arch = arch; /* freed by guestfs___free_inspect_info */ + + return 0; +} + +/* At the moment, pull just the ProductName and version numbers from + * the registry. In future there is a case for making many more + * registry fields available to callers. + */ +static int +check_windows_registry (guestfs_h *g, struct inspect_fs *fs, + const char *systemroot) +{ + size_t len = strlen (systemroot) + 64; + char software[len]; + snprintf (software, len, "%s/system32/config/software", systemroot); + + char *software_path = resolve_windows_path_silently (g, software); + if (!software_path) + /* If the software hive doesn't exist, just accept that we cannot + * find product_name etc. + */ + return 0; + + int ret = -1; + hive_h *h = NULL; + hive_value_h *values = NULL; + + char dir[] = "/tmp/winreg.XXXXXX"; +#define dir_len 18 + if (mkdtemp (dir) == NULL) { + perrorf (g, "mkdtemp"); + goto out; + } + + char software_hive[dir_len + 16]; + snprintf (software_hive, dir_len + 16, "%s/software", dir); + + if (guestfs_download (g, software_path, software_hive) == -1) + goto out; + + h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0); + if (h == NULL) { + perrorf (g, "hivex_open"); + goto out; + } + + hive_node_h node = hivex_root (h); + const char *hivepath[] + { "Microsoft", "Windows NT", "CurrentVersion" }; + size_t i; + for (i = 0; + node != 0 && i < sizeof hivepath / sizeof hivepath[0]; + ++i) { + node = hivex_node_get_child (h, node, hivepath[i]); + } + + if (node == 0) { + perrorf (g, "hivex: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); + goto out; + } + + values = hivex_node_values (h, node); + + for (i = 0; values[i] != 0; ++i) { + char *key = hivex_value_key (h, values[i]); + if (key == NULL) { + perrorf (g, "hivex_value_key"); + goto out; + } + + if (STRCASEEQ (key, "ProductName")) { + fs->product_name = hivex_value_string (h, values[i]); + if (!fs->product_name) { + perrorf (g, "hivex_value_string"); + free (key); + goto out; + } + } + else if (STRCASEEQ (key, "CurrentVersion")) { + char *version = hivex_value_string (h, values[i]); + if (!version) { + perrorf (g, "hivex_value_string"); + free (key); + goto out; + } + char *major, *minor; + if (match2 (version, re_windows_version, &major, &minor)) { + fs->major_version = parse_unsigned_int (g, major); + free (major); + if (fs->major_version == -1) { + free (minor); + free (key); + free (version); + goto out; + } + fs->minor_version = parse_unsigned_int (g, minor); + free (minor); + if (fs->minor_version == -1) { + free (key); + free (version); + return -1; + } + } + + free (version); + } + + free (key); + } + + ret = 0; + + out: + if (h) hivex_close (h); + free (values); + free (software_path); + + /* Free up the temporary directory. Note the directory name cannot + * contain shell meta-characters because of the way it was + * constructed above. + */ + char cmd[dir_len + 16]; + snprintf (cmd, dir_len + 16, "rm -rf %s", dir); + ignore_value (system (cmd)); +#undef dir_len + + return ret; +} + +static char * +resolve_windows_path_silently (guestfs_h *g, const char *path) +{ + guestfs_error_handler_cb old_error_cb = g->error_cb; + g->error_cb = NULL; + char *ret = guestfs_case_sensitive_path (g, path); + g->error_cb = old_error_cb; + return ret; +} + +static int +extend_fses (guestfs_h *g) +{ + size_t n = g->nr_fses + 1; + struct inspect_fs *p; + + p = realloc (g->fses, n * sizeof (struct inspect_fs)); + if (p == NULL) { + perrorf (g, "realloc"); + return -1; + } + + g->fses = p; + g->nr_fses = n; + + memset (&g->fses[n-1], 0, sizeof (struct inspect_fs)); + + return 0; +} + +/* Parse small, unsigned ints, as used in version numbers. */ +static int +parse_unsigned_int (guestfs_h *g, const char *str) +{ + long ret; + int r = xstrtol (str, NULL, 10, &ret, ""); + if (r != LONGINT_OK) { + error (g, "could not parse integer in version number: %s", str); + return -1; + } + return ret; +} + +static struct inspect_fs * +search_for_root (guestfs_h *g, const char *root) +{ + if (g->nr_fses == 0) { + error (g, _("no inspection data: call guestfs_inspect_os first")); + return NULL; + } + + size_t i; + struct inspect_fs *fs; + for (i = 0; i < g->nr_fses; ++i) { + fs = &g->fses[i]; + if (fs->is_root && STREQ (root, fs->device)) + return fs; + } + + error (g, _("%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"), + root); + return NULL; +} + +char * +guestfs__inspect_get_type (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + char *ret; + switch (fs->type) { + case OS_TYPE_LINUX: ret = safe_strdup (g, "linux"); break; + case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break; + case OS_TYPE_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break; + } + + return ret; +} + +char * +guestfs__inspect_get_arch (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + return safe_strdup (g, fs->arch ? : "unknown"); +} + +char * +guestfs__inspect_get_distro (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + char *ret; + switch (fs->distro) { + case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break; + case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break; + case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break; + case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break; + case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break; + case OS_DISTRO_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break; + } + + return ret; +} + +int +guestfs__inspect_get_major_version (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return -1; + + return fs->major_version; +} + +int +guestfs__inspect_get_minor_version (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return -1; + + return fs->minor_version; +} + +char * +guestfs__inspect_get_product_name (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + return safe_strdup (g, fs->product_name ? : "unknown"); +} + +char ** +guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + char **ret; + + /* If no fstab information (Windows) return just the root. */ + if (fs->nr_fstab == 0) { + ret = calloc (3, sizeof (char *)); + ret[0] = safe_strdup (g, root); + ret[1] = safe_strdup (g, "/"); + ret[2] = NULL; + return ret; + } + +#define CRITERION fs->fstab[i].mountpoint[0] == '/' + size_t i, count = 0; + for (i = 0; i < fs->nr_fstab; ++i) + if (CRITERION) + count++; + + /* Hashtables have 2N+1 entries. */ + ret = calloc (2*count+1, sizeof (char *)); + if (ret == NULL) { + perrorf (g, "calloc"); + return NULL; + } + + count = 0; + for (i = 0; i < fs->nr_fstab; ++i) + if (CRITERION) { + ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint); + ret[2*count+1] = safe_strdup (g, fs->fstab[i].device); + count++; + } +#undef CRITERION + + return ret; +} + +char ** +guestfs__inspect_get_filesystems (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + char **ret; + + /* If no fstab information (Windows) return just the root. */ + if (fs->nr_fstab == 0) { + ret = calloc (2, sizeof (char *)); + ret[0] = safe_strdup (g, root); + ret[1] = NULL; + return ret; + } + + ret = calloc (fs->nr_fstab + 1, sizeof (char *)); + if (ret == NULL) { + perrorf (g, "calloc"); + return NULL; + } + + size_t i; + for (i = 0; i < fs->nr_fstab; ++i) + ret[i] = safe_strdup (g, fs->fstab[i].device); + + return ret; +} -- 1.7.1
Richard W.M. Jones
2010-Aug-02 17:14 UTC
[Libguestfs] [PATCH v3 4/5] fish: Add -c/--connect and -d/--domain options.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html -------------- next part -------------->From c6d5c347f78a1636996627057baeda64fef4b66e Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 2 Aug 2010 16:33:25 +0100 Subject: [PATCH 4/5] fish: Add -c/--connect and -d/--domain options. The -d option lets you specify libvirt domains. The disks from these domains are found and added, as if you'd named them with -a. The -c option lets you specify a libvirt URI, which is needed when we consult libvirt to implement the above. --- README | 4 + configure.ac | 10 +++ fish/Makefile.am | 8 ++- fish/fish.c | 125 +++++++++++++++++++++++++--------- fish/fish.h | 5 ++ fish/guestfish.pod | 14 ++++ fish/virt.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + 8 files changed, 325 insertions(+), 33 deletions(-) create mode 100644 fish/virt.c diff --git a/README b/README index e542073..4207801 100644 --- a/README +++ b/README @@ -50,6 +50,10 @@ Requirements - pcre (Perl Compatible Regular Expressions C library) +- libvirt + +- libxml2 + - squashfs-tools (mksquashfs only) - genisoimage / mkisofs diff --git a/configure.ac b/configure.ac index 9cb2278..b94fca0 100644 --- a/configure.ac +++ b/configure.ac @@ -458,6 +458,16 @@ dnl For i18n. AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION([0.17]) +dnl libvirt (required) +PKG_CHECK_MODULES([LIBVIRT], [libvirt]) +AC_SUBST([LIBVIRT_CFLAGS]) +AC_SUBST([LIBVIRT_LIBS]) + +dnl libxml2 (required) +PKG_CHECK_MODULES([LIBXML2], [libxml-2.0]) +AC_SUBST([LIBXML2_CFLAGS]) +AC_SUBST([LIBXML2_LIBS]) + dnl hivex library (highly recommended). dnl This used to be a part of libguestfs, but was spun off into its dnl own separate upstream project in libguestfs 1.0.85. diff --git a/fish/Makefile.am b/fish/Makefile.am index f6b3e7d..cd16733 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -52,7 +52,8 @@ guestfish_SOURCES = \ reopen.c \ supported.c \ tilde.c \ - time.c + time.c \ + virt.c # This convenience library is solely to avoid compiler warnings # in its generated sources. @@ -65,9 +66,12 @@ guestfish_CFLAGS = \ -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \ -DLOCALEBASEDIR=\""$(datadir)/locale"\" \ -I$(srcdir)/../gnulib/lib -I../gnulib/lib \ + $(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \ $(WARN_CFLAGS) $(WERROR_CFLAGS) -guestfish_LDADD = $(top_builddir)/src/libguestfs.la $(LIBREADLINE) +guestfish_LDADD = \ + $(LIBVIRT_LIBS) $(LIBXML2_LIBS) \ + $(top_builddir)/src/libguestfs.la $(LIBREADLINE) # Make libguestfs use the convenience library. noinst_LTLIBRARIES = librc_protocol.la diff --git a/fish/fish.c b/fish/fish.c index 68f26ed..71b9827 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -43,11 +43,23 @@ #include "closeout.h" #include "progname.h" +/* List of drives added via -a, -d or -N options. */ struct drv { struct drv *next; - char *filename; /* disk filename (for -a or -N options) */ - prep_data *data; /* prepared type (for -N option only) */ - char *device; /* device inside the appliance */ + enum { drv_a, drv_d, drv_N } type; + union { + struct { + char *filename; /* disk filename */ + } a; + struct { + char *guest; /* guest name */ + } d; + struct { + char *filename; /* disk filename (testX.img) */ + prep_data *data; /* prepared type */ + char *device; /* device inside the appliance */ + } N; + }; }; struct mp { @@ -56,7 +68,7 @@ struct mp { char *mountpoint; }; -static void add_drives (struct drv *drv); +static char add_drives (struct drv *drv, char next_drive); static void prepare_drives (struct drv *drv); static void mount_mps (struct mp *mp); static int launch (void); @@ -82,6 +94,7 @@ int remote_control = 0; int exit_on_error = 1; int command_num = 0; int keys_from_stdin = 0; +const char *libvirt_uri = NULL; static void __attribute__((noreturn)) usage (int status) @@ -109,6 +122,7 @@ usage (int status) " -h|--cmd-help List available commands\n" " -h|--cmd-help cmd Display detailed help on 'cmd'\n" " -a|--add image Add image\n" + " -d|--domain guest Add disks from libvirt guest\n" " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" " -f|--file file Read commands from file\n" " -i|--inspector Run virt-inspector to get disk mountpoints\n" @@ -145,10 +159,12 @@ main (int argc, char *argv[]) enum { HELP_OPTION = CHAR_MAX + 1 }; - static const char *options = "a:Df:h::im:nN:rv?Vx"; + static const char *options = "a:c:d:Df:h::im:nN:rv?Vx"; static const struct option long_options[] = { { "add", 1, 0, 'a' }, { "cmd-help", 2, 0, 'h' }, + { "connect", 1, 0, 'c' }, + { "domain", 1, 0, 'd' }, { "file", 1, 0, 'f' }, { "help", 0, 0, HELP_OPTION }, { "inspector", 0, 0, 'i' }, @@ -174,7 +190,6 @@ main (int argc, char *argv[]) int inspector = 0; int option_index; struct sigaction sa; - char next_drive = 'a'; int next_prepared_drive = 1; initialize_readline (); @@ -262,15 +277,26 @@ main (int argc, char *argv[]) perror ("malloc"); exit (EXIT_FAILURE); } - drv->filename = optarg; - drv->data = NULL; - /* We could fill the device field in, but in fact we - * only use it for the -N option at present. - */ - drv->device = NULL; + drv->type = drv_a; + drv->a.filename = optarg; + drv->next = drvs; + drvs = drv; + break; + + case 'c': + libvirt_uri = optarg; + break; + + case 'd': + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + drv->type = drv_d; + drv->d.guest = optarg; drv->next = drvs; drvs = drv; - next_drive++; break; case 'N': @@ -283,16 +309,14 @@ main (int argc, char *argv[]) perror ("malloc"); exit (EXIT_FAILURE); } - if (asprintf (&drv->filename, "test%d.img", + drv->type = drv_N; + if (asprintf (&drv->N.filename, "test%d.img", next_prepared_drive++) == -1) { perror ("asprintf"); exit (EXIT_FAILURE); } - drv->data = create_prepared_file (optarg, drv->filename); - if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) { - perror ("asprintf"); - exit (EXIT_FAILURE); - } + drv->N.data = create_prepared_file (optarg, drv->N.filename); + drv->N.device = NULL; /* filled in by add_drives */ drv->next = drvs; drvs = drv; break; @@ -476,7 +500,7 @@ main (int argc, char *argv[]) } /* If we've got drives to add, add them now. */ - add_drives (drvs); + add_drives (drvs, 'a'); /* If we've got mountpoints or prepared drives, we must launch the * guest and mount them. @@ -584,21 +608,60 @@ mount_mps (struct mp *mp) } } -static void -add_drives (struct drv *drv) +static char +add_drives (struct drv *drv, char next_drive) { int r; + if (next_drive > 'z') { + fprintf (stderr, + _("guestfish: too many drives added on the command line\n")); + exit (EXIT_FAILURE); + } + if (drv) { - add_drives (drv->next); + next_drive = add_drives (drv->next, next_drive); - if (drv->data /* -N option is not affected by --ro */ || !read_only) - r = guestfs_add_drive (g, drv->filename); - else - r = guestfs_add_drive_ro (g, drv->filename); - if (r == -1) - exit (EXIT_FAILURE); + switch (drv->type) { + case drv_a: + if (!read_only) + r = guestfs_add_drive (g, drv->a.filename); + else + r = guestfs_add_drive_ro (g, drv->a.filename); + if (r == -1) + exit (EXIT_FAILURE); + + next_drive++; + break; + + case drv_d: + r = add_libvirt_drives (drv->d.guest); + if (r == -1) + exit (EXIT_FAILURE); + + next_drive += r; + break; + + case drv_N: + /* -N option is not affected by --ro */ + r = guestfs_add_drive (g, drv->N.filename); + if (r == -1) + exit (EXIT_FAILURE); + + if (asprintf (&drv->N.device, "/dev/sd%c", next_drive) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + + next_drive++; + break; + + default: /* keep GCC happy */ + abort (); + } } + + return next_drive; } static void @@ -606,8 +669,8 @@ prepare_drives (struct drv *drv) { if (drv) { prepare_drives (drv->next); - if (drv->data) - prepare_drive (drv->filename, drv->data, drv->device); + if (drv->type == drv_N) + prepare_drive (drv->N.filename, drv->N.data, drv->N.device); } } diff --git a/fish/fish.h b/fish/fish.h index da1b087..bf1f81c 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -49,9 +49,11 @@ /* in fish.c */ extern guestfs_h *g; +extern int read_only; extern int quit; extern int verbose; extern int command_num; +extern const char *libvirt_uri; extern int issue_command (const char *cmd, char *argv[], const char *pipe); extern void pod2text (const char *name, const char *shortdesc, const char *body); extern void list_builtin_commands (void); @@ -131,6 +133,9 @@ extern int do_time (const char *cmd, int argc, char *argv[]); /* in tilde.c */ extern char *try_tilde_expansion (char *path); +/* in virt.c */ +extern int add_libvirt_drives (const char *guest); + /* This should just list all the built-in commands so they can * be added to the generated auto-completion code. */ diff --git a/fish/guestfish.pod b/fish/guestfish.pod index bfcec5c..8daebc8 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -14,6 +14,8 @@ guestfish - the libguestfs Filesystem Interactive SHell guestfish -a disk.img -m dev[:mountpoint] + guestfish -d libvirt-domain + guestfish -i libvirt-domain guestfish -i disk.img [disk.img ...] @@ -140,6 +142,18 @@ Displays detailed help on a single command C<cmd>. Add a block device or virtual machine image to the shell. +=item B<-c URI> | B<--connect URI> + +When used in conjunction with the I<-d> option, this specifies +the libvirt URI to use. The default is to use the default libvirt +connection. + +=item B<-d libvirt-domain> | B<--domain libvirt-domain> + +Add disks from the named libvirt domain. If the I<--ro> option is +also used, then any libvirt domain can be used. However in write +mode, only libvirt domains which are shut down can be named here. + =item B<-D> | B<--no-dest-paths> Don't tab-complete paths on the guest filesystem. It is useful to be diff --git a/fish/virt.c b/fish/virt.c new file mode 100644 index 0000000..9c4ce1a --- /dev/null +++ b/fish/virt.c @@ -0,0 +1,191 @@ +/* guestfish - the filesystem interactive shell + * Copyright (C) 2010 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> + +#include <libxml/xpath.h> +#include <libxml/parser.h> +#include <libxml/tree.h> + +#include "fish.h" + +static int add_drives_from_node_set (xmlDocPtr doc, xmlNodeSetPtr nodes); + +/* Implements the guts of the '-d' option. + * + * Note that we have to observe the '--ro' flag in two respects: by + * adding the drives read-only if the flag is set, and by restricting + * guests to shut down ones unless '--ro' is set. + * + * Returns the number of drives added (> 0), or -1 for failure. + */ +int +add_libvirt_drives (const char *guest) +{ + static int initialized = 0; + if (!initialized) { + initialized = 1; + + if (virInitialize () == -1) + return -1; + + xmlInitParser (); + LIBXML_TEST_VERSION; + } + + int r = -1, nr_added = 0; + virErrorPtr err; + virConnectPtr conn = NULL; + virDomainPtr dom = NULL; + xmlDocPtr doc = NULL; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + char *xml = NULL; + + /* Connect to libvirt, find the domain. */ + conn = virConnectOpenReadOnly (libvirt_uri); + if (!conn) { + err = virGetLastError (); + fprintf (stderr, _("guestfish: could not connect to libvirt (code %d, domain %d): %s\n"), + err->code, err->domain, err->message); + goto cleanup; + } + + dom = virDomainLookupByName (conn, guest); + if (!dom) { + err = virConnGetLastError (conn); + fprintf (stderr, _("guestfish: no libvirt domain called '%s': %s\n"), + guest, err->message); + goto cleanup; + } + if (!read_only) { + virDomainInfo info; + if (virDomainGetInfo (dom, &info) == -1) { + err = virConnGetLastError (conn); + fprintf (stderr, _("guestfish: error getting domain info about '%s': %s\n"), + guest, err->message); + goto cleanup; + } + if (info.state != VIR_DOMAIN_SHUTOFF) { + fprintf (stderr, _("guestfish: error: '%s' is a live virtual machine.\nYou must use '--ro' because write access to a running virtual machine can\ncause disk corruption.\n"), + guest); + goto cleanup; + } + } + + /* Domain XML. */ + xml = virDomainGetXMLDesc (dom, 0); + + if (!xml) { + err = virConnGetLastError (conn); + fprintf (stderr, _("guestfish: error reading libvirt XML information about '%s': %s\n"), + guest, err->message); + goto cleanup; + } + + /* Now the horrible task of parsing out the fields we need from the XML. + * http://www.xmlsoft.org/examples/xpath1.c + */ + doc = xmlParseMemory (xml, strlen (xml)); + if (doc == NULL) { + fprintf (stderr, _("guestfish: unable to parse XML information returned by libvirt\n")); + goto cleanup; + } + + xpathCtx = xmlXPathNewContext (doc); + if (xpathCtx == NULL) { + fprintf (stderr, _("guestfish: unable to create new XPath context\n")); + goto cleanup; + } + + xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk/source/@dev", + xpathCtx); + if (xpathObj == NULL) { + fprintf (stderr, _("guestfish: unable to evaluate XPath expression\n")); + goto cleanup; + } + + nr_added += add_drives_from_node_set (doc, xpathObj->nodesetval); + + xmlXPathFreeObject (xpathObj); xpathObj = NULL; + + xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk/source/@file", + xpathCtx); + if (xpathObj == NULL) { + fprintf (stderr, _("guestfish: unable to evaluate XPath expression\n")); + goto cleanup; + } + + nr_added += add_drives_from_node_set (doc, xpathObj->nodesetval); + + if (nr_added == 0) { + fprintf (stderr, _("guestfish: libvirt domain '%s' has no disks\n"), + guest); + goto cleanup; + } + + /* Successful. */ + r = nr_added; + +cleanup: + free (xml); + if (xpathObj) xmlXPathFreeObject (xpathObj); + if (xpathCtx) xmlXPathFreeContext (xpathCtx); + if (doc) xmlFreeDoc (doc); + if (dom) virDomainFree (dom); + if (conn) virConnectClose (conn); + + return r; +} + +static int +add_drives_from_node_set (xmlDocPtr doc, xmlNodeSetPtr nodes) +{ + if (!nodes) + return 0; + + int i; + + for (i = 0; i < nodes->nodeNr; ++i) { + assert (nodes->nodeTab[i]); + assert (nodes->nodeTab[i]->type == XML_ATTRIBUTE_NODE); + xmlAttrPtr attr = (xmlAttrPtr) nodes->nodeTab[i]; + + char *device = (char *) xmlNodeListGetString (doc, attr->children, 1); + + int r; + if (!read_only) + r = guestfs_add_drive (g, device); + else + r = guestfs_add_drive_ro (g, device); + if (r == -1) + exit (EXIT_FAILURE); + + xmlFree (device); + } + + return nodes->nodeNr; +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 8ce5c97..e463bbb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -85,6 +85,7 @@ fish/reopen.c fish/supported.c fish/tilde.c fish/time.c +fish/virt.c fuse/dircache.c fuse/guestmount.c inspector/virt-inspector.pl -- 1.7.1
Richard W.M. Jones
2010-Aug-02 17:14 UTC
[Libguestfs] [PATCH v3 5/5] fish: Reimplement -i option using new C-based inspection.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html -------------- next part -------------->From c81043ede1d98092361685da5759aea57800733c Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 2 Aug 2010 17:43:23 +0100 Subject: [PATCH 5/5] fish: Reimplement -i option using new C-based inspection. Don't shell out to virt-inspector. Instead, use the new C-based inspection APIs. This is much faster. The new syntax is slightly different: guestfish -a disk.img -i guestfish -d guest -i However, the old syntax still works. --- fish/Makefile.am | 1 + fish/fish.c | 154 +++++++++++++--------------------------------------- fish/fish.h | 3 + fish/guestfish.pod | 37 +++++++------ fish/inspect.c | 78 ++++++++++++++++++++++++++ po/POTFILES.in | 1 + 6 files changed, 139 insertions(+), 135 deletions(-) create mode 100644 fish/inspect.c diff --git a/fish/Makefile.am b/fish/Makefile.am index cd16733..9bc5b73 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -44,6 +44,7 @@ guestfish_SOURCES = \ fish.c \ fish.h \ glob.c \ + inspect.c \ lcd.c \ man.c \ more.c \ diff --git a/fish/fish.c b/fish/fish.c index 71b9827..9968119 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -81,7 +81,6 @@ static void cleanup_readline (void); #ifdef HAVE_LIBREADLINE static void add_history_line (const char *); #endif -static void print_shell_quote (FILE *stream, const char *str); /* Currently open libguestfs handle. */ guestfs_h *g; @@ -125,7 +124,7 @@ usage (int status) " -d|--domain guest Add disks from libvirt guest\n" " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" " -f|--file file Read commands from file\n" - " -i|--inspector Run virt-inspector to get disk mountpoints\n" + " -i|--inspector Automatically mount filesystems\n" " --keys-from-stdin Read passphrases from stdin\n" " --listen Listen for remote commands\n" " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" @@ -227,7 +226,7 @@ main (int argc, char *argv[]) * using it just above. * * getopt_long uses argv[0], so give it the sanitized name. Save a copy - * of the original, in case it's needed in virt-inspector mode, below. + * of the original, in case it's needed below. */ char *real_argv0 = argv[0]; argv[0] = bad_cast (program_name); @@ -397,115 +396,46 @@ main (int argc, char *argv[]) } } - /* Inspector mode invalidates most of the other arguments. */ - if (inspector) { - if (drvs || mps || remote_control_listen || remote_control || - guestfs_get_selinux (g)) { - fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, " - "--listen, --remote or --selinux\n"), - program_name); - exit (EXIT_FAILURE); - } - if (optind >= argc) { - fprintf (stderr, - _("%s: -i requires a libvirt domain or path(s) to disk image(s)\n"), - program_name); - exit (EXIT_FAILURE); - } - - char *cmd; - size_t cmdlen; - FILE *fp = open_memstream (&cmd, &cmdlen); - if (fp == NULL) { - perror ("open_memstream"); - exit (EXIT_FAILURE); - } - - fprintf (fp, "virt-inspector"); + /* Old-style -i syntax? Since -a/-d/-N and -i was disallowed + * previously, if we have -i without any drives but with something + * on the command line, it must be old-style syntax. + */ + if (inspector && drvs == NULL && optind < argc) { while (optind < argc) { - fputc (' ', fp); - print_shell_quote (fp, argv[optind]); - optind++; - } - - if (read_only) - fprintf (fp, " --ro-fish"); - else - fprintf (fp, " --fish"); - - if (fclose (fp) == -1) { - perror ("fclose"); - exit (EXIT_FAILURE); - } - - if (verbose) - fprintf (stderr, - "%s -i: running: %s\n", program_name, cmd); - - FILE *pp = popen (cmd, "r"); - if (pp == NULL) { - perror (cmd); - exit (EXIT_FAILURE); - } - - char *cmd2; - fp = open_memstream (&cmd2, &cmdlen); - if (fp == NULL) { - perror ("open_memstream"); - exit (EXIT_FAILURE); - } - - fprintf (fp, "%s", real_argv0); - - if (guestfs_get_verbose (g)) - fprintf (fp, " -v"); - if (!guestfs_get_autosync (g)) - fprintf (fp, " -n"); - if (guestfs_get_trace (g)) - fprintf (fp, " -x"); - - char *insp = NULL; - size_t insplen; - if (getline (&insp, &insplen, pp) == -1) { - perror (cmd); - exit (EXIT_FAILURE); - } - fprintf (fp, " %s", insp); - - if (pclose (pp) == -1) { - perror (cmd); - exit (EXIT_FAILURE); - } - - if (fclose (fp) == -1) { - perror ("fclose"); - exit (EXIT_FAILURE); - } - - if (verbose) - fprintf (stderr, - "%s -i: running: %s\n", program_name, cmd2); + if (strchr (argv[optind], '/') || + access (argv[optind], F_OK) == 0) { /* simulate -a option */ + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + drv->type = drv_a; + drv->a.filename = argv[optind]; + drv->next = drvs; + drvs = drv; + } else { /* simulate -d option */ + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + drv->type = drv_d; + drv->d.guest = argv[optind]; + drv->next = drvs; + drvs = drv; + } - int r = system (cmd2); - if (r == -1) { - perror (cmd2); - exit (EXIT_FAILURE); + optind++; } - - free (cmd); - free (cmd2); - free (insp); - - exit (WEXITSTATUS (r)); } /* If we've got drives to add, add them now. */ add_drives (drvs, 'a'); - /* If we've got mountpoints or prepared drives, we must launch the - * guest and mount them. + /* If we've got mountpoints or prepared drives or -i option, we must + * launch the guest and mount them. */ - if (next_prepared_drive > 1 || mps != NULL) { + if (next_prepared_drive > 1 || mps != NULL || inspector) { /* RHBZ#612178: If --listen flag is given, then we will fork into * the background in rc_listen(). However you can't do this while * holding a libguestfs handle open because the recovery process @@ -518,6 +448,10 @@ main (int argc, char *argv[]) guestfs_set_recovery_proc (g, 0); if (launch () == -1) exit (EXIT_FAILURE); + + if (inspector) + inspect_mount (); + prepare_drives (drvs); mount_mps (mps); } @@ -1839,17 +1773,3 @@ read_key (const char *param) return ret; } - -static void -print_shell_quote (FILE *stream, const char *str) -{ -#define SAFE(c) (c_isalnum((c)) || \ - (c) == '/' || (c) == '-' || (c) == '_' || (c) == '.') - int i; - - for (i = 0; str[i]; ++i) { - if (!SAFE(str[i])) - putc ('\\', stream); - putc (str[i], stream); - } -} diff --git a/fish/fish.h b/fish/fish.h index bf1f81c..6f8ccd0 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -96,6 +96,9 @@ extern int do_echo (const char *cmd, int argc, char *argv[]); /* in edit.c */ extern int do_edit (const char *cmd, int argc, char *argv[]); +/* in inspect.c */ +extern void inspect_mount (void); + /* in lcd.c */ extern int do_lcd (const char *cmd, int argc, char *argv[]); diff --git a/fish/guestfish.pod b/fish/guestfish.pod index 8daebc8..cf1140a 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -16,9 +16,9 @@ guestfish - the libguestfs Filesystem Interactive SHell guestfish -d libvirt-domain - guestfish -i libvirt-domain + guestfish -a disk.img -i - guestfish -i disk.img [disk.img ...] + guestfish -d libvirt-domain -i =head1 WARNING @@ -75,13 +75,14 @@ Edit C</boot/grub/grub.conf> interactively: --mount /dev/sda1:/boot \ edit /boot/grub/grub.conf -=head2 Using virt-inspector +=head2 Mount disks automatically -Use the I<-i> option to get virt-inspector to mount -the filesystems automatically as they would be mounted -in the virtual machine: +Use the I<-i> option to automatically mount the +disks from a virtual machine: - guestfish --ro -i disk.img cat /etc/group + guestfish --ro -a disk.img -i cat /etc/group + + guestfish --ro -d libvirt-domain -i cat /etc/group =head2 As a script interpreter @@ -170,28 +171,28 @@ scripts, use: =item B<-i> | B<--inspector> -Run virt-inspector on the named libvirt domain or list of disk -images. If virt-inspector is available and if it can identify -the domain or disk images, then partitions will be mounted -correctly at start-up. +Using L<virt-inspector(1)> code, inspect the disks looking for +an operating system and mount filesystems as they would be +mounted on the real virtual machine. Typical usage is either: - guestfish -i myguest + guestfish -d myguest -i (for an inactive libvirt domain called I<myguest>), or: - guestfish --ro -i myguest + guestfish --ro -d myguest -i (for active domains, readonly), or specify the block device directly: - guestfish -i /dev/Guests/MyGuest + guestfish -a /dev/Guests/MyGuest -i + +Note that the command line syntax changed slightly over older +versions of guestfish. You can still use the old syntax: -You cannot use I<-a>, I<-m>, I<-N>, I<--listen>, I<--remote> or -I<--selinux> in conjunction with this option, and options other than -I<--ro> might not behave correctly. + guestfish [--ro] -i disk.img -See also: L<virt-inspector(1)>. + guestfish [--ro] -i libvirt-domain =item B<--keys-from-stdin> diff --git a/fish/inspect.c b/fish/inspect.c new file mode 100644 index 0000000..3d46e1d --- /dev/null +++ b/fish/inspect.c @@ -0,0 +1,78 @@ +/* guestfish - the filesystem interactive shell + * Copyright (C) 2010 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "fish.h" + +static int +compare_keys (const void *p1, const void *p2) +{ + const char *key1 = * (char * const *) p1; + const char *key2 = * (char * const *) p2; + return strlen (key1) - strlen (key2); +} + +void +inspect_mount (void) +{ + char **roots = guestfs_inspect_os (g); + if (roots == NULL) + exit (EXIT_FAILURE); + + if (roots[0] == NULL) { + fprintf (stderr, _("guestfish: no operating system was found on this disk\n")); + exit (EXIT_FAILURE); + } + + if (roots[1] != NULL) { + fprintf (stderr, _("guestfish: multi-boot operating systems are not supported by the -i option\n")); + exit (EXIT_FAILURE); + } + + char *root = roots[0]; + free (roots); + + char **mountpoints = guestfs_inspect_get_mountpoints (g, root); + if (mountpoints == NULL) + exit (EXIT_FAILURE); + + /* Sort by key length, shortest key first, so that we end up + * mounting the filesystems in the correct order. + */ + qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *), + compare_keys); + + size_t i; + for (i = 0; mountpoints[i] != NULL; i += 2) { + int r; + if (!read_only) + r = guestfs_mount_options (g, "", mountpoints[i+1], mountpoints[i]); + else + r = guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]); + if (r == -1) + exit (EXIT_FAILURE); + } + + free (root); + free_strings (mountpoints); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index e463bbb..843e8e0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -76,6 +76,7 @@ fish/echo.c fish/edit.c fish/fish.c fish/glob.c +fish/inspect.c fish/lcd.c fish/man.c fish/more.c -- 1.7.1