/* guestfish - the filesystem interactive shell
- * Copyright (C) 2009-2010 Red Hat Inc.
+ * Copyright (C) 2009-2011 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
#include "closeout.h"
#include "progname.h"
+/* Return from parse_command_line. See description below. */
+struct parsed_command {
+ int status;
+ char *pipe;
+ char *cmd;
+ char *argv[64];
+};
+
static void set_up_terminal (void);
static void prepare_drives (struct drv *drv);
static int launch (void);
static void shell_script (void);
static void script (int prompt);
static void cmdline (char *argv[], int optind, int argc);
+static struct parsed_command parse_command_line (char *buf, int *exit_on_error_rtn);
+static int execute_and_inline (const char *cmd, int exit_on_error);
static void initialize_readline (void);
static void cleanup_readline (void);
#ifdef HAVE_LIBREADLINE
guestfs_h *g;
int read_only = 0;
+int live = 0;
int quit = 0;
int verbose = 0;
int remote_control_listen = 0;
int remote_control_csh = 0;
int remote_control = 0;
-int exit_on_error = 1;
int command_num = 0;
int keys_from_stdin = 0;
int echo_keys = 0;
fprintf (stdout,
_("%s: guest filesystem shell\n"
"%s lets you edit virtual machine filesystems\n"
- "Copyright (C) 2009-2010 Red Hat Inc.\n"
+ "Copyright (C) 2009-2011 Red Hat Inc.\n"
"Usage:\n"
" %s [--options] cmd [: cmd : cmd ...]\n"
- " %s [--ro] -i -a disk-image\n"
- " %s [--ro] -i -d libvirt-domain\n"
- "or for interactive use:\n"
- " %s\n"
- "or from a shell script:\n"
- " %s <<EOF\n"
- " cmd\n"
- " ...\n"
- " EOF\n"
"Options:\n"
" -h|--cmd-help List available commands\n"
" -h|--cmd-help cmd Display detailed help on 'cmd'\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"
+ " --live Connect to a live virtual machine\n"
+ " -m|--mount dev[:mnt[:opts]] Mount dev on mnt (if omitted, /)\n"
" -n|--no-sync Don't autosync\n"
" -N|--new type Create prepared disk (test1.img, ...)\n"
" --progress-bars Enable progress bars even when not interactive\n"
" --selinux Enable SELinux support\n"
" -v|--verbose Verbose messages\n"
" -V|--version Display version and exit\n"
+ " -w|--rw Mount read-write\n"
" -x Echo each command before executing it\n"
+ "\n"
+ "To examine a disk image, ISO, hard disk, filesystem etc:\n"
+ " %s [--ro|--rw] -i -a /path/to/disk.img\n"
+ "or\n"
+ " %s [--ro|--rw] -i -d name-of-libvirt-domain\n"
+ "\n"
+ "--ro recommended to avoid any writes to the disk image. If -i option fails\n"
+ "run again without -i and use 'run' + 'list-filesystems' + 'mount' cmds.\n"
+ "\n"
"For more information, see the manpage %s(1).\n"),
program_name, program_name, program_name,
program_name, program_name, program_name,
- program_name, program_name, program_name);
+ program_name);
}
exit (status);
}
bindtextdomain (PACKAGE, LOCALEBASEDIR);
textdomain (PACKAGE);
+ parse_config ();
+
set_up_terminal ();
enum { HELP_OPTION = CHAR_MAX + 1 };
{ "inspector", 0, 0, 'i' },
{ "keys-from-stdin", 0, 0, 0 },
{ "listen", 0, 0, 0 },
+ { "live", 0, 0, 0 },
{ "mount", 1, 0, 'm' },
{ "new", 1, 0, 'N' },
{ "no-dest-paths", 0, 0, 'D' },
* getopt_long uses argv[0], so give it the sanitized name. Save a copy
* of the original, in case it's needed below.
*/
- char *real_argv0 = argv[0];
+ //char *real_argv0 = argv[0];
argv[0] = bad_cast (program_name);
for (;;) {
format = optarg;
} else if (STREQ (long_options[option_index].name, "csh")) {
remote_control_csh = 1;
+ } else if (STREQ (long_options[option_index].name, "live")) {
+ live = 1;
} else {
fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
program_name, long_options[option_index].name, option_index);
exit (EXIT_FAILURE);
}
drv->type = drv_N;
+ drv->device = NULL;
+ drv->nr_drives = -1;
if (asprintf (&drv->N.filename, "test%d.img",
next_prepared_drive++) == -1) {
perror ("asprintf");
}
drv->N.data = create_prepared_file (optarg, drv->N.filename);
drv->N.data_free = free_prep_data;
- drv->N.device = NULL; /* filled in by add_drives */
drv->next = drvs;
drvs = drv;
break;
: (optind >= argc && isatty (0));
if (progress_bars)
- guestfs_set_progress_callback (g, progress_callback, NULL);
+ guestfs_set_event_callback (g, progress_callback,
+ GUESTFS_EVENT_PROGRESS, 0, NULL);
/* Interactive, shell script, or command(s) on the command line? */
if (optind >= argc) {
have_terminfo = 1;
}
-void
-pod2text (const char *name, const char *shortdesc, const char *str)
-{
- FILE *fp;
-
- fp = popen ("pod2text", "w");
- if (fp == NULL) {
- /* pod2text failed, maybe not found, so let's just print the
- * source instead, since that's better than doing nothing.
- */
- printf ("%s - %s\n\n%s\n", name, shortdesc, str);
- return;
- }
- fprintf (fp, "=head1 NAME\n\n%s - %s\n\n", name, shortdesc);
- fputs (str, fp);
- pclose (fp);
-}
-
static void
prepare_drives (struct drv *drv)
{
if (drv) {
prepare_drives (drv->next);
if (drv->type == drv_N)
- prepare_drive (drv->N.filename, drv->N.data, drv->N.device);
+ prepare_drive (drv->N.filename, drv->N.data, drv->device);
}
}
script (int prompt)
{
char *buf;
- char *cmd;
- char *p, *pend;
- char *argv[64];
- int len;
int global_exit_on_error = !prompt;
- int tilde_candidate;
+ int exit_on_error;
+ struct parsed_command pcmd;
if (prompt) {
printf (_("\n"
}
while (!quit) {
- char *pipe = NULL;
-
exit_on_error = global_exit_on_error;
buf = rl_gets (prompt);
break;
}
- /* Skip any initial whitespace before the command. */
- again:
- while (*buf && c_isspace (*buf))
- buf++;
+ pcmd = parse_command_line (buf, &exit_on_error);
+ if (pcmd.status == -1 && exit_on_error)
+ exit (EXIT_FAILURE);
+ if (pcmd.status == 1) {
+ if (issue_command (pcmd.cmd, pcmd.argv, pcmd.pipe, exit_on_error) == -1) {
+ if (exit_on_error) exit (EXIT_FAILURE);
+ }
+ }
+ }
+ if (prompt) printf ("\n");
+}
- if (!*buf) continue;
+/* Parse a command string, splitting at whitespace, handling '!', '#' etc.
+ * This destructively updates 'buf'.
+ *
+ * 'exit_on_error_rtn' is used to pass in the global exit_on_error
+ * setting and to return the local setting (eg. if the command begins
+ * with '-').
+ *
+ * Returns in parsed_command.status:
+ * 1 = got a guestfish command (returned in cmd_rtn/argv_rtn/pipe_rtn)
+ * 0 = no guestfish command, but otherwise OK
+ * -1 = an error
+ */
+static struct parsed_command
+parse_command_line (char *buf, int *exit_on_error_rtn)
+{
+ struct parsed_command pcmd;
+ char *p, *pend;
+ int len;
+ int tilde_candidate;
+ int r;
+ const size_t argv_len = sizeof pcmd.argv / sizeof pcmd.argv[0];
- /* If the next character is '#' then this is a comment. */
- if (*buf == '#') continue;
+ /* Note that pcmd.pipe must be set to NULL for correct usage. Other
+ * fields do not need to be, but this silences a gcc warning.
+ */
+ memset (&pcmd, 0, sizeof pcmd);
- /* If the next character is '!' then pass the whole lot to system(3). */
- if (*buf == '!') {
- int r;
+ again:
+ /* Skip any initial whitespace before the command. */
+ while (*buf && c_isspace (*buf))
+ buf++;
- r = system (buf+1);
- if (exit_on_error) {
- if (r == -1 ||
- (WIFSIGNALED (r) &&
- (WTERMSIG (r) == SIGINT || WTERMSIG (r) == SIGQUIT)) ||
- WEXITSTATUS (r) != 0)
- exit (EXIT_FAILURE);
- }
- continue;
- }
+ if (!*buf) {
+ pcmd.status = 0;
+ return pcmd;
+ }
- /* If the next character is '-' allow the command to fail without
- * exiting on error (just for this one command though).
- */
- if (*buf == '-') {
- exit_on_error = 0;
- buf++;
- goto again;
- }
+ /* If the next character is '#' then this is a comment. */
+ if (*buf == '#') {
+ pcmd.status = 0;
+ return pcmd;
+ }
- /* Get the command (cannot be quoted). */
- len = strcspn (buf, " \t");
+ /* If the next character is '!' then pass the whole lot to system(3). */
+ if (*buf == '!') {
+ r = system (buf+1);
+ if (r == -1 ||
+ (WIFSIGNALED (r) &&
+ (WTERMSIG (r) == SIGINT || WTERMSIG (r) == SIGQUIT)) ||
+ WEXITSTATUS (r) != 0)
+ pcmd.status = -1;
+ else
+ pcmd.status = 0;
+ return pcmd;
+ }
- if (len == 0) continue;
+ /* If the next two characters are "<!" then pass the command to
+ * popen(3), read the result and execute it as guestfish commands.
+ */
+ if (buf[0] == '<' && buf[1] == '!') {
+ int r = execute_and_inline (&buf[2], *exit_on_error_rtn);
+ if (r == -1)
+ pcmd.status = -1;
+ else
+ pcmd.status = 0;
+ return pcmd;
+ }
- cmd = buf;
- unsigned int i = 0;
- if (buf[len] == '\0') {
- argv[0] = NULL;
- goto got_command;
- }
+ /* If the next character is '-' allow the command to fail without
+ * exiting on error (just for this one command though).
+ */
+ if (*buf == '-') {
+ *exit_on_error_rtn = 0;
+ buf++;
+ goto again;
+ }
- buf[len] = '\0';
- p = &buf[len+1];
- p += strspn (p, " \t");
+ /* Get the command (cannot be quoted). */
+ len = strcspn (buf, " \t");
- /* Get the parameters. */
- while (*p && i < sizeof argv / sizeof argv[0]) {
- tilde_candidate = 0;
+ if (len == 0) {
+ pcmd.status = 0;
+ return pcmd;
+ }
+
+ pcmd.cmd = buf;
+ unsigned int i = 0;
+ if (buf[len] == '\0') {
+ pcmd.argv[0] = NULL;
+ pcmd.status = 1;
+ return pcmd;
+ }
+
+ buf[len] = '\0';
+ p = &buf[len+1];
+ p += strspn (p, " \t");
- /* Parameters which start with quotes or pipes are treated
- * specially. Bare parameters are delimited by whitespace.
+ /* Get the parameters. */
+ while (*p && i < argv_len) {
+ tilde_candidate = 0;
+
+ /* Parameters which start with quotes or pipes are treated
+ * specially. Bare parameters are delimited by whitespace.
+ */
+ if (*p == '"') {
+ p++;
+ len = strcspn (p, "\"");
+ if (p[len] == '\0') {
+ fprintf (stderr, _("%s: unterminated double quote\n"), program_name);
+ pcmd.status = -1;
+ return pcmd;
+ }
+ if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
+ fprintf (stderr,
+ _("%s: command arguments not separated by whitespace\n"),
+ program_name);
+ pcmd.status = -1;
+ return pcmd;
+ }
+ p[len] = '\0';
+ pend = p[len+1] ? &p[len+2] : &p[len+1];
+ } else if (*p == '\'') {
+ p++;
+ len = strcspn (p, "'");
+ if (p[len] == '\0') {
+ fprintf (stderr, _("%s: unterminated single quote\n"), program_name);
+ pcmd.status = -1;
+ return pcmd;
+ }
+ if (p[len+1] && (p[len+1] != ' ' && p[len+1] != '\t')) {
+ fprintf (stderr,
+ _("%s: command arguments not separated by whitespace\n"),
+ program_name);
+ pcmd.status = -1;
+ return pcmd;
+ }
+ p[len] = '\0';
+ pend = p[len+1] ? &p[len+2] : &p[len+1];
+ } else if (*p == '|') {
+ *p = '\0';
+ pcmd.pipe = p+1;
+ continue;
+ } else if (*p != ' ' && *p != '\t') {
+ /* If the first character is a ~ then note that this parameter
+ * is a candidate for ~username expansion. NB this does not
+ * apply to quoted parameters.
*/
- if (*p == '"') {
- p++;
- len = strcspn (p, "\"");
- if (p[len] == '\0') {
- fprintf (stderr, _("%s: unterminated double quote\n"), program_name);
- 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 (EXIT_FAILURE);
- goto next_command;
- }
- p[len] = '\0';
- pend = p[len+1] ? &p[len+2] : &p[len+1];
- } else if (*p == '\'') {
- p++;
- len = strcspn (p, "'");
- if (p[len] == '\0') {
- fprintf (stderr, _("%s: unterminated single quote\n"), program_name);
- 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 (EXIT_FAILURE);
- goto next_command;
- }
+ tilde_candidate = *p == '~';
+ len = strcspn (p, " \t");
+ if (p[len]) {
p[len] = '\0';
- pend = p[len+1] ? &p[len+2] : &p[len+1];
- } else if (*p == '|') {
- *p = '\0';
- pipe = p+1;
- continue;
- /*
- } else if (*p == '[') {
- int c = 1;
- p++;
- pend = p;
- while (*pend && c != 0) {
- if (*pend == '[') c++;
- else if (*pend == ']') c--;
- pend++;
- }
- if (c != 0) {
- fprintf (stderr,
- _("%s: unterminated \"[...]\" sequence\n"), program_name);
- 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 (EXIT_FAILURE);
- goto next_command;
- }
- *(pend-1) = '\0';
- */
- } else if (*p != ' ' && *p != '\t') {
- /* If the first character is a ~ then note that this parameter
- * is a candidate for ~username expansion. NB this does not
- * apply to quoted parameters.
- */
- tilde_candidate = *p == '~';
- len = strcspn (p, " \t");
- if (p[len]) {
- p[len] = '\0';
- pend = &p[len+1];
- } else
- pend = &p[len];
- } else {
- fprintf (stderr, _("%s: internal error parsing string at '%s'\n"),
- program_name, p);
- abort ();
- }
+ pend = &p[len+1];
+ } else
+ pend = &p[len];
+ } else {
+ fprintf (stderr, _("%s: internal error parsing string at '%s'\n"),
+ program_name, p);
+ abort ();
+ }
- if (!tilde_candidate)
- argv[i] = p;
- else
- argv[i] = try_tilde_expansion (p);
- i++;
- p = pend;
+ if (!tilde_candidate)
+ pcmd.argv[i] = p;
+ else
+ pcmd.argv[i] = try_tilde_expansion (p);
+ i++;
+ p = pend;
- if (*p)
- p += strspn (p, " \t");
- }
+ if (*p)
+ p += strspn (p, " \t");
+ }
- if (i == sizeof argv / sizeof argv[0]) {
- fprintf (stderr, _("%s: too many arguments\n"), program_name);
- if (exit_on_error) exit (EXIT_FAILURE);
- goto next_command;
- }
+ if (i == argv_len) {
+ fprintf (stderr, _("%s: too many arguments\n"), program_name);
+ pcmd.status = -1;
+ return pcmd;
+ }
+
+ pcmd.argv[i] = NULL;
- argv[i] = NULL;
+ pcmd.status = 1;
+ return pcmd;
+}
- got_command:
- if (issue_command (cmd, argv, pipe) == -1) {
- if (exit_on_error) exit (EXIT_FAILURE);
+/* Used to handle "<!" (execute command and inline result). */
+static int
+execute_and_inline (const char *cmd, int global_exit_on_error)
+{
+ FILE *pp;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t n;
+ int exit_on_error;
+ struct parsed_command pcmd;
+
+ pp = popen (cmd, "r");
+ if (!pp) {
+ perror ("popen");
+ return -1;
+ }
+
+ while ((n = getline (&line, &len, pp)) != -1) {
+ exit_on_error = global_exit_on_error;
+
+ /* Chomp final line ending which parse_command_line would not expect. */
+ if (n > 0 && line[n-1] == '\n')
+ line[n-1] = '\0';
+
+ pcmd = parse_command_line (line, &exit_on_error);
+ if (pcmd.status == -1 && exit_on_error)
+ exit (EXIT_FAILURE);
+ if (pcmd.status == 1) {
+ if (issue_command (pcmd.cmd, pcmd.argv, pcmd.pipe, exit_on_error) == -1) {
+ if (exit_on_error) exit (EXIT_FAILURE);
+ }
}
+ }
+
+ free (line);
- next_command:;
+ if (pclose (pp) == -1) {
+ perror ("pclose");
+ return -1;
}
- if (prompt) printf ("\n");
+
+ return 0;
}
static void
{
const char *cmd;
char **params;
+ int exit_on_error;
exit_on_error = 1;
optind++;
if (optind == argc) {
- if (issue_command (cmd, params, NULL) == -1 && exit_on_error)
+ if (issue_command (cmd, params, NULL, exit_on_error) == -1 && exit_on_error)
exit (EXIT_FAILURE);
} else {
argv[optind] = NULL;
- if (issue_command (cmd, params, NULL) == -1 && exit_on_error)
+ if (issue_command (cmd, params, NULL, exit_on_error) == -1 && exit_on_error)
exit (EXIT_FAILURE);
cmdline (argv, optind+1, argc);
}
}
+/* Note: 'rc_exit_on_error_flag' is the exit_on_error flag that we
+ * pass to the remote server (when issuing --remote commands). It
+ * does not cause issue_command itself to exit on error.
+ */
int
-issue_command (const char *cmd, char *argv[], const char *pipecmd)
+issue_command (const char *cmd, char *argv[], const char *pipecmd,
+ int rc_exit_on_error_flag)
{
int argc;
int stdout_saved_fd = -1;
int pid = 0;
- int i, r;
+ int r;
reset_progress_bar ();
/* If --remote was set, then send this command to a remote process. */
if (remote_control)
- r = rc_remote (remote_control, cmd, argc, argv, exit_on_error);
+ r = rc_remote (remote_control, cmd, argc, argv, rc_exit_on_error_flag);
/* Otherwise execute it locally. */
else if (STRCASEEQ (cmd, "help")) {