X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=fish%2Ffish.c;h=470c625105d7b6281f8df4017c3d0082cb7b22de;hp=9a89f55a168a98ad79a58fb908cb7080e1907bc3;hb=f2b7a8e15c49ebc70c7ea56aefb340362aae5a99;hpb=9353c6253d5fac1648b13ad9958468a2d9f6ad6f diff --git a/fish/fish.c b/fish/fish.c index 9a89f55..470c625 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -1,5 +1,5 @@ /* guestfish - the filesystem interactive shell - * Copyright (C) 2009 Red Hat Inc. + * Copyright (C) 2009-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 @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef HAVE_LIBREADLINE #include @@ -41,26 +42,33 @@ #include "closeout.h" #include "progname.h" +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 */ +}; + struct mp { struct mp *next; char *device; char *mountpoint; }; -struct drv { - struct drv *next; - char *filename; -}; - static void add_drives (struct drv *drv); +static void prepare_drives (struct drv *drv); static void mount_mps (struct mp *mp); +static int launch (void); static void interactive (void); static void shell_script (void); static void script (int prompt); static void cmdline (char *argv[], int optind, int argc); static void initialize_readline (void); 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; @@ -68,22 +76,10 @@ guestfs_h *g; int read_only = 0; int quit = 0; int verbose = 0; -int echo_commands = 0; int remote_control_listen = 0; int remote_control = 0; int exit_on_error = 1; - -int -launch (guestfs_h *_g) -{ - assert (_g == g); - - if (guestfs_is_config (g)) { - if (guestfs_launch (g) == -1) - return -1; - } - return 0; -} +int command_num = 0; static void __attribute__((noreturn)) usage (int status) @@ -117,6 +113,7 @@ usage (int status) " --listen Listen for remote commands\n" " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" " -n|--no-sync Don't autosync\n" + " -N|--new type Create prepared disk (test1.img, ...)\n" " --remote[=pid] Send commands to remote %s\n" " -r|--ro Mount read-only\n" " --selinux Enable SELinux support\n" @@ -139,9 +136,13 @@ main (int argc, char *argv[]) atexit (close_stdout); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEBASEDIR); + textdomain (PACKAGE); + enum { HELP_OPTION = CHAR_MAX + 1 }; - static const char *options = "a:Df:h::im:nrv?Vx"; + static const char *options = "a:Df:h::im:nN:rv?Vx"; static const struct option long_options[] = { { "add", 1, 0, 'a' }, { "cmd-help", 2, 0, 'h' }, @@ -150,6 +151,7 @@ main (int argc, char *argv[]) { "inspector", 0, 0, 'i' }, { "listen", 0, 0, 0 }, { "mount", 1, 0, 'm' }, + { "new", 1, 0, 'N' }, { "no-dest-paths", 0, 0, 'D' }, { "no-sync", 0, 0, 'n' }, { "remote", 2, 0, 0 }, @@ -168,6 +170,8 @@ 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 (); @@ -182,7 +186,7 @@ main (int argc, char *argv[]) g = guestfs_create (); if (g == NULL) { fprintf (stderr, _("guestfs_create: failed to create handle\n")); - exit (1); + exit (EXIT_FAILURE); } guestfs_set_autosync (g, 1); @@ -215,14 +219,14 @@ main (int argc, char *argv[]) switch (c) { case 0: /* options which are long only */ - if (strcmp (long_options[option_index].name, "listen") == 0) + if (STREQ (long_options[option_index].name, "listen")) remote_control_listen = 1; - else if (strcmp (long_options[option_index].name, "remote") == 0) { + else if (STREQ (long_options[option_index].name, "remote")) { if (optarg) { if (sscanf (optarg, "%d", &remote_control) != 1) { fprintf (stderr, _("%s: --listen=PID: PID was not a number: %s\n"), program_name, optarg); - exit (1); + exit (EXIT_FAILURE); } } else { p = getenv ("GUESTFISH_PID"); @@ -230,29 +234,59 @@ main (int argc, char *argv[]) fprintf (stderr, _("%s: remote: $GUESTFISH_PID must be set" " to the PID of the remote process\n"), program_name); - exit (1); + exit (EXIT_FAILURE); } } - } else if (strcmp (long_options[option_index].name, "selinux") == 0) { + } else if (STREQ (long_options[option_index].name, "selinux")) { guestfs_set_selinux (g, 1); } else { fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), program_name, long_options[option_index].name, option_index); - exit (1); + exit (EXIT_FAILURE); } break; case 'a': if (access (optarg, R_OK) != 0) { perror (optarg); - exit (1); + exit (EXIT_FAILURE); } drv = malloc (sizeof (struct drv)); if (!drv) { perror ("malloc"); - exit (1); + 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->next = drvs; + drvs = drv; + next_drive++; + break; + + case 'N': + if (STRCASEEQ (optarg, "list")) { + list_prepared_drives (); + exit (EXIT_SUCCESS); + } + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + if (asprintf (&drv->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->next = drvs; drvs = drv; break; @@ -265,19 +299,23 @@ main (int argc, char *argv[]) if (file) { fprintf (stderr, _("%s: only one -f parameter can be given\n"), program_name); - exit (1); + exit (EXIT_FAILURE); } file = optarg; break; - case 'h': + case 'h': { + int r = 0; + if (optarg) - display_command (optarg); + r = display_command (optarg); else if (argv[optind] && argv[optind][0] != '-') - display_command (argv[optind++]); + r = display_command (argv[optind++]); else list_commands (); - exit (0); + + exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + } case 'i': inspector = 1; @@ -287,7 +325,7 @@ main (int argc, char *argv[]) mp = malloc (sizeof (struct mp)); if (!mp) { perror ("malloc"); - exit (1); + exit (EXIT_FAILURE); } p = strchr (optarg, ':'); if (p) { @@ -315,84 +353,131 @@ main (int argc, char *argv[]) case 'V': printf ("%s %s\n", program_name, PACKAGE_VERSION); - exit (0); + exit (EXIT_SUCCESS); case 'x': - echo_commands = 1; + guestfs_set_trace (g, 1); break; case HELP_OPTION: - usage (0); + usage (EXIT_SUCCESS); default: - usage (1); + usage (EXIT_FAILURE); } } /* Inspector mode invalidates most of the other arguments. */ if (inspector) { - char cmd[1024]; - int r; - if (drvs || mps || remote_control_listen || remote_control || guestfs_get_selinux (g)) { - fprintf (stderr, _("%s: cannot use -i option with -a, -m," - " --listen, --remote or --selinux\n"), + fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, " + "--listen, --remote or --selinux\n"), program_name); - exit (1); + 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 (1); + exit (EXIT_FAILURE); + } + + char *cmd; + size_t cmdlen; + FILE *fp = open_memstream (&cmd, &cmdlen); + if (fp == NULL) { + perror ("open_memstream"); + exit (EXIT_FAILURE); } - strcpy (cmd, "a=`virt-inspector"); + fprintf (fp, "virt-inspector"); while (optind < argc) { - if (strlen (cmd) + strlen (argv[optind]) + strlen (real_argv0) + 60 - >= sizeof cmd) { - fprintf (stderr, - _("%s: virt-inspector command too long for fixed-size buffer\n"), - program_name); - exit (1); - } - strcat (cmd, " '"); - strcat (cmd, argv[optind]); - strcat (cmd, "'"); + fputc (' ', fp); + print_shell_quote (fp, argv[optind]); optind++; } if (read_only) - strcat (cmd, " --ro-fish"); + fprintf (fp, " --ro-fish"); else - strcat (cmd, " --fish"); + 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); + } - sprintf (&cmd[strlen(cmd)], "` && %s $a", real_argv0); + 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)) - strcat (cmd, " -v"); + fprintf (fp, " -v"); if (!guestfs_get_autosync (g)) - strcat (cmd, " -n"); + 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 virt-inspector command:\n%s\n", program_name, cmd); + "%s -i: running: %s\n", program_name, cmd2); - r = system (cmd); + int r = system (cmd2); if (r == -1) { - perror ("system"); - exit (1); + perror (cmd2); + exit (EXIT_FAILURE); } + + free (cmd); + free (cmd2); + free (insp); + exit (WEXITSTATUS (r)); } /* If we've got drives to add, add them now. */ add_drives (drvs); - /* If we've got mountpoints, we must launch the guest and mount them. */ - if (mps != NULL) { - if (launch (g) == -1) exit (1); + /* If we've got mountpoints or prepared drives, we must launch the + * guest and mount them. + */ + if (next_prepared_drive > 1 || mps != NULL) { + if (launch () == -1) exit (EXIT_FAILURE); + prepare_drives (drvs); mount_mps (mps); } @@ -401,7 +486,7 @@ main (int argc, char *argv[]) fprintf (stderr, _("%s: cannot use --listen and --remote options at the same time\n"), program_name); - exit (1); + exit (EXIT_FAILURE); } if (remote_control_listen) { @@ -409,13 +494,13 @@ main (int argc, char *argv[]) fprintf (stderr, _("%s: extra parameters on the command line with --listen flag\n"), program_name); - exit (1); + exit (EXIT_FAILURE); } if (file) { fprintf (stderr, _("%s: cannot use --listen and --file options at the same time\n"), program_name); - exit (1); + exit (EXIT_FAILURE); } rc_listen (); } @@ -425,7 +510,7 @@ main (int argc, char *argv[]) close (0); if (open (file, O_RDONLY) == -1) { perror (file); - exit (1); + exit (EXIT_FAILURE); } } @@ -441,7 +526,7 @@ main (int argc, char *argv[]) cleanup_readline (); - exit (0); + exit (EXIT_SUCCESS); } void @@ -457,7 +542,7 @@ pod2text (const char *name, const char *shortdesc, const char *str) printf ("%s - %s\n\n%s\n", name, shortdesc, str); return; } - fprintf (fp, "=head1 %s - %s\n\n", name, shortdesc); + fprintf (fp, "=head1 NAME\n\n%s - %s\n\n", name, shortdesc); fputs (str, fp); pclose (fp); } @@ -470,12 +555,15 @@ mount_mps (struct mp *mp) if (mp) { mount_mps (mp->next); - if (!read_only) - r = guestfs_mount (g, mp->device, mp->mountpoint); - else - r = guestfs_mount_ro (g, mp->device, mp->mountpoint); + + /* Don't use guestfs_mount here because that will default to mount + * options -o sync,noatime. For more information, see guestfs(3) + * section "LIBGUESTFS GOTCHAS". + */ + const char *options = read_only ? "ro" : ""; + r = guestfs_mount_options (g, options, mp->device, mp->mountpoint); if (r == -1) - exit (1); + exit (EXIT_FAILURE); } } @@ -486,15 +574,36 @@ add_drives (struct drv *drv) if (drv) { add_drives (drv->next); - if (!read_only) + + 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 (1); + exit (EXIT_FAILURE); + } +} + +static void +prepare_drives (struct drv *drv) +{ + if (drv) { + prepare_drives (drv->next); + if (drv->data) + prepare_drive (drv->filename, drv->data, drv->device); } } +static int +launch (void) +{ + if (guestfs_is_config (g)) { + if (guestfs_launch (g) == -1) + return -1; + } + return 0; +} + static void interactive (void) { @@ -562,7 +671,8 @@ script (int prompt) "Welcome to guestfish, the libguestfs filesystem interactive shell for\n" "editing virtual machine filesystems.\n" "\n" - "Type: 'help' for help with commands\n" + "Type: 'help' for a list of commands\n" + " 'man' to read the manual\n" " 'quit' to quit the shell\n" "\n")); @@ -597,7 +707,7 @@ script (int prompt) (WIFSIGNALED (r) && (WTERMSIG (r) == SIGINT || WTERMSIG (r) == SIGQUIT)) || WEXITSTATUS (r) != 0) - exit (1); + exit (EXIT_FAILURE); } continue; } @@ -639,14 +749,14 @@ script (int prompt) len = strcspn (p, "\""); if (p[len] == '\0') { fprintf (stderr, _("%s: unterminated double quote\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) { fprintf (stderr, _("%s: command arguments not separated by whitespace\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } p[len] = '\0'; @@ -656,14 +766,14 @@ script (int prompt) len = strcspn (p, "'"); if (p[len] == '\0') { fprintf (stderr, _("%s: unterminated single quote\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) { fprintf (stderr, _("%s: command arguments not separated by whitespace\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } p[len] = '\0'; @@ -685,14 +795,14 @@ script (int prompt) if (c != 0) { fprintf (stderr, _("%s: unterminated \"[...]\" sequence\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } if (*pend && (*pend != ' ' && *pend != '\t')) { fprintf (stderr, _("%s: command arguments not separated by whitespace\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } *(pend-1) = '\0'; @@ -728,7 +838,7 @@ script (int prompt) if (i == sizeof argv / sizeof argv[0]) { fprintf (stderr, _("%s: too many arguments\n"), program_name); - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); goto next_command; } @@ -736,7 +846,7 @@ script (int prompt) got_command: if (issue_command (cmd, argv, pipe) == -1) { - if (exit_on_error) exit (1); + if (exit_on_error) exit (EXIT_FAILURE); } next_command:; @@ -755,21 +865,32 @@ cmdline (char *argv[], int optind, int argc) if (optind >= argc) return; cmd = argv[optind++]; - if (strcmp (cmd, ":") == 0) { + if (STREQ (cmd, ":")) { fprintf (stderr, _("%s: empty command on command line\n"), program_name); - exit (1); + exit (EXIT_FAILURE); } + + /* Allow -cmd on the command line to mean (temporarily) override + * the normal exit on error (RHBZ#578407). + */ + if (cmd[0] == '-') { + exit_on_error = 0; + cmd++; + } + params = &argv[optind]; /* Search for end of command list or ":" ... */ - while (optind < argc && strcmp (argv[optind], ":") != 0) + while (optind < argc && STRNEQ (argv[optind], ":")) optind++; if (optind == argc) { - if (issue_command (cmd, params, NULL) == -1) exit (1); + if (issue_command (cmd, params, NULL) == -1 && exit_on_error) + exit (EXIT_FAILURE); } else { argv[optind] = NULL; - if (issue_command (cmd, params, NULL) == -1) exit (1); + if (issue_command (cmd, params, NULL) == -1 && exit_on_error) + exit (EXIT_FAILURE); cmdline (argv, optind+1, argc); } } @@ -782,12 +903,8 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) int pid = 0; int i, r; - if (echo_commands) { - printf ("%s", cmd); - for (i = 0; argv[i] != NULL; ++i) - printf (" %s", argv[i]); - printf ("\n"); - } + /* This counts the commands issued, starting at 1. */ + command_num++; /* For | ... commands. Annoyingly we can't use popen(3) here. */ if (pipecmd) { @@ -844,11 +961,11 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) /* Otherwise execute it locally. */ else if (STRCASEEQ (cmd, "help")) { - if (argc == 0) + if (argc == 0) { list_commands (); - else - display_command (argv[0]); - r = 0; + r = 0; + } else + r = display_command (argv[0]); } else if (STRCASEEQ (cmd, "quit") || STRCASEEQ (cmd, "exit") || @@ -869,6 +986,9 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) r = do_lcd (cmd, argc, argv); else if (STRCASEEQ (cmd, "glob")) r = do_glob (cmd, argc, argv); + else if (STRCASEEQ (cmd, "man") || + STRCASEEQ (cmd, "manual")) + r = do_man (cmd, argc, argv); else if (STRCASEEQ (cmd, "more") || STRCASEEQ (cmd, "less")) r = do_more (cmd, argc, argv); @@ -876,6 +996,8 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) r = do_reopen (cmd, argc, argv); else if (STRCASEEQ (cmd, "sparse")) r = do_sparse (cmd, argc, argv); + else if (STRCASEEQ (cmd, "supported")) + r = do_supported (cmd, argc, argv); else if (STRCASEEQ (cmd, "time")) r = do_time (cmd, argc, argv); else @@ -908,10 +1030,12 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) void list_builtin_commands (void) { - /* help and quit should appear at the top */ + /* help, man and quit should appear at the top */ printf ("%-20s %s\n", "help", _("display a list of commands or help on a command")); printf ("%-20s %s\n", + "man", _("read the manual")); + printf ("%-20s %s\n", "quit", _("quit guestfish")); printf ("%-20s %s\n", @@ -931,18 +1055,20 @@ list_builtin_commands (void) printf ("%-20s %s\n", "sparse", _("allocate a sparse image file")); printf ("%-20s %s\n", + "supported", _("list supported groups of commands")); + printf ("%-20s %s\n", "time", _("measure time taken to run command")); /* actions are printed after this (see list_commands) */ } -void +int display_builtin_command (const char *cmd) { /* help for actions is auto-generated, see display_command */ if (STRCASEEQ (cmd, "alloc") || - STRCASEEQ (cmd, "allocate")) + STRCASEEQ (cmd, "allocate")) { printf (_("alloc - allocate an image\n" " alloc \n" "\n" @@ -951,57 +1077,69 @@ display_builtin_command (const char *cmd) "\n" " For more advanced image creation, see qemu-img utility.\n" "\n" - " Size can be specified (where means a number):\n" - " number of kilobytes\n" - " eg: 1440 standard 3.5\" floppy\n" - " K or KB number of kilobytes\n" - " M or MB number of megabytes\n" - " G or GB number of gigabytes\n" - " T or TB number of terabytes\n" - " P or PB number of petabytes\n" - " E or EB number of exabytes\n" - " sects number of 512 byte sectors\n")); - else if (STRCASEEQ (cmd, "echo")) + " Size can be specified using standard suffixes, eg. '1M'.\n" + )); + return 0; + } + else if (STRCASEEQ (cmd, "echo")) { printf (_("echo - display a line of text\n" " echo [ ...]\n" "\n" " This echos the parameters to the terminal.\n")); + return 0; + } else if (STRCASEEQ (cmd, "edit") || STRCASEEQ (cmd, "vi") || - STRCASEEQ (cmd, "emacs")) + STRCASEEQ (cmd, "emacs")) { printf (_("edit - edit a file in the image\n" " edit \n" "\n" " This is used to edit a file.\n" "\n" " It is the equivalent of (and is implemented by)\n" - " running \"cat\", editing locally, and then \"write-file\".\n" + " running \"cat\", editing locally, and then \"write\".\n" "\n" " Normally it uses $EDITOR, but if you use the aliases\n" " \"vi\" or \"emacs\" you will get those editors.\n" "\n" " NOTE: This will not work reliably for large files\n" " (> 2 MB) or binary files containing \\0 bytes.\n")); - else if (STRCASEEQ (cmd, "lcd")) + return 0; + } + else if (STRCASEEQ (cmd, "lcd")) { printf (_("lcd - local change directory\n" " lcd \n" "\n" " Change guestfish's current directory. This command is\n" " useful if you want to download files to a particular\n" " place.\n")); - else if (STRCASEEQ (cmd, "glob")) + return 0; + } + else if (STRCASEEQ (cmd, "glob")) { printf (_("glob - expand wildcards in command\n" " glob [ ...]\n" "\n" " Glob runs with wildcards expanded in any\n" " command args. Note that the command is run repeatedly\n" " once for each expanded argument.\n")); - else if (STRCASEEQ (cmd, "help")) + return 0; + } + else if (STRCASEEQ (cmd, "man") || + STRCASEEQ (cmd, "manual")) { + printf (_("man - read the manual\n" + " man\n" + "\n" + " Opens the manual page for guestfish.\n")); + return 0; + } + else if (STRCASEEQ (cmd, "help")) { printf (_("help - display a list of commands or help on a command\n" " help cmd\n" " help\n")); + return 0; + } else if (STRCASEEQ (cmd, "more") || - STRCASEEQ (cmd, "less")) + STRCASEEQ (cmd, "less")) { printf (_("more - view a file in the pager\n" " more \n" "\n" @@ -1015,19 +1153,25 @@ display_builtin_command (const char *cmd) "\n" " NOTE: This will not work reliably for large files\n" " (> 2 MB) or binary files containing \\0 bytes.\n")); + return 0; + } else if (STRCASEEQ (cmd, "quit") || STRCASEEQ (cmd, "exit") || - STRCASEEQ (cmd, "q")) + STRCASEEQ (cmd, "q")) { printf (_("quit - quit guestfish\n" " quit\n")); - else if (STRCASEEQ (cmd, "reopen")) + return 0; + } + else if (STRCASEEQ (cmd, "reopen")) { printf (_("reopen - close and reopen the libguestfs handle\n" " reopen\n" "\n" "Close and reopen the libguestfs handle. It is not necessary to use\n" "this normally, because the handle is closed properly when guestfish\n" "exits. However this is occasionally useful for testing.\n")); - else if (STRCASEEQ (cmd, "sparse")) + return 0; + } + else if (STRCASEEQ (cmd, "sparse")) { printf (_("sparse - allocate a sparse image file\n" " sparse \n" "\n" @@ -1044,25 +1188,50 @@ display_builtin_command (const char *cmd) "\n" " For more advanced image creation, see qemu-img utility.\n" "\n" - " Size can be specified (where means a number):\n" - " number of kilobytes\n" - " eg: 1440 standard 3.5\" floppy\n" - " K or KB number of kilobytes\n" - " M or MB number of megabytes\n" - " G or GB number of gigabytes\n" - " T or TB number of terabytes\n" - " P or PB number of petabytes\n" - " E or EB number of exabytes\n" - " sects number of 512 byte sectors\n")); - else if (STRCASEEQ (cmd, "time")) + " Size can be specified using standard suffixes, eg. '1M'.\n" + )); + return 0; + } + else if (STRCASEEQ (cmd, "supported")) { + printf (_("supported - list supported groups of commands\n" + " supported\n" + "\n" + " This command returns a list of the optional groups\n" + " known to the daemon, and indicates which ones are\n" + " supported by this build of the libguestfs appliance.\n" + "\n" + " See also guestfs(3) section AVAILABILITY.\n" + )); + return 0; + } + else if (STRCASEEQ (cmd, "time")) { printf (_("time - measure time taken to run command\n" " time [ ...]\n" "\n" " This runs as usual, and prints the elapsed\n" " time afterwards.\n")); - else + return 0; + } + else { fprintf (stderr, _("%s: command not known, use -h to list all commands\n"), cmd); + return -1; + } +} + +/* This is printed when the user types in an unknown command for the + * first command issued. A common case is the user doing: + * guestfish disk.img + * expecting guestfish to open 'disk.img' (in fact, this tried to + * run a command 'disk.img'). + */ +void +extended_help_message (void) +{ + fprintf (stderr, + _("Did you mean to open a disk image? guestfish -a disk.img\n" + "For a list of commands: guestfish -h\n" + "For complete documentation: man guestfish\n")); } void @@ -1107,11 +1276,11 @@ int is_true (const char *str) { return - strcasecmp (str, "0") != 0 && - strcasecmp (str, "f") != 0 && - strcasecmp (str, "false") != 0 && - strcasecmp (str, "n") != 0 && - strcasecmp (str, "no") != 0; + STRCASENEQ (str, "0") && + STRCASENEQ (str, "f") && + STRCASENEQ (str, "false") && + STRCASENEQ (str, "n") && + STRCASENEQ (str, "no"); } /* Free strings from a non-NULL terminated char** */ @@ -1191,7 +1360,7 @@ parse_string_list (const char *str) perror ("realloc"); free_n_strings (argv, argv_len); free (tok); - exit (1); + exit (EXIT_FAILURE); } tok = tok_new; @@ -1237,7 +1406,7 @@ parse_string_list (const char *str) perror ("realloc"); free_n_strings (argv, argv_len-1); free (tok); - exit (1); + exit (EXIT_FAILURE); } argv = argv_new; @@ -1251,7 +1420,7 @@ parse_string_list (const char *str) if (NULL == argv_new) { perror ("realloc"); free_n_strings (argv, argv_len-1); - exit (1); + exit (EXIT_FAILURE); } argv = argv_new; @@ -1280,6 +1449,13 @@ initialize_readline (void) rl_readline_name = "guestfish"; rl_attempted_completion_function = do_completion; + + /* Note that .inputrc (or /etc/inputrc) is not read until the first + * call the readline(), which happens later. Therefore, these + * provide default values which can be overridden by the user if + * they wish. + */ + (void) rl_variable_bind ("completion-ignore-case", "on"); #endif } @@ -1297,19 +1473,23 @@ cleanup_readline (void) } close (fd); +#ifdef HAVE_APPEND_HISTORY (void) append_history (nr_history_lines, histfile); +#else + (void) write_history (histfile); +#endif } #endif } +#ifdef HAVE_LIBREADLINE static void add_history_line (const char *line) { -#ifdef HAVE_LIBREADLINE add_history (line); nr_history_lines++; -#endif } +#endif int xwrite (int fd, const void *v_buf, size_t len) @@ -1340,7 +1520,7 @@ resolve_win_path (const char *path) char *ret; size_t i; - if (strncasecmp (path, "win:", 4) != 0) { + if (STRCASENEQLEN (path, "win:", 4)) { ret = strdup (path); if (ret == NULL) perror ("strdup"); @@ -1350,7 +1530,7 @@ resolve_win_path (const char *path) path += 4; /* Drop drive letter, if it's "C:". */ - if (strncasecmp (path, "c:", 2) == 0) + if (STRCASEEQLEN (path, "c:", 2)) path += 2; if (!*path) { @@ -1377,3 +1557,158 @@ resolve_win_path (const char *path) return ret; } + +/* Resolve the special FileIn paths ("-" or "-< or "END\n" in input. */ + size_t blen = strlen (buffer); + if (STREQLEN (buffer, endmarker, markerlen) && + (blen == markerlen || + (blen == markerlen+1 && buffer[markerlen] == '\n'))) + goto found_end; + + if (xwrite (fd, buffer, blen) == -1) { + if (!write_error) perror ("write"); + write_error = 1; + /* continue reading up to the end marker */ + } + } + + /* Reached EOF of stdin without finding the end marker, which + * is likely to be an error. + */ + fprintf (stderr, "%s: end of input reached without finding '%s'\n", + program_name, endmarker); + goto error2; + + found_end: + if (write_error) { + close (fd); + goto error2; + } + + if (close (fd) == -1) { + perror ("close"); + goto error2; + } + + return file_in_tmpfile; + + error2: + unlink (file_in_tmpfile); + + error1: + free (file_in_tmpfile); + file_in_tmpfile = NULL; + return NULL; +} + +void +free_file_in (char *s) +{ + if (file_in_tmpfile) { + if (unlink (file_in_tmpfile) == -1) + perror (file_in_tmpfile); + file_in_tmpfile = NULL; + } + + /* Free the device or file name which was strdup'd in file_in(). + * Note it's not immediately clear, but for -<< heredocs, + * s == file_in_tmpfile, so this frees up that buffer. + */ + free (s); +} + +/* Resolve the special FileOut paths ("-" or filename). + * The caller (cmds.c) will call free (str) after the command has run. + */ +char * +file_out (const char *arg) +{ + char *ret; + + if (STREQ (arg, "-")) + ret = strdup ("/dev/stdout"); + else + ret = strdup (arg); + + if (!ret) { + perror ("strdup"); + return NULL; + } + 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); + } +}