This patch adds support for pipes to guestfish, so you can pipe output from a guestfish command through a command on the host. The canonical example is:
<fs> hexdump /bin/ls | less
Another example, looking for root backdoors in the password file:
<fs> cat /etc/passwd | awk -F: '$3 == 0 { print }' | grep -v ^root:
Anything right of the first pipe symbol gets passed to the local shell, thus expansion, redirection and so on work on that.
Rich.
From 57de1f4b17aba957ac18b64fccef66d2f21241d9 Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones rjones@redhat.com Date: Sat, 27 Jun 2009 15:00:48 +0200 Subject: [PATCH] Guestfish pipes.
--- fish/fish.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++---------- fish/fish.h | 2 +- fish/glob.c | 2 +- guestfish.pod | 26 +++++++++++++++++ 4 files changed, 99 insertions(+), 17 deletions(-)
diff --git a/fish/fish.c b/fish/fish.c index 5e8a6e9..5b0a065 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -29,6 +29,8 @@ #include <signal.h> #include <assert.h> #include <ctype.h> +#include <sys/types.h> +#include <sys/wait.h>
#ifdef HAVE_LIBREADLINE #include <readline/readline.h> @@ -138,9 +140,15 @@ main (int argc, char *argv[]) struct mp *mp; char *p, *file = NULL; int c, inspector = 0; + struct sigaction sa;
initialize_readline ();
+ memset (&sa, 0, sizeof sa); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_RESTART; + sigaction (SIGPIPE, &sa, NULL); + /* guestfs_create is meant to be a lightweight operation, so * it's OK to do it early here. */ @@ -462,6 +470,8 @@ script (int prompt) "\n"));
while (!quit) { + char *pipe = NULL; + exit_on_error = global_exit_on_error;
buf = rl_gets (prompt); @@ -522,9 +532,8 @@ script (int prompt)
/* Get the parameters. */ while (*p && i < sizeof argv / sizeof argv[0]) { - /* Parameters which start with quotes or square brackets - * are treated specially. Bare parameters are delimited - * by whitespace. + /* Parameters which start with quotes or pipes are treated + * specially. Bare parameters are delimited by whitespace. */ if (*p == '"') { p++; @@ -556,6 +565,10 @@ script (int prompt) } 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; @@ -607,7 +620,7 @@ script (int prompt) argv[i] = NULL;
got_command: - if (issue_command (cmd, argv) == -1) { + if (issue_command (cmd, argv, pipe) == -1) { if (exit_on_error) exit (1); }
@@ -636,18 +649,51 @@ cmdline (char *argv[], int optind, int argc) optind++;
if (optind == argc) { - if (issue_command (cmd, params) == -1) exit (1); + if (issue_command (cmd, params, NULL) == -1) exit (1); } else { argv[optind] = NULL; - if (issue_command (cmd, params) == -1) exit (1); + if (issue_command (cmd, params, NULL) == -1) exit (1); cmdline (argv, optind+1, argc); } }
int -issue_command (const char *cmd, char *argv[]) +issue_command (const char *cmd, char *argv[], const char *pipecmd) { int argc; + int stdout_saved_fd = -1; + int pid = 0; + int r; + + /* For | ... commands. Annoyingly we can't use popen(3) here. */ + if (pipecmd) { + int fd[2]; + + fflush (stdout); + pipe (fd); + pid = fork (); + if (pid == -1) { + perror ("fork"); + return -1; + } + + if (pid == 0) { /* Child process. */ + close (fd[1]); + dup2 (fd[0], 0); + + r = system (pipecmd); + if (r == -1) { + perror (pipecmd); + _exit (1); + } + _exit (WEXITSTATUS (r)); + } + + stdout_saved_fd = dup (1); + close (fd[0]); + dup2 (fd[1], 1); + close (fd[1]); + }
for (argc = 0; argv[argc] != NULL; ++argc) ; @@ -657,29 +703,39 @@ issue_command (const char *cmd, char *argv[]) list_commands (); else display_command (argv[0]); - return 0; + r = 0; } else if (strcasecmp (cmd, "quit") == 0 || strcasecmp (cmd, "exit") == 0 || strcasecmp (cmd, "q") == 0) { quit = 1; - return 0; + r = 0; } else if (strcasecmp (cmd, "alloc") == 0 || strcasecmp (cmd, "allocate") == 0) - return do_alloc (cmd, argc, argv); + r = do_alloc (cmd, argc, argv); else if (strcasecmp (cmd, "echo") == 0) - return do_echo (cmd, argc, argv); + r = do_echo (cmd, argc, argv); else if (strcasecmp (cmd, "edit") == 0 || strcasecmp (cmd, "vi") == 0 || strcasecmp (cmd, "emacs") == 0) - return do_edit (cmd, argc, argv); + r = do_edit (cmd, argc, argv); else if (strcasecmp (cmd, "lcd") == 0) - return do_lcd (cmd, argc, argv); + r = do_lcd (cmd, argc, argv); else if (strcasecmp (cmd, "glob") == 0) - return do_glob (cmd, argc, argv); + r = do_glob (cmd, argc, argv); else - return run_action (cmd, argc, argv); + r = run_action (cmd, argc, argv); + + if (pipecmd) { + fflush (stdout); + close (1); + dup2 (stdout_saved_fd, 1); + close (stdout_saved_fd); + waitpid (pid, NULL, 0); + } + + return r; }
void diff --git a/fish/fish.h b/fish/fish.h index 8815807..8f57595 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -34,7 +34,7 @@ extern guestfs_h *g; extern int quit; extern int verbose; -extern int issue_command (const char *cmd, char *argv[]); +extern int issue_command (const char *cmd, char *argv[], const char *pipe); extern void pod2text (const char *heading, const char *body); extern void list_builtin_commands (void); extern void display_builtin_command (const char *cmd); diff --git a/fish/glob.c b/fish/glob.c index f20da84..a8ac58a 100644 --- a/fish/glob.c +++ b/fish/glob.c @@ -147,7 +147,7 @@ glob_issue (char *cmd, int argc, } printf ("\n");
- if (issue_command (argv[0], &argv[1]) == -1) + if (issue_command (argv[0], &argv[1], NULL) == -1) *r = -1; /* ... but don't exit */
for (i = argc-1; i >= 1; --i) { diff --git a/guestfish.pod b/guestfish.pod index 0870b9e..26312fe 100644 --- a/guestfish.pod +++ b/guestfish.pod @@ -282,6 +282,32 @@ will create a directory C<local> on the host, and then export the contents of C</remote> on the mounted filesystem to C<local/remote-data.tar.gz>. (See C<tgz-out>).
+=head1 PIPES + +Use C<command E<lt>spaceE<gt> | command> to pipe the output of the +first command (a guestfish command) to the second command (any host +command). For example: + + cat /etc/passwd | awk -F: '$3 == 0 { print }' + +(where C<cat> is the guestfish cat command, but C<awk> is the host awk +program). The above command would list all accounts in the guest +filesystem which have UID 0, ie. root accounts including backdoors. +Other examples: + + hexdump /bin/ls | head + list-devices | tail -1 + +The space before the pipe symbol is required, any space after the pipe +symbol is optional. Everything after the pipe symbol is just passed +straight to the host shell, so it can contain redirections, globs and +anything else that makes sense on the host side. + +To use a literal argument which begins with a pipe symbol, you have +to quote it, eg: + + echo "|" + =head1 EXIT ON ERROR BEHAVIOUR
By default, guestfish will ignore any errors when in interactive mode
I applied this branch since no one shouted. It seems to work fine for me, and looks like a useful addition, but if it has any negative effects on command line usage we can revert it easily before the next release.
Rich.