Roughly equivalent to popen(...,"r"|"w") the new internal API allows you to run a subprocess and either read its stdout, or write to its stdin. --- src/command.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++++ src/guestfs-internal.h | 3 + 2 files changed, 152 insertions(+) diff --git a/src/command.c b/src/command.c index 8fb0714..0547111 100644 --- a/src/command.c +++ b/src/command.c @@ -133,6 +133,9 @@ struct command bool capture_errors; int errorfd; + /* When using the pipe_* APIs, stderr is pointed to a temporary file. */ + char *error_file; + /* Close file descriptors (defaults to true). */ bool close_files; @@ -711,6 +714,147 @@ guestfs_int_cmd_run (struct command *cmd) return wait_command (cmd); } +/* Fork and run the command, but don't wait. Roughly equivalent to + * popen (..., "r"|"w"). + * + * Returns the file descriptor of the pipe, connected to stdout ("r") + * or stdin ("w") of the child process. + * + * After reading/writing to this pipe, call guestfs_int_cmd_pipe_wait + * to wait for the status of the child. + * + * Errors from the subcommand cannot be captured to the error log + * using this interface. Instead the caller should call + * guestfs_int_cmd_get_pipe_errors (after guestfs_int_cmd_pipe_wait + * returns an error). + */ +int +guestfs_int_cmd_pipe_run (struct command *cmd, const char *mode) +{ + int fd[2] = { -1, -1 }; + int errfd = -1; + int r_mode; + int ret; + + finish_command (cmd); + + /* Various options cannot be used here. */ + assert (!cmd->capture_errors); + assert (!cmd->stdout_callback); + assert (!cmd->stderr_to_stdout); + + if (STREQ (mode, "r")) r_mode = 1; + else if (STREQ (mode, "w")) r_mode = 0; + else abort (); + + if (pipe2 (fd, O_CLOEXEC) == -1) { + perrorf (cmd->g, "pipe2"); + goto error; + } + + /* We can't easily capture errors from the child process, so instead + * we write them into a temporary file and provide a separate + * function for the caller to read the error messages. + */ + if (guestfs_int_lazy_make_tmpdir (cmd->g) == -1) + goto error; + + cmd->error_file = + safe_asprintf (cmd->g, "%s/cmderr.%d", cmd->g->tmpdir, ++cmd->g->unique); + errfd = open (cmd->error_file, + O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC|O_CLOEXEC, 0600); + if (errfd == -1) { + perrorf (cmd->g, "open: %s", cmd->error_file); + goto error; + } + + cmd->pid = fork (); + if (cmd->pid == -1) { + perrorf (cmd->g, "fork"); + goto error; + } + + /* Parent. */ + if (cmd->pid > 0) { + close (errfd); + errfd = -1; + + if (r_mode) { + close (fd[1]); + ret = fd[0]; + } + else { + close (fd[0]); + ret = fd[1]; + } + + return ret; + } + + /* Child. */ + dup2 (errfd, 2); + close (errfd); + + if (r_mode) { + close (fd[0]); + dup2 (fd[1], 1); + close (fd[1]); + } + else { + close (fd[1]); + dup2 (fd[0], 0); + close (fd[0]); + } + + run_child (cmd); + /*NOTREACHED*/ + + error: + if (errfd >= 0) + close (errfd); + if (fd[0] >= 0) + close (fd[0]); + if (fd[1] >= 0) + close (fd[1]); + return -1; +} + +/* Wait for a subprocess created by guestfs_int_cmd_pipe_run to + * finish. On error (eg. failed syscall) this returns -1 and sets the + * error. If the subcommand fails, then use WIF* macros to check + * this, and call guestfs_int_cmd_get_pipe_errors to read the error + * messages printed by the child. + */ +int +guestfs_int_cmd_pipe_wait (struct command *cmd) +{ + return wait_command (cmd); +} + +/* Read the error messages printed by the child. The caller must free + * the returned buffer after use. + */ +char * +guestfs_int_cmd_get_pipe_errors (struct command *cmd) +{ + char *ret; + size_t len; + + assert (cmd->error_file != NULL); + + if (guestfs_int_read_whole_file (cmd->g, cmd->error_file, &ret, NULL) == -1) + return NULL; + + /* If the file ends with \n characters, trim them. */ + len = strlen (ret); + while (len > 0 && ret[len-1] == '\n') { + ret[len-1] = '\0'; + len--; + } + + return ret; +} + void guestfs_int_cmd_close (struct command *cmd) { @@ -733,6 +877,11 @@ guestfs_int_cmd_close (struct command *cmd) break; } + if (cmd->error_file != NULL) { + unlink (cmd->error_file); + free (cmd->error_file); + } + if (cmd->errorfd >= 0) close (cmd->errorfd); diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 75ca71c..9c7175f 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -881,6 +881,9 @@ extern void guestfs_int_cmd_clear_close_files (struct command *); extern void guestfs_int_cmd_set_child_callback (struct command *, cmd_child_callback child_callback, void *data); extern int guestfs_int_cmd_run (struct command *); extern void guestfs_int_cmd_close (struct command *); +extern int guestfs_int_cmd_pipe_run (struct command *cmd, const char *mode); +extern int guestfs_int_cmd_pipe_wait (struct command *cmd); +extern char *guestfs_int_cmd_get_pipe_errors (struct command *cmd); #ifdef HAVE_ATTRIBUTE_CLEANUP #define CLEANUP_CMD_CLOSE __attribute__((cleanup(guestfs_int_cleanup_cmd_close))) -- 2.5.0