Previously discussed here: https://www.redhat.com/archives/libguestfs/2010-August/msg00002.html -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/
Richard W.M. Jones
2010-Aug-17 11:02 UTC
[Libguestfs] [PATCH v4 1/8] Add safe_strndup call.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 11b218e89b620940bd374630600acce508e24b15 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Tue, 17 Aug 2010 11:26:26 +0100 Subject: [PATCH 1/8] Add safe_strndup call. --- src/guestfs-internal.h | 2 ++ src/guestfs.c | 8 ++++++++ 2 files changed, 10 insertions(+), 0 deletions(-) diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 12ca0ec..d0fc3ca 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -141,6 +141,7 @@ extern void guestfs_perrorf (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3))); extern void *guestfs_safe_realloc (guestfs_h *g, void *ptr, int nbytes); extern char *guestfs_safe_strdup (guestfs_h *g, const char *str); +extern char *guestfs_safe_strndup (guestfs_h *g, const char *str, size_t n); extern void *guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size); extern int guestfs___set_busy (guestfs_h *g); extern int guestfs___end_busy (guestfs_h *g); @@ -157,6 +158,7 @@ extern int guestfs___accept_from_daemon (guestfs_h *g); #define safe_malloc guestfs_safe_malloc #define safe_realloc guestfs_safe_realloc #define safe_strdup guestfs_safe_strdup +#define safe_strndup guestfs_safe_strndup #define safe_memdup guestfs_safe_memdup #endif /* GUESTFS_INTERNAL_H_ */ diff --git a/src/guestfs.c b/src/guestfs.c index c54462d..871d713 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -397,6 +397,14 @@ guestfs_safe_strdup (guestfs_h *g, const char *str) return s; } +char * +guestfs_safe_strndup (guestfs_h *g, const char *str, size_t n) +{ + char *s = strndup (str, n); + if (!s) g->abort_cb (); + return s; +} + void * guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size) { -- 1.7.1
Richard W.M. Jones
2010-Aug-17 11:03 UTC
[Libguestfs] [PATCH v4 2/8] generator: No need to redefine safe_* macros.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v -------------- next part -------------->From d1b4b88f7d340bf653a8d9e851a568bf50904805 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Tue, 17 Aug 2010 11:26:54 +0100 Subject: [PATCH 2/8] generator: No need to redefine safe_* macros. These are already defined in "guestfs-internal.h" which is included in the .c file. --- src/generator.ml | 7 ------- 1 files changed, 0 insertions(+), 7 deletions(-) diff --git a/src/generator.ml b/src/generator.ml index 52e7aba..398f64a 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -6059,13 +6059,6 @@ and generate_client_actions () #include \"guestfs-internal-actions.h\" #include \"guestfs_protocol.h\" -#define error guestfs_error -//#define perrorf guestfs_perrorf -#define safe_malloc guestfs_safe_malloc -#define safe_realloc guestfs_safe_realloc -//#define safe_strdup guestfs_safe_strdup -#define safe_memdup guestfs_safe_memdup - /* Check the return message from a call for validity. */ static int check_reply_header (guestfs_h *g, -- 1.7.1
Richard W.M. Jones
2010-Aug-17 11:03 UTC
[Libguestfs] [PATCH v4 3/8] New API: file-architecture
This contains the changes that were noted in the patch review. Rich. -- 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 d3ae7fcad80fd6c2c973184bed45ab0d3edf705d Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 28 Jul 2010 15:38:57 +0100 Subject: [PATCH 3/8] 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 | 4 + configure.ac | 19 +++ 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 | 271 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 428 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..867bc56 100644 --- a/README +++ b/README @@ -48,6 +48,10 @@ Requirements - XDR, rpcgen (on Linux these are provided by glibc) +- pcre (Perl Compatible Regular Expressions C library) + +- libmagic (the library that corresponds to the 'file' command) + - squashfs-tools (mksquashfs only) - genisoimage / mkisofs diff --git a/configure.ac b/configure.ac index a14dfd9..8cb7761 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"]) @@ -449,6 +458,16 @@ dnl For i18n. AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION([0.17]) +dnl libmagic (required) +AC_CHECK_LIB([magic],[magic_file],[ + AC_SUBST([LIBMAGIC], ["-lmagic"]) + ],[ + AC_MSG_FAILURE([libmagic is required]) + ]) +AC_CHECK_HEADER([magic.h],[],[ + AC_MSG_FAILURE([magic.h header file is required]) + ]) + 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/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..61cec04 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) $(LIBMAGIC) $(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 398f64a..dadda63 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..fa5942c --- /dev/null +++ b/src/inspect.c @@ -0,0 +1,271 @@ +/* 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 <magic.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_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 (guestfs_h *g, 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, sizeof vec / sizeof vec[0]); + 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 (guestfs_h *g, 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, sizeof vec / sizeof vec[0]); + 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 safe_strndup (g, &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 (g, 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. + * Notes: + * (1) Two lists must be identical. + * (2) Implicit limit of 31 bytes for length of each element (see code + * below). + */ +#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 (sizeof dir) + 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)) { + magic_t m = magic_open (g->verbose ? MAGIC_DEBUG : MAGIC_NONE); + if (m == NULL) { + perrorf (g, "magic_open"); + goto out; + } + + const char *line = magic_file (m, bin); + if (line == NULL) { + perrorf (g, "magic_file: %s", bin); + magic_close (m); + goto out; + } + + magic_close (m); + + char *elf_arch; + if ((elf_arch = match1 (g, 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 (g, 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 (strstr (file, "PE32+ executable")) + 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-17 11:04 UTC
[Libguestfs] [PATCH v4 4/8] New APIs: findfs-label and findfs-uuid
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v -------------- next part -------------->From 2c7954032ec259c7684fc9439f98094dacdc1af8 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 28 Jul 2010 23:11:38 +0100 Subject: [PATCH 4/8] 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 dadda63..c81411f 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-17 11:04 UTC
[Libguestfs] [PATCH v4 5/8] New APIs for guest inspection.
-- 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 -------------- next part -------------->From 91e41b1c7fa5dd734eeab7e5aec439b61657f475 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Wed, 28 Jul 2010 15:40:42 +0100 Subject: [PATCH 5/8] 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 | 1017 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1360 insertions(+), 5 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 61cec04..cc01459 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -131,7 +131,7 @@ libguestfs_la_SOURCES = \ proto.c \ libguestfs.syms -libguestfs_la_LIBADD = $(LIBPCRE) $(LIBMAGIC) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la +libguestfs_la_LIBADD = $(HIVEX_LIBS) $(LIBPCRE) $(LIBMAGIC) $(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 c81411f..8dfada0 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 d0fc3ca..9bf76da 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; @@ -143,6 +196,7 @@ extern void *guestfs_safe_realloc (guestfs_h *g, void *ptr, int nbytes); extern char *guestfs_safe_strdup (guestfs_h *g, const char *str); extern char *guestfs_safe_strndup (guestfs_h *g, const char *str, size_t n); 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 871d713..cef80db 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 fa5942c..96b00c1 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -28,8 +28,11 @@ #include <pcre.h> #include <magic.h> +#include <hivex.h> +#include <augeas.h> #include "ignore-value.h" +#include "xstrtol.h" #include "guestfs.h" #include "guestfs-internal.h" @@ -44,6 +47,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) COMPILE (re_file_elf, "ELF.*(?:executable|shared object|relocatable), (.+?),", 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 (guestfs_h *g, const char *str, const pcre *re) return safe_strndup (g, &str[vec[2]], vec[3]-vec[2]); } +/* Match a regular expression which contains exactly two captures. */ +static int +match2 (guestfs_h *g, 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 = safe_strndup (g, &str[vec[2]], vec[3]-vec[2]); + *ret2 = safe_strndup (g, &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. */ @@ -269,3 +314,975 @@ guestfs__file_architecture (guestfs_h *g, const char *path) free (elf_arch); return ret; /* caller frees */ } + +/* The main inspection code. */ +static int feature_available (guestfs_h *g, const char *feature); +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. */ + if (feature_available (g, "lvm2")) { + 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); +} + +/* In the Perl code this is a public function. */ +static int +feature_available (guestfs_h *g, const char *feature) +{ + /* 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; + + const char *groups[] = { feature, NULL }; + int r = guestfs_available (g, (char * const *) groups); + + g->error_cb = old_error_cb; + + return r == 0 ? 1 : 0; +} + +/* 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 (g, 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 (g, fs->product_name, re_rhel_old, &major, &minor) || + match2 (g, 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 (g, 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 (g, 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. + * XXX What if !feature_available (g, "augeas")? + */ + 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 (g, 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 (g, 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 (g, 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-17 11:04 UTC
[Libguestfs] [PATCH v4 6/8] fish: Add -c/--connect and -d/--domain options.
-- 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 1d85a81a1fb22e3322b3a526db4c7b7c344cd419 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 2 Aug 2010 16:33:25 +0100 Subject: [PATCH 6/8] 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 | 126 ++++++++++++++++++++++++++--------- fish/fish.h | 5 ++ fish/guestfish.pod | 14 ++++ fish/virt.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + 8 files changed, 326 insertions(+), 33 deletions(-) create mode 100644 fish/virt.c diff --git a/README b/README index 867bc56..15e6581 100644 --- a/README +++ b/README @@ -52,6 +52,10 @@ Requirements - libmagic (the library that corresponds to the 'file' command) +- libvirt + +- libxml2 + - squashfs-tools (mksquashfs only) - genisoimage / mkisofs diff --git a/configure.ac b/configure.ac index 8cb7761..649ba7d 100644 --- a/configure.ac +++ b/configure.ac @@ -468,6 +468,16 @@ AC_CHECK_HEADER([magic.h],[],[ AC_MSG_FAILURE([magic.h header file is required]) ]) +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..bc7d96c 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,8 @@ 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" + " -c|--connect uri Specify libvirt URI for -d option\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 +160,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 +191,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 +278,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 +310,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 +501,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 +609,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 +670,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-17 11:05 UTC
[Libguestfs] [PATCH v4 7/8] fish: Reimplement -i option using new C-based inspection.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From d614bb637e277161b20b3a3ffc26f27937f8c379 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 2 Aug 2010 17:43:23 +0100 Subject: [PATCH 7/8] 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 | 164 ++++++++++++++------------------------------------- fish/fish.h | 4 + fish/guestfish.pod | 37 ++++++------ fish/inspect.c | 117 +++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + 6 files changed, 187 insertions(+), 137 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 bc7d96c..a896a92 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; @@ -95,6 +94,7 @@ int exit_on_error = 1; int command_num = 0; int keys_from_stdin = 0; const char *libvirt_uri = NULL; +int inspector = 0; static void __attribute__((noreturn)) usage (int status) @@ -126,7 +126,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" @@ -188,7 +188,6 @@ main (int argc, char *argv[]) struct mp *mp; char *p, *file = NULL; int c; - int inspector = 0; int option_index; struct sigaction sa; int next_prepared_drive = 1; @@ -228,7 +227,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); @@ -398,115 +397,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 @@ -519,6 +449,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); } @@ -747,7 +681,7 @@ script (int prompt) int global_exit_on_error = !prompt; int tilde_candidate; - if (prompt) + if (prompt) { printf (_("\n" "Welcome to guestfish, the libguestfs filesystem interactive shell for\n" "editing virtual machine filesystems.\n" @@ -757,6 +691,12 @@ script (int prompt) " 'quit' to quit the shell\n" "\n")); + if (inspector) { + print_inspect_prompt (); + printf ("\n"); + } + } + while (!quit) { char *pipe = NULL; @@ -1840,17 +1780,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..660b8ee 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -96,6 +96,10 @@ 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); +extern void print_inspect_prompt (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..00ebf7f --- /dev/null +++ b/fish/inspect.c @@ -0,0 +1,117 @@ +/* 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" + +/* Global that saves the root device between inspect_mount and + * print_inspect_prompt. + */ +static char *root = NULL; + +static int +compare_keys_len (const void *p1, const void *p2) +{ + const char *key1 = * (char * const *) p1; + const char *key2 = * (char * const *) p2; + return strlen (key1) - strlen (key2); +} + +static int +compare_keys (const void *p1, const void *p2) +{ + const char *key1 = * (char * const *) p1; + const char *key2 = * (char * const *) p2; + return strcasecmp (key1, key2); +} + +/* This function implements the -i option. */ +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); + } + + 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_len); + + 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_strings (mountpoints); +} + +/* This function is called only if the above function was called, + * and only after we've printed the prompt in interactive mode. + */ +void +print_inspect_prompt (void) +{ + char *name = guestfs_inspect_get_product_name (g, root); + if (STRNEQ (name, "unknown")) + printf ("%s\n", name); + free (name); + + char **mountpoints = guestfs_inspect_get_mountpoints (g, root); + if (mountpoints == NULL) + return; + + /* Sort by key. */ + qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *), + compare_keys); + + size_t i; + for (i = 0; mountpoints[i] != NULL; i += 2) + printf (_(" %30s mounted on %s\n"), mountpoints[i+1], mountpoints[i]); + + 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
Richard W.M. Jones
2010-Aug-17 11:05 UTC
[Libguestfs] [PATCH v4 8/8] Remove old ocaml-inspector code.
-- 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 e470532da2dc1ae814a23d4ba63441046e3018a0 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Mon, 2 Aug 2010 23:29:43 +0100 Subject: [PATCH 8/8] Remove old ocaml-inspector code. Not used by anyone, didn't work well, and replaced now by the C inspection APIs. --- ocaml/Makefile.am | 13 +- ocaml/t/guestfs_500_inspect.ml | 42 ---- src/generator.ml | 490 ---------------------------------------- 3 files changed, 3 insertions(+), 542 deletions(-) delete mode 100644 ocaml/t/guestfs_500_inspect.ml diff --git a/ocaml/Makefile.am b/ocaml/Makefile.am index 99bb390..5c7b929 100644 --- a/ocaml/Makefile.am +++ b/ocaml/Makefile.am @@ -20,15 +20,12 @@ include $(top_srcdir)/subdir-rules.mk generator_built = \ guestfs.mli \ guestfs.ml \ - guestfs_inspector.mli \ - guestfs_inspector.ml \ guestfs_c_actions.c \ bindtests.ml EXTRA_DIST = \ $(generator_built) \ guestfs_c.c guestfs_c.h \ - guestfs_inspector.mli guestfs_inspector.ml \ .depend META.in \ run-bindtests \ t/*.ml @@ -45,7 +42,7 @@ if HAVE_XML_LIGHT noinst_DATA = mlguestfs.cma mlguestfs.cmxa META -OBJS = guestfs_c.o guestfs_c_actions.o guestfs.cmo guestfs_inspector.cmo +OBJS = guestfs_c.o guestfs_c_actions.o guestfs.cmo XOBJS = $(OBJS:.cmo=.cmx) mlguestfs.cma: $(OBJS) @@ -67,10 +64,10 @@ TESTS_ENVIRONMENT = \ TESTS = run-bindtests \ t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \ - t/guestfs_060_readdir t/guestfs_070_threads t/guestfs_500_inspect + t/guestfs_060_readdir t/guestfs_070_threads noinst_DATA += bindtests \ t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \ - t/guestfs_060_readdir t/guestfs_070_threads t/guestfs_500_inspect + t/guestfs_060_readdir t/guestfs_070_threads bindtests: bindtests.cmx mlguestfs.cmxa mkdir -p t @@ -96,10 +93,6 @@ t/guestfs_070_threads: t/guestfs_070_threads.cmx mlguestfs.cmxa mkdir -p t $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package unix,threads -thread -linkpkg mlguestfs.cmxa $< -o $@ -t/guestfs_500_inspect: t/guestfs_500_inspect.cmx mlguestfs.cmxa - mkdir -p t - $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package xml-light,unix -linkpkg mlguestfs.cmxa $< -o $@ - # Need to rebuild the tests from source if the main library has # changed at all, otherwise we get inconsistent assumptions. t/guestfs_070_threads.cmx: t/guestfs_070_threads.ml mlguestfs.cmxa diff --git a/ocaml/t/guestfs_500_inspect.ml b/ocaml/t/guestfs_500_inspect.ml deleted file mode 100644 index ec1071a..0000000 --- a/ocaml/t/guestfs_500_inspect.ml +++ /dev/null @@ -1,42 +0,0 @@ -(* libguestfs OCaml bindings - * 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. - *) - -open Unix - -let (//) = Filename.concat -let dotdot = Filename.parent_dir_name - -let read_file name - let chan = open_in name in - let lines = ref [] in - let lines - try while true do lines := input_line chan :: !lines done; [] - with End_of_file -> List.rev !lines in - close_in chan; - String.concat "" lines - -let parse name - let xml = read_file name in - let os = Guestfs_inspector.inspect ~xml [] in - os - -let () - ignore (parse (dotdot // "inspector" // "example1.xml")); - ignore (parse (dotdot // "inspector" // "example2.xml")); - ignore (parse (dotdot // "inspector" // "example3.xml")); - ignore (parse (dotdot // "inspector" // "example4.xml")) diff --git a/src/generator.ml b/src/generator.ml index 8dfada0..631dcda 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -12182,494 +12182,6 @@ and generate_lang_bindtests call (* XXX Add here tests of the return and error functions. *) -(* Code to generator bindings for virt-inspector. Currently only - * implemented for OCaml code (for virt-p2v 2.0). - *) -let rng_input = "inspector/virt-inspector.rng" - -(* Read the input file and parse it into internal structures. This is - * by no means a complete RELAX NG parser, but is just enough to be - * able to parse the specific input file. - *) -type rng - | Element of string * rng list (* <element name=name/> *) - | Attribute of string * rng list (* <attribute name=name/> *) - | Interleave of rng list (* <interleave/> *) - | ZeroOrMore of rng (* <zeroOrMore/> *) - | OneOrMore of rng (* <oneOrMore/> *) - | Optional of rng (* <optional/> *) - | Choice of string list (* <choice><value/>*</choice> *) - | Value of string (* <value>str</value> *) - | Text (* <text/> *) - -let rec string_of_rng = function - | Element (name, xs) -> - "Element (\"" ^ name ^ "\", (" ^ string_of_rng_list xs ^ "))" - | Attribute (name, xs) -> - "Attribute (\"" ^ name ^ "\", (" ^ string_of_rng_list xs ^ "))" - | Interleave xs -> "Interleave (" ^ string_of_rng_list xs ^ ")" - | ZeroOrMore rng -> "ZeroOrMore (" ^ string_of_rng rng ^ ")" - | OneOrMore rng -> "OneOrMore (" ^ string_of_rng rng ^ ")" - | Optional rng -> "Optional (" ^ string_of_rng rng ^ ")" - | Choice values -> "Choice [" ^ String.concat ", " values ^ "]" - | Value value -> "Value \"" ^ value ^ "\"" - | Text -> "Text" - -and string_of_rng_list xs - String.concat ", " (List.map string_of_rng xs) - -let rec parse_rng ?defines context = function - | [] -> [] - | Xml.Element ("element", ["name", name], children) :: rest -> - Element (name, parse_rng ?defines context children) - :: parse_rng ?defines context rest - | Xml.Element ("attribute", ["name", name], children) :: rest -> - Attribute (name, parse_rng ?defines context children) - :: parse_rng ?defines context rest - | Xml.Element ("interleave", [], children) :: rest -> - Interleave (parse_rng ?defines context children) - :: parse_rng ?defines context rest - | Xml.Element ("zeroOrMore", [], [child]) :: rest -> - let rng = parse_rng ?defines context [child] in - (match rng with - | [child] -> ZeroOrMore child :: parse_rng ?defines context rest - | _ -> - failwithf "%s: <zeroOrMore> contains more than one child element" - context - ) - | Xml.Element ("oneOrMore", [], [child]) :: rest -> - let rng = parse_rng ?defines context [child] in - (match rng with - | [child] -> OneOrMore child :: parse_rng ?defines context rest - | _ -> - failwithf "%s: <oneOrMore> contains more than one child element" - context - ) - | Xml.Element ("optional", [], [child]) :: rest -> - let rng = parse_rng ?defines context [child] in - (match rng with - | [child] -> Optional child :: parse_rng ?defines context rest - | _ -> - failwithf "%s: <optional> contains more than one child element" - context - ) - | Xml.Element ("choice", [], children) :: rest -> - let values = List.map ( - function Xml.Element ("value", [], [Xml.PCData value]) -> value - | _ -> - failwithf "%s: can't handle anything except <value> in <choice>" - context - ) children in - Choice values - :: parse_rng ?defines context rest - | Xml.Element ("value", [], [Xml.PCData value]) :: rest -> - Value value :: parse_rng ?defines context rest - | Xml.Element ("text", [], []) :: rest -> - Text :: parse_rng ?defines context rest - | Xml.Element ("ref", ["name", name], []) :: rest -> - (* Look up the reference. Because of limitations in this parser, - * we can't handle arbitrarily nested <ref> yet. You can only - * use <ref> from inside <start>. - *) - (match defines with - | None -> - failwithf "%s: contains <ref>, but no refs are defined yet" context - | Some map -> - let rng = StringMap.find name map in - rng @ parse_rng ?defines context rest - ) - | x :: _ -> - failwithf "%s: can't handle '%s' in schema" context (Xml.to_string x) - -let grammar - let xml = Xml.parse_file rng_input in - match xml with - | Xml.Element ("grammar", _, - Xml.Element ("start", _, gram) :: defines) -> - (* The <define/> elements are referenced in the <start> section, - * so build a map of those first. - *) - let defines = List.fold_left ( - fun map -> - function Xml.Element ("define", ["name", name], defn) -> - StringMap.add name defn map - | _ -> - failwithf "%s: expected <define name=name/>" rng_input - ) StringMap.empty defines in - let defines = StringMap.mapi parse_rng defines in - - (* Parse the <start> clause, passing the defines. *) - parse_rng ~defines "<start>" gram - | _ -> - failwithf "%s: input is not <grammar><start/><define>*</grammar>" - rng_input - -let name_of_field = function - | Element (name, _) | Attribute (name, _) - | ZeroOrMore (Element (name, _)) - | OneOrMore (Element (name, _)) - | Optional (Element (name, _)) -> name - | Optional (Attribute (name, _)) -> name - | Text -> (* an unnamed field in an element *) - "data" - | rng -> - failwithf "name_of_field failed at: %s" (string_of_rng rng) - -(* At the moment this function only generates OCaml types. However we - * should parameterize it later so it can generate types/structs in a - * variety of languages. - *) -let generate_types xs - (* A simple type is one that can be printed out directly, eg. - * "string option". A complex type is one which has a name and has - * to be defined via another toplevel definition, eg. a struct. - * - * generate_type generates code for either simple or complex types. - * In the simple case, it returns the string ("string option"). In - * the complex case, it returns the name ("mountpoint"). In the - * complex case it has to print out the definition before returning, - * so it should only be called when we are at the beginning of a - * new line (BOL context). - *) - let rec generate_type = function - | Text -> (* string *) - "string", true - | Choice values -> (* [`val1|`val2|...] *) - "[" ^ String.concat "|" (List.map ((^)"`") values) ^ "]", true - | ZeroOrMore rng -> (* <rng> list *) - let t, is_simple = generate_type rng in - t ^ " list (* 0 or more *)", is_simple - | OneOrMore rng -> (* <rng> list *) - let t, is_simple = generate_type rng in - t ^ " list (* 1 or more *)", is_simple - (* virt-inspector hack: bool *) - | Optional (Attribute (name, [Value "1"])) -> - "bool", true - | Optional rng -> (* <rng> list *) - let t, is_simple = generate_type rng in - t ^ " option", is_simple - (* type name = { fields ... } *) - | Element (name, fields) when is_attrs_interleave fields -> - generate_type_struct name (get_attrs_interleave fields) - | Element (name, [field]) (* type name = field *) - | Attribute (name, [field]) -> - let t, is_simple = generate_type field in - if is_simple then (t, true) - else ( - pr "type %s = %s\n" name t; - name, false - ) - | Element (name, fields) -> (* type name = { fields ... } *) - generate_type_struct name fields - | rng -> - failwithf "generate_type failed at: %s" (string_of_rng rng) - - and is_attrs_interleave = function - | [Interleave _] -> true - | Attribute _ :: fields -> is_attrs_interleave fields - | Optional (Attribute _) :: fields -> is_attrs_interleave fields - | _ -> false - - and get_attrs_interleave = function - | [Interleave fields] -> fields - | ((Attribute _) as field) :: fields - | ((Optional (Attribute _)) as field) :: fields -> - field :: get_attrs_interleave fields - | _ -> assert false - - and generate_types xs - List.iter (fun x -> ignore (generate_type x)) xs - - and generate_type_struct name fields - (* Calculate the types of the fields first. We have to do this - * before printing anything so we are still in BOL context. - *) - let types = List.map fst (List.map generate_type fields) in - - (* Special case of a struct containing just a string and another - * field. Turn it into an assoc list. - *) - match types with - | ["string"; other] -> - let fname1, fname2 - match fields with - | [f1; f2] -> name_of_field f1, name_of_field f2 - | _ -> assert false in - pr "type %s = string * %s (* %s -> %s *)\n" name other fname1 fname2; - name, false - - | types -> - pr "type %s = {\n" name; - List.iter ( - fun (field, ftype) -> - let fname = name_of_field field in - pr " %s_%s : %s;\n" name fname ftype - ) (List.combine fields types); - pr "}\n"; - (* Return the name of this type, and - * false because it's not a simple type. - *) - name, false - in - - generate_types xs - -let generate_parsers xs - (* As for generate_type above, generate_parser makes a parser for - * some type, and returns the name of the parser it has generated. - * Because it (may) need to print something, it should always be - * called in BOL context. - *) - let rec generate_parser = function - | Text -> (* string *) - "string_child_or_empty" - | Choice values -> (* [`val1|`val2|...] *) - sprintf "(fun x -> match Xml.pcdata (first_child x) with %s | str -> failwith (\"unexpected field value: \" ^ str))" - (String.concat "|" - (List.map (fun v -> sprintf "%S -> `%s" v v) values)) - | ZeroOrMore rng -> (* <rng> list *) - let pa = generate_parser rng in - sprintf "(fun x -> List.map %s (Xml.children x))" pa - | OneOrMore rng -> (* <rng> list *) - let pa = generate_parser rng in - sprintf "(fun x -> List.map %s (Xml.children x))" pa - (* virt-inspector hack: bool *) - | Optional (Attribute (name, [Value "1"])) -> - sprintf "(fun x -> try ignore (Xml.attrib x %S); true with Xml.No_attribute _ -> false)" name - | Optional rng -> (* <rng> list *) - let pa = generate_parser rng in - sprintf "(function None -> None | Some x -> Some (%s x))" pa - (* type name = { fields ... } *) - | Element (name, fields) when is_attrs_interleave fields -> - generate_parser_struct name (get_attrs_interleave fields) - | Element (name, [field]) -> (* type name = field *) - let pa = generate_parser field in - let parser_name = sprintf "parse_%s_%d" name (unique ()) in - pr "let %s =\n" parser_name; - pr " %s\n" pa; - pr "let parse_%s = %s\n" name parser_name; - parser_name - | Attribute (name, [field]) -> - let pa = generate_parser field in - let parser_name = sprintf "parse_%s_%d" name (unique ()) in - pr "let %s =\n" parser_name; - pr " %s\n" pa; - pr "let parse_%s = %s\n" name parser_name; - parser_name - | Element (name, fields) -> (* type name = { fields ... } *) - generate_parser_struct name ([], fields) - | rng -> - failwithf "generate_parser failed at: %s" (string_of_rng rng) - - and is_attrs_interleave = function - | [Interleave _] -> true - | Attribute _ :: fields -> is_attrs_interleave fields - | Optional (Attribute _) :: fields -> is_attrs_interleave fields - | _ -> false - - and get_attrs_interleave = function - | [Interleave fields] -> [], fields - | ((Attribute _) as field) :: fields - | ((Optional (Attribute _)) as field) :: fields -> - let attrs, interleaves = get_attrs_interleave fields in - (field :: attrs), interleaves - | _ -> assert false - - and generate_parsers xs - List.iter (fun x -> ignore (generate_parser x)) xs - - and generate_parser_struct name (attrs, interleaves) - (* Generate parsers for the fields first. We have to do this - * before printing anything so we are still in BOL context. - *) - let fields = attrs @ interleaves in - let pas = List.map generate_parser fields in - - (* Generate an intermediate tuple from all the fields first. - * If the type is just a string + another field, then we will - * return this directly, otherwise it is turned into a record. - * - * RELAX NG note: This code treats <interleave> and plain lists of - * fields the same. In other words, it doesn't bother enforcing - * any ordering of fields in the XML. - *) - pr "let parse_%s x =\n" name; - pr " let t = (\n "; - let comma = ref false in - List.iter ( - fun x -> - if !comma then pr ",\n "; - comma := true; - match x with - | Optional (Attribute (fname, [field])), pa -> - pr "%s x" pa - | Optional (Element (fname, [field])), pa -> - pr "%s (optional_child %S x)" pa fname - | Attribute (fname, [Text]), _ -> - pr "attribute %S x" fname - | (ZeroOrMore _ | OneOrMore _), pa -> - pr "%s x" pa - | Text, pa -> - pr "%s x" pa - | (field, pa) -> - let fname = name_of_field field in - pr "%s (child %S x)" pa fname - ) (List.combine fields pas); - pr "\n ) in\n"; - - (match fields with - | [Element (_, [Text]) | Attribute (_, [Text]); _] -> - pr " t\n" - - | _ -> - pr " (Obj.magic t : %s)\n" name -(* - List.iter ( - function - | (Optional (Attribute (fname, [field])), pa) -> - pr " %s_%s =\n" name fname; - pr " %s x;\n" pa - | (Optional (Element (fname, [field])), pa) -> - pr " %s_%s =\n" name fname; - pr " (let x = optional_child %S x in\n" fname; - pr " %s x);\n" pa - | (field, pa) -> - let fname = name_of_field field in - pr " %s_%s =\n" name fname; - pr " (let x = child %S x in\n" fname; - pr " %s x);\n" pa - ) (List.combine fields pas); - pr "}\n" -*) - ); - sprintf "parse_%s" name - in - - generate_parsers xs - -(* Generate ocaml/guestfs_inspector.mli. *) -let generate_ocaml_inspector_mli () - generate_header ~extra_inputs:[rng_input] OCamlStyle LGPLv2plus; - - pr "\ -(** This is an OCaml language binding to the external [virt-inspector] - program. - - For more information, please read the man page [virt-inspector(1)]. -*) - -"; - - generate_types grammar; - pr "(** The nested information returned from the {!inspect} function. *)\n"; - pr "\n"; - - pr "\ -val inspect : ?connect:string -> ?xml:string -> string list -> operatingsystems -(** To inspect a libvirt domain called [name], pass a singleton - list: [inspect [name]]. When using libvirt only, you may - optionally pass a libvirt URI using [inspect ~connect:uri ...]. - - To inspect a disk image or images, pass a list of the filenames - of the disk images: [inspect filenames] - - This function inspects the given guest or disk images and - returns a list of operating system(s) found and a large amount - of information about them. In the vast majority of cases, - a virtual machine only contains a single operating system. - - If the optional [~xml] parameter is given, then this function - skips running the external virt-inspector program and just - parses the given XML directly (which is expected to be XML - produced from a previous run of virt-inspector). The list of - names and connect URI are ignored in this case. - - This function can throw a wide variety of exceptions, for example - if the external virt-inspector program cannot be found, or if - it doesn't generate valid XML. -*) -" - -(* Generate ocaml/guestfs_inspector.ml. *) -let generate_ocaml_inspector_ml () - generate_header ~extra_inputs:[rng_input] OCamlStyle LGPLv2plus; - - pr "open Unix\n"; - pr "\n"; - - generate_types grammar; - pr "\n"; - - pr "\ -(* Misc functions which are used by the parser code below. *) -let first_child = function - | Xml.Element (_, _, c::_) -> c - | Xml.Element (name, _, []) -> - failwith (\"expected <\" ^ name ^ \"/> to have a child node\") - | Xml.PCData str -> - failwith (\"expected XML tag, but read PCDATA '\" ^ str ^ \"' instead\") - -let string_child_or_empty = function - | Xml.Element (_, _, [Xml.PCData s]) -> s - | Xml.Element (_, _, []) -> \"\" - | Xml.Element (x, _, _) -> - failwith (\"expected XML tag with a single PCDATA child, but got \" ^ - x ^ \" instead\") - | Xml.PCData str -> - failwith (\"expected XML tag, but read PCDATA '\" ^ str ^ \"' instead\") - -let optional_child name xml - let children = Xml.children xml in - try - Some (List.find (function - | Xml.Element (n, _, _) when n = name -> true - | _ -> false) children) - with - Not_found -> None - -let child name xml - match optional_child name xml with - | Some c -> c - | None -> - failwith (\"mandatory field <\" ^ name ^ \"/> missing in XML output\") - -let attribute name xml - try Xml.attrib xml name - with Xml.No_attribute _ -> - failwith (\"mandatory attribute \" ^ name ^ \" missing in XML output\") - -"; - - generate_parsers grammar; - pr "\n"; - - pr "\ -(* Run external virt-inspector, then use parser to parse the XML. *) -let inspect ?connect ?xml names - let xml - match xml with - | None -> - if names = [] then invalid_arg \"inspect: no names given\"; - let cmd = [ \"virt-inspector\"; \"--xml\" ] @ - (match connect with None -> [] | Some uri -> [ \"--connect\"; uri ]) @ - names in - let cmd = List.map Filename.quote cmd in - let cmd = String.concat \" \" cmd in - let chan = open_process_in cmd in - let xml = Xml.parse_in chan in - (match close_process_in chan with - | WEXITED 0 -> () - | WEXITED _ -> failwith \"external virt-inspector command failed\" - | WSIGNALED i | WSTOPPED i -> - failwith (\"external virt-inspector command died or stopped on sig \" ^ - string_of_int i) - ); - xml - | Some doc -> - Xml.parse_string doc in - parse_operatingsystems xml -" - and generate_max_proc_nr () pr "%d\n" max_proc_nr @@ -12749,8 +12261,6 @@ Run it from the top source directory using the command output_to "ocaml/guestfs.ml" generate_ocaml_ml; output_to "ocaml/guestfs_c_actions.c" generate_ocaml_c; output_to "ocaml/bindtests.ml" generate_ocaml_bindtests; - output_to "ocaml/guestfs_inspector.mli" generate_ocaml_inspector_mli; - output_to "ocaml/guestfs_inspector.ml" generate_ocaml_inspector_ml; output_to "perl/Guestfs.xs" generate_perl_xs; output_to "perl/lib/Sys/Guestfs.pm" generate_perl_pm; output_to "perl/bindtests.pl" generate_perl_bindtests; -- 1.7.1