From 4440e22f4f7ebffe0728a8c019319d1a2b260cf5 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Mon, 2 Aug 2010 17:43:23 +0100 Subject: [PATCH] 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 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 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), 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. + 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..d17496f --- /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 + +#include +#include +#include + +#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 (_("Operating system: %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 (_("%s 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.8.3.1