Richard W.M. Jones
2011-Jan-18 12:09 UTC
[Libguestfs] [PATCH 0/4] In guestfish allow <! for inline execution
Read the man page in the final patch, but the idea is: <! for n in `seq 1 100`; do echo write /foo.$n $n; done Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top
Richard W.M. Jones
2011-Jan-18 12:09 UTC
[Libguestfs] [PATCH 1/4] fish: exit_on_error is a local variable.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From f6a21c1e0d44f9db409ce6583be7b5bc694767a8 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Tue, 18 Jan 2011 10:21:49 +0000 Subject: [PATCH 1/4] fish: exit_on_error is a local variable. --- fish/fish.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index 265464e..711073a 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -69,7 +69,7 @@ int verbose = 0; int remote_control_listen = 0; int remote_control_csh = 0; int remote_control = 0; -int exit_on_error = 1; +static int exit_on_error = 1; int command_num = 0; int keys_from_stdin = 0; int echo_keys = 0; -- 1.7.3.4
Richard W.M. Jones
2011-Jan-18 12:10 UTC
[Libguestfs] [PATCH 2/4] fish: Make exit_on_error into a completely local variable.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top -------------- next part -------------->From 4bcb267a248977c6b044e18a72266d665102de30 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Tue, 18 Jan 2011 10:33:01 +0000 Subject: [PATCH 2/4] fish: Make exit_on_error into a completely local variable. Note that 'time' and 'glob' (which both run subcommands) do not correctly pass the exit_on_error flag in the remote case. This is not a regression: the current code doesn't work either. --- fish/fish.c | 18 ++++++++++++------ fish/fish.h | 2 +- fish/glob.c | 2 +- fish/rc.c | 2 +- fish/time.c | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index 711073a..fbc364f 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -69,7 +69,6 @@ int verbose = 0; int remote_control_listen = 0; int remote_control_csh = 0; int remote_control = 0; -static int exit_on_error = 1; int command_num = 0; int keys_from_stdin = 0; int echo_keys = 0; @@ -611,6 +610,7 @@ script (int prompt) char *argv[64]; int len; int global_exit_on_error = !prompt; + int exit_on_error; int tilde_candidate; if (prompt) { @@ -798,7 +798,7 @@ script (int prompt) argv[i] = NULL; got_command: - if (issue_command (cmd, argv, pipe) == -1) { + if (issue_command (cmd, argv, pipe, exit_on_error) == -1) { if (exit_on_error) exit (EXIT_FAILURE); } @@ -812,6 +812,7 @@ cmdline (char *argv[], int optind, int argc) { const char *cmd; char **params; + int exit_on_error; exit_on_error = 1; @@ -838,18 +839,23 @@ cmdline (char *argv[], int optind, int argc) 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; @@ -912,7 +918,7 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) /* 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")) { diff --git a/fish/fish.h b/fish/fish.h index f4b6c9c..da0c6a7 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -63,7 +63,7 @@ extern int have_terminfo; extern int progress_bars; extern int remote_control_csh; extern const char *libvirt_uri; -extern int issue_command (const char *cmd, char *argv[], const char *pipe); +extern int issue_command (const char *cmd, char *argv[], const char *pipe, int rc_exit_on_error_flag); extern void list_builtin_commands (void); extern int display_builtin_command (const char *cmd); extern void free_strings (char **argv); diff --git a/fish/glob.c b/fish/glob.c index 509532b..8250c13 100644 --- a/fish/glob.c +++ b/fish/glob.c @@ -144,7 +144,7 @@ glob_issue (char *cmd, size_t argc, for (i = 1; i < argc; ++i) argv[i] = globs[i][posn[i]]; - if (issue_command (argv[0], &argv[1], NULL) == -1) + if (issue_command (argv[0], &argv[1], NULL, 0) == -1) *r = -1; /* ... but don't exit */ for (i = argc-1; i >= 1; --i) { diff --git a/fish/rc.c b/fish/rc.c index a2bde4a..721c613 100644 --- a/fish/rc.c +++ b/fish/rc.c @@ -284,7 +284,7 @@ rc_listen (void) } /* Run the command. */ - reply.r = issue_command (call.cmd, argv, NULL); + reply.r = issue_command (call.cmd, argv, NULL, 0); xdr_free ((xdrproc_t) xdr_guestfish_call, (char *) &call); diff --git a/fish/time.c b/fish/time.c index 931403f..3a46049 100644 --- a/fish/time.c +++ b/fish/time.c @@ -39,7 +39,7 @@ run_time (const char *cmd, size_t argc, char *argv[]) gettimeofday (&start_t, NULL); - if (issue_command (argv[0], &argv[1], NULL) == -1) + if (issue_command (argv[0], &argv[1], NULL, 0) == -1) return -1; gettimeofday (&end_t, NULL); -- 1.7.3.4
Richard W.M. Jones
2011-Jan-18 12:10 UTC
[Libguestfs] [PATCH 3/4] fish: Factor out command line parsing.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html -------------- next part -------------->From 61a4db138e4f85033c655bf6b24df0949683c24c Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Tue, 18 Jan 2011 11:24:38 +0000 Subject: [PATCH 3/4] fish: Factor out command line parsing. Factor out the code which splits a string into a command line. --- fish/fish.c | 324 +++++++++++++++++++++++++++++++---------------------------- 1 files changed, 170 insertions(+), 154 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index fbc364f..566d769 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -45,6 +45,14 @@ #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); @@ -52,6 +60,7 @@ static void interactive (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 void initialize_readline (void); static void cleanup_readline (void); #ifdef HAVE_LIBREADLINE @@ -605,13 +614,9 @@ static void script (int prompt) { char *buf; - char *cmd; - char *p, *pend; - char *argv[64]; - int len; int global_exit_on_error = !prompt; int exit_on_error; - int tilde_candidate; + struct parsed_command pcmd; if (prompt) { printf (_("\n" @@ -630,8 +635,6 @@ script (int prompt) } while (!quit) { - char *pipe = NULL; - exit_on_error = global_exit_on_error; buf = rl_gets (prompt); @@ -640,171 +643,184 @@ script (int prompt) break; } - /* Skip any initial whitespace before the command. */ - again: - while (*buf && c_isspace (*buf)) - buf++; - - if (!*buf) continue; + 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 the next character is '#' then this is a comment. */ - 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 pass the whole lot to system(3). */ - if (*buf == '!') { - int r; + pcmd.pipe = NULL; - 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; - } + again: + /* Skip any initial whitespace before the command. */ + while (*buf && c_isspace (*buf)) + buf++; - /* 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 (!*buf) { + pcmd.status = 0; + return pcmd; + } - /* Get the command (cannot be quoted). */ - len = strcspn (buf, " \t"); + /* If the next character is '#' then this is a comment. */ + if (*buf == '#') { + pcmd.status = 0; + return pcmd; + } - if (len == 0) continue; + /* 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; + } - 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; + } - /* 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); - 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; - } - 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 (); - } + pcmd.cmd = buf; + unsigned int i = 0; + if (buf[len] == '\0') { + pcmd.argv[0] = NULL; + pcmd.status = 1; + return pcmd; + } - if (!tilde_candidate) - argv[i] = p; - else - argv[i] = try_tilde_expansion (p); - i++; - p = pend; + buf[len] = '\0'; + p = &buf[len+1]; + p += strspn (p, " \t"); - if (*p) - p += strspn (p, " \t"); - } + /* Get the parameters. */ + while (*p && i < argv_len) { + tilde_candidate = 0; - 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; + /* 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 (); } - argv[i] = NULL; + if (!tilde_candidate) + pcmd.argv[i] = p; + else + pcmd.argv[i] = try_tilde_expansion (p); + i++; + p = pend; - got_command: - if (issue_command (cmd, argv, pipe, exit_on_error) == -1) { - if (exit_on_error) exit (EXIT_FAILURE); - } + if (*p) + p += strspn (p, " \t"); + } - next_command:; + if (i == argv_len) { + fprintf (stderr, _("%s: too many arguments\n"), program_name); + pcmd.status = -1; + return pcmd; } - if (prompt) printf ("\n"); + + pcmd.argv[i] = NULL; + + pcmd.status = 1; + return pcmd; } static void -- 1.7.3.4
Richard W.M. Jones
2011-Jan-18 12:10 UTC
[Libguestfs] [PATCH 4/4] fish: <! cmd executes a shell command and inlines the resulting commands.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 487cc792a77308c1544b2ed8b1882e9a2fafeb09 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Tue, 18 Jan 2011 11:46:03 +0000 Subject: [PATCH 4/4] fish: <! cmd executes a shell command and inlines the resulting commands. The new guestfish construct "<! cmd" executes the shell command "cmd", and then anything printed to stdout by "cmd" is parsed and executed as a guestfish command. This allows some very hairy shell scripting with guestfish. --- fish/fish.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fish/guestfish.pod | 22 ++++++++++++++++++++ 2 files changed, 79 insertions(+), 0 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index 566d769..4a960dc 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -61,6 +61,7 @@ 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 @@ -708,6 +709,18 @@ parse_command_line (char *buf, int *exit_on_error_rtn) 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). */ @@ -823,6 +836,50 @@ parse_command_line (char *buf, int *exit_on_error_rtn) 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) { diff --git a/fish/guestfish.pod b/fish/guestfish.pod index 21f25bd..ece3d29 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -676,6 +676,28 @@ C<local/remote-data.tar.gz>. (See C<tgz-out>). To change the local directory, use the C<lcd> command. C<!cd> will have no effect, due to the way that subprocesses work in Unix. +=head2 LOCAL COMMANDS WITH INLINE EXECUTION + +If a line starts with I<E<lt>!> then the command is executed (as for +I<!>), but subsequently any output (stdout) of the command is parsed +and executed as guestfish commands. + +Thus you can use shell script to construct arbitrary guestfish +commands which are then parsed by guestfish. + +For example it is tedious to create a sequence of files +(eg. C</foo.1> through C</foo.100>) using guestfish commands +alone. However this is simple if we use a shell script to +create the guestfish commands for us: + + <! for n in `seq 1 100`; do echo write /foo.$n $n; done + +When using guestfish interactively it can be helpful to just run the +shell script first (ie. remove the initial C<E<lt>> character so it is +just an ordinary I<!> local command), see what guestfish commands it +would run, and when you are happy with those prepend the C<E<lt>> +character to run the guestfish commands for real. + =head1 PIPES Use C<command E<lt>spaceE<gt> | command> to pipe the output of the -- 1.7.3.4
Possibly Parallel Threads
- [PATCH 0/4] Another four patches to get guestfish working
- [PATCH 0/8 v2] Complete fix for CVE-2010-3851.
- [PATCH febootstrap 0/8] Add support for building an ext2-based appliance
- [PATCH 0/5] 5 conservative changes to errno handling
- [PATCH 0/7] Prepare for adding write support to hivex (windows registry) library