+ usage (EXIT_FAILURE);
+ }
+ }
+
+ /* 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) {
+ 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->a.format = NULL;
+ 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;
+ }
+
+ optind++;
+ }
+ }
+
+ /* If we've got drives to add, add them now. */
+ add_drives (drvs, 'a');
+
+ /* 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 || 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
+ * will think the main program has died and kill qemu. Therefore
+ * don't use the recovery process for this case. (A better
+ * solution would be to call launch () etc after the fork, but
+ * that greatly complicates the code here).
+ */
+ if (remote_control_listen)
+ guestfs_set_recovery_proc (g, 0);
+
+ if (launch () == -1) exit (EXIT_FAILURE);
+
+ if (inspector)
+ inspect_mount ();
+
+ prepare_drives (drvs);
+ mount_mps (mps);
+ }
+
+ /* Free up data structures, no longer needed after this point. */
+ free_drives (drvs);
+ free_mps (mps);
+
+ /* Remote control? */
+ if (remote_control_listen && remote_control) {
+ fprintf (stderr,
+ _("%s: cannot use --listen and --remote options at the same time\n"),
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+
+ if (remote_control_listen) {
+ if (optind < argc) {
+ fprintf (stderr,
+ _("%s: extra parameters on the command line with --listen flag\n"),
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ if (file) {
+ fprintf (stderr,
+ _("%s: cannot use --listen and --file options at the same time\n"),
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ rc_listen ();
+ }
+
+ /* -f (file) parameter? */
+ if (file) {
+ close (0);
+ if (open (file, O_RDONLY) == -1) {
+ perror (file);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ /* Decide if we display progress bars. */
+ progress_bars =
+ override_progress_bars >= 0
+ ? override_progress_bars
+ : (optind >= argc && isatty (0));
+
+ if (progress_bars)
+ 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) {
+ if (isatty (0))
+ interactive ();
+ else
+ shell_script ();
+ }
+ else
+ cmdline (argv, optind, argc);
+
+ cleanup_readline ();
+
+ exit (EXIT_SUCCESS);
+}
+
+/* The <term.h> header file which defines this has "issues". */
+extern int tgetent (char *, const char *);
+
+static void
+set_up_terminal (void)
+{
+ /* http://www.cl.cam.ac.uk/~mgk25/unicode.html#activate */
+ utf8_mode = STREQ (nl_langinfo (CODESET), "UTF-8");
+
+ char *term = getenv ("TERM");
+ if (term == NULL) {
+ //fprintf (stderr, _("guestfish: TERM (terminal type) not defined.\n"));
+ return;
+ }
+
+ int r = tgetent (NULL, term);
+ if (r == -1) {
+ fprintf (stderr, _("guestfish: could not access termcap or terminfo database.\n"));
+ return;
+ }
+ if (r == 0) {
+ fprintf (stderr, _("guestfish: terminal type \"%s\" not defined.\n"),
+ term);
+ return;
+ }
+
+ have_terminfo = 1;
+}
+
+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->device);
+ }
+}
+
+static int
+launch (void)
+{
+ if (guestfs_is_config (g)) {
+ if (guestfs_launch (g) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+static void
+interactive (void)
+{
+ script (1);
+}
+
+static void
+shell_script (void)
+{
+ script (0);
+}
+
+#define FISH "><fs> "
+
+static char *line_read = NULL;
+
+static char *
+rl_gets (int prompt)
+{
+#ifdef HAVE_LIBREADLINE
+
+ if (prompt) {
+ if (line_read) {
+ free (line_read);
+ line_read = NULL;
+ }
+
+ line_read = readline (prompt ? FISH : "");
+
+ if (line_read && *line_read)
+ add_history_line (line_read);
+
+ return line_read;
+ }
+
+#endif /* HAVE_LIBREADLINE */
+
+ static char buf[8192];
+ int len;
+
+ if (prompt) printf (FISH);
+ line_read = fgets (buf, sizeof buf, stdin);
+
+ if (line_read) {
+ len = strlen (line_read);
+ if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
+ }
+
+ return line_read;
+}
+
+static void
+script (int prompt)
+{
+ char *buf;
+ int global_exit_on_error = !prompt;
+ int exit_on_error;
+ struct parsed_command pcmd;
+
+ if (prompt) {
+ printf (_("\n"
+ "Welcome to guestfish, the libguestfs filesystem interactive shell for\n"
+ "editing virtual machine filesystems.\n"
+ "\n"
+ "Type: 'help' for help on commands\n"
+ " 'man' to read the manual\n"
+ " 'quit' to quit the shell\n"
+ "\n"));
+
+ if (inspector) {
+ print_inspect_prompt ();
+ printf ("\n");
+ }
+ }
+
+ while (!quit) {
+ exit_on_error = global_exit_on_error;
+
+ buf = rl_gets (prompt);
+ if (!buf) {
+ quit = 1;
+ break;
+ }
+
+ 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");
+}
+
+/* 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];
+
+ /* 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);
+
+ again:
+ /* Skip any initial whitespace before the command. */
+ while (*buf && c_isspace (*buf))
+ buf++;
+
+ if (!*buf) {
+ pcmd.status = 0;
+ return pcmd;
+ }
+
+ /* If the next character is '#' then this is a comment. */
+ if (*buf == '#') {
+ pcmd.status = 0;
+ return pcmd;
+ }
+
+ /* 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 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;
+ }
+
+ /* 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;
+ }
+
+ /* Get the command (cannot be quoted). */
+ len = strcspn (buf, " \t");
+
+ 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");
+
+ /* 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.
+ */
+ 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 ();
+ }
+
+ 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 (i == argv_len) {
+ fprintf (stderr, _("%s: too many arguments\n"), program_name);
+ pcmd.status = -1;
+ return pcmd;
+ }
+
+ pcmd.argv[i] = NULL;
+
+ pcmd.status = 1;
+ return pcmd;
+}
+
+/* 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);
+
+ if (pclose (pp) == -1) {
+ perror ("pclose");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+cmdline (char *argv[], int optind, int argc)
+{
+ const char *cmd;
+ char **params;
+ int exit_on_error;
+
+ exit_on_error = 1;
+
+ if (optind >= argc) return;
+
+ cmd = argv[optind++];
+ if (STREQ (cmd, ":")) {
+ fprintf (stderr, _("%s: empty command on command line\n"), program_name);
+ 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 && STRNEQ (argv[optind], ":"))
+ optind++;
+
+ if (optind == argc) {
+ 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, 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,
+ int rc_exit_on_error_flag)
+{
+ int argc;
+ int stdout_saved_fd = -1;
+ int pid = 0;
+ int r;
+
+ reset_progress_bar ();
+
+ /* This counts the commands issued, starting at 1. */
+ command_num++;
+
+ /* For | ... commands. Annoyingly we can't use popen(3) here. */
+ if (pipecmd) {
+ int fd[2];
+
+ if (fflush (stdout) == EOF) {
+ perror ("failed to flush standard output");
+ return -1;
+ }
+ if (pipe (fd) < 0) {
+ perror ("pipe failed");
+ return -1;