[Libguestfs] [PATCH 01/10] lib: Add a new 'command' mini-library for running external commands.

Richard W.M. Jones rjones at redhat.com
Thu Oct 18 21:14:02 UTC 2012


From: "Richard W.M. Jones" <rjones at redhat.com>

This is a wrapper or mini-library for running external command,
loosely based on libvirt's virCommand interface.

Amongst the advantages are:

 - Can redirect errors into the error log (RHBZ#713678).
 - Can redirect output into a callback function.
 - Handles shell quoting properly.
 - Safely resets signal handlers, closes file descriptors, etc.
 - Single place where we can implement other improvements in future.
---
 po/POTFILES            |   1 +
 src/Makefile.am        |   1 +
 src/command.c          | 752 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/guestfs-internal.h |  18 ++
 4 files changed, 772 insertions(+)
 create mode 100644 src/command.c

diff --git a/po/POTFILES b/po/POTFILES
index 1de4fc0..a73377d 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -218,6 +218,7 @@ ruby/ext/guestfs/_guestfs.c
 src/actions.c
 src/appliance.c
 src/bindtests.c
+src/command.c
 src/dbdump.c
 src/errnostring-gperf.c
 src/errnostring.c
diff --git a/src/Makefile.am b/src/Makefile.am
index b189c4a..5f80bc1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -125,6 +125,7 @@ libguestfs_la_SOURCES = \
 	actions.c \
 	appliance.c \
 	bindtests.c \
+	command.c \
 	dbdump.c \
 	events.c \
 	file.c \
diff --git a/src/command.c b/src/command.c
new file mode 100644
index 0000000..01cd2c3
--- /dev/null
+++ b/src/command.c
@@ -0,0 +1,752 @@
+/* libguestfs
+ * Copyright (C) 2010-2012 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Wrapper for running external command, loosely based on libvirt's
+ * virCommand interface.  Read the comments at the top of each
+ * function for detailed information on how to use this interface.  In
+ * outline what you have to do is:
+ *
+ * (1) Create a new command handle:
+ *
+ *   struct command *cmd;
+ *   cmd = guestfs___new_command (g);
+ *
+ * (2) EITHER add arguments:
+ *
+ *   guestfs___cmd_add_arg (cmd, "qemu-img");
+ *   guestfs___cmd_add_arg (cmd, "info");
+ *   guestfs___cmd_add_arg (cmd, filename);
+ *
+ * NB: You don't need to add a NULL argument at the end.
+ *
+ * (3) OR construct a command using a mix of quoted and unquoted
+ * strings.  (This is useful for system(3)/popen("r")-style shell
+ * commands, with the added safety of allowing args to be quoted
+ * properly).
+ *
+ *   guestfs___cmd_add_string_unquoted (cmd, "qemu-img info ");
+ *   guestfs___cmd_add_string_quoted (cmd, filename);
+ *
+ * (4) Set various flags, such as whether you want to capture
+ * errors in the regular libguestfs error log.
+ *
+ * (5) Run the command.  This is what does the fork call, optionally
+ * loops over the output, and then does a waitpid and returns the
+ * exit status of the command.
+ *
+ *   r = guestfs___cmd_run (cmd);
+ *   if (r == -1)
+ *     // error
+ *   // else test r using the WIF* functions
+ *
+ * (6) Close the handle:
+ *
+ *   guestfs___cmd_close (cmd);
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+
+enum command_style {
+  COMMAND_STYLE_NOT_SELECTED = 0,
+  COMMAND_STYLE_EXECV = 1,
+  COMMAND_STYLE_SYSTEM = 2
+};
+
+struct command;
+
+static void add_line_buffer (struct command *cmd, const char *buf, size_t len);
+static void close_line_buffer (struct command *cmd);
+static void add_unbuffered (struct command *cmd, const char *buf, size_t len);
+static void add_whole_buffer (struct command *cmd, const char *buf, size_t len);
+static void close_whole_buffer (struct command *cmd);
+
+struct buffering {
+  char *buffer;
+  size_t len;
+  void (*add_data) (struct command *cmd, const char *buf, size_t len);
+  void (*close_data) (struct command *cmd);
+};
+
+struct command
+{
+  guestfs_h *g;
+
+  enum command_style style;
+  union {
+    /* COMMAND_STYLE_EXECV */
+    struct {
+      char **args;
+      size_t len, alloc;
+    } argv;
+    /* COMMAND_STYLE_SYSTEM */
+    struct {
+      char *str;
+      size_t len, alloc;
+    } string;
+  };
+
+  /* Capture errors to the error log (defaults to true). */
+  bool capture_errors;
+  int errorfd;
+
+  /* Supply a callback to receive stdout. */
+  cmd_stdout_callback stdout_callback;
+  void *stdout_data;
+  int outfd;
+  struct buffering outbuf;
+
+  /* For programs that send output to stderr.  Hello qemu. */
+  bool stderr_to_stdout;
+
+  /* PID of subprocess (if > 0). */
+  pid_t pid;
+};
+
+/* Create a new command handle. */
+struct command *
+guestfs___new_command (guestfs_h *g)
+{
+  struct command *cmd;
+
+  cmd = safe_calloc (g, 1, sizeof *cmd);
+  cmd->g = g;
+  cmd->capture_errors = true;
+  cmd->errorfd = -1;
+  cmd->outfd = -1;
+  return cmd;
+}
+
+/* Add single arg (for execv-style command execution). */
+static void
+add_arg_no_strdup (struct command *cmd, char *arg)
+{
+  assert (cmd->style != COMMAND_STYLE_SYSTEM);
+  cmd->style = COMMAND_STYLE_EXECV;
+
+  if (cmd->argv.len >= cmd->argv.alloc) {
+    if (cmd->argv.alloc == 0)
+      cmd->argv.alloc = 16;
+    else
+      cmd->argv.alloc *= 2;
+    cmd->argv.args = safe_realloc (cmd->g, cmd->argv.args,
+                                   cmd->argv.alloc * sizeof (char *));
+  }
+  cmd->argv.args[cmd->argv.len] = arg;
+  cmd->argv.len++;
+}
+
+static void
+add_arg (struct command *cmd, const char *arg)
+{
+  assert (arg != NULL);
+  add_arg_no_strdup (cmd, safe_strdup (cmd->g, arg));
+}
+
+void
+guestfs___cmd_add_arg (struct command *cmd, const char *arg)
+{
+  add_arg (cmd, arg);
+}
+
+void
+guestfs___cmd_add_arg_format (struct command *cmd, const char *fs, ...)
+{
+  va_list args;
+  char *arg;
+
+  va_start (args, fs);
+  int err = vasprintf (&arg, fs, args);
+  va_end (args);
+
+  if (err < 0)
+    cmd->g->abort_cb ();
+
+  add_arg_no_strdup (cmd, arg);
+}
+
+/* Add strings (for system(3)-style command execution). */
+static void
+add_string (struct command *cmd, const char *str, size_t len)
+{
+  assert (cmd->style != COMMAND_STYLE_EXECV);
+  cmd->style = COMMAND_STYLE_SYSTEM;
+
+  if (cmd->string.len >= cmd->string.alloc) {
+    if (cmd->string.alloc == 0)
+      cmd->string.alloc = 256;
+    else
+      cmd->string.alloc += MAX (cmd->string.alloc, len);
+    cmd->string.str = safe_realloc (cmd->g, cmd->string.str, cmd->string.alloc);
+  }
+
+  memcpy (&cmd->string.str[cmd->string.len], str, len);
+  cmd->string.len += len;
+}
+
+void
+guestfs___cmd_add_string_unquoted (struct command *cmd, const char *str)
+{
+  add_string (cmd, str, strlen (str));
+}
+
+/* Add a string enclosed in double quotes, with any special characters
+ * within the string which need escaping done.  This is used to add a
+ * single argument to a system(3)-style command string.
+ */
+void
+guestfs___cmd_add_string_quoted (struct command *cmd, const char *str)
+{
+  add_string (cmd, "\"", 1);
+
+  for (; *str; str++) {
+    if (*str == '$' ||
+        *str == '`' ||
+        *str == '\\' ||
+        *str == '"')
+      add_string (cmd, "\\", 1);
+    add_string (cmd, str, 1);
+  }
+
+  add_string (cmd, "\"", 1);
+}
+
+/* Set a callback which will capture stdout.
+ *
+ * If flags contains CMD_STDOUT_FLAG_LINE_BUFFER (the default), then
+ * the callback is called line by line on the output.  If there is a
+ * trailing \n then it is automatically removed before the callback is
+ * called.  The line buffer is \0-terminated.
+ *
+ * If flags contains CMD_STDOUT_FLAG_UNBUFFERED, then buffers are
+ * passed to the callback as it is received from the command.  Note in
+ * this case the buffer is NOT \0-terminated, so you need to may
+ * attention to the length field in the callback.
+ *
+ * If flags contains CMD_STDOUT_FLAG_WHOLE_BUFFER, then the callback
+ * is called exactly once, with the entire buffer.  Note in this case
+ * the buffer is NOT \0-terminated, so you need to may attention to
+ * the length field in the callback.
+ */
+void
+guestfs___cmd_set_stdout_callback (struct command *cmd,
+                                   cmd_stdout_callback stdout_callback,
+                                   void *stdout_data, unsigned flags)
+{
+  cmd->stdout_callback = stdout_callback;
+  cmd->stdout_data = stdout_data;
+
+  /* Buffering mode. */
+  if ((flags & 3) == CMD_STDOUT_FLAG_LINE_BUFFER) {
+    cmd->outbuf.add_data = add_line_buffer;
+    cmd->outbuf.close_data = close_line_buffer;
+  }
+  else if ((flags & 3) == CMD_STDOUT_FLAG_UNBUFFERED) {
+    cmd->outbuf.add_data = add_unbuffered;
+    cmd->outbuf.close_data = NULL;
+  }
+  else if ((flags & 3) == CMD_STDOUT_FLAG_WHOLE_BUFFER) {
+    cmd->outbuf.add_data = add_whole_buffer;
+    cmd->outbuf.close_data = close_whole_buffer;
+  }
+  else
+    abort ();
+}
+
+/* Equivalent to adding 2>&1 to the end of the command.  This is
+ * incompatible with the capture_errors flag, because it doesn't make
+ * sense to combine them.
+ */
+void
+guestfs___cmd_set_stderr_to_stdout (struct command *cmd)
+{
+  cmd->stderr_to_stdout = true;
+}
+
+/* Clear the capture_errors flag.  This means that any errors will go
+ * to stderr, instead of being captured in the event log, and that is
+ * usually undesirable.
+ */
+void
+guestfs___cmd_clear_capture_errors (struct command *cmd)
+{
+  cmd->capture_errors = false;
+}
+
+/* Finish off the command by either NULL-terminating the argv array or
+ * adding a terminating \0 to the string, or die with an internal
+ * error if no command has been added.
+ */
+static void
+finish_command (struct command *cmd)
+{
+  switch (cmd->style) {
+  case COMMAND_STYLE_EXECV:
+    add_arg_no_strdup (cmd, NULL);
+    break;
+
+  case COMMAND_STYLE_SYSTEM:
+    add_string (cmd, "\0", 1);
+    break;
+
+  case COMMAND_STYLE_NOT_SELECTED:
+  default:
+    abort ();
+  }
+}
+
+static void
+debug_command (struct command *cmd)
+{
+  size_t i, last;
+
+  switch (cmd->style) {
+  case COMMAND_STYLE_EXECV:
+    debug (cmd->g, "command: run: %s", cmd->argv.args[0]);
+    last = cmd->argv.len-1;     /* omit final NULL pointer */
+    for (i = 1; i < last; ++i) {
+      if (i < last-1 &&
+          cmd->argv.args[i][0] == '-' && cmd->argv.args[i+1][0] != '-') {
+        debug (cmd->g, "command: run: \\ %s %s",
+               cmd->argv.args[i], cmd->argv.args[i+1]);
+        i++;
+      }
+      else
+        debug (cmd->g, "command: run: \\ %s", cmd->argv.args[i]);
+    }
+    break;
+
+  case COMMAND_STYLE_SYSTEM:
+    debug (cmd->g, "command: run: %s", cmd->string.str);
+    break;
+
+  case COMMAND_STYLE_NOT_SELECTED:
+  default:
+    abort ();
+  }
+}
+
+static int
+run_command (struct command *cmd)
+{
+  struct sigaction sa;
+  int i, fd, max_fd, r;
+  int errorfd[2] = { -1, -1 };
+  int outfd[2] = { -1, -1 };
+
+  /* Set up a pipe to capture command output and send it to the error log. */
+  if (cmd->capture_errors) {
+    if (pipe2 (errorfd, O_CLOEXEC) == -1) {
+      perrorf (cmd->g, "pipe2");
+      goto error;
+    }
+  }
+
+  /* Set up a pipe to capture stdout for the callback. */
+  if (cmd->stdout_callback) {
+    if (pipe2 (outfd, O_CLOEXEC) == -1) {
+      perrorf (cmd->g, "pipe2");
+      goto error;
+    }
+  }
+
+  cmd->pid = fork ();
+  if (cmd->pid == -1) {
+    perrorf (cmd->g, "fork");
+    goto error;
+  }
+
+  /* In parent, return to caller. */
+  if (cmd->pid > 0) {
+    if (cmd->capture_errors) {
+      close (errorfd[1]);
+      errorfd[1] = -1;
+      cmd->errorfd = errorfd[0];
+      errorfd[0] = -1;
+    }
+
+    if (cmd->stdout_callback) {
+      close (outfd[1]);
+      outfd[1] = -1;
+      cmd->outfd = outfd[0];
+      outfd[0] = -1;
+    }
+
+    return 0;
+  }
+
+  /* Child process. */
+  if (cmd->capture_errors) {
+    close (errorfd[0]);
+    if (!cmd->stdout_callback)
+      dup2 (errorfd[1], 1);
+    dup2 (errorfd[1], 2);
+    close (errorfd[1]);
+  }
+
+  if (cmd->stdout_callback) {
+    close (outfd[0]);
+    dup2 (outfd[1], 1);
+    close (outfd[1]);
+  }
+
+  if (cmd->stderr_to_stdout)
+    dup2 (1, 2);
+
+  /* Remove all signal handlers.  See the justification here:
+   * https://www.redhat.com/archives/libvir-list/2008-August/msg00303.html
+   * We don't mask signal handlers yet, so this isn't completely
+   * race-free, but better than not doing it at all.
+   */
+  memset (&sa, 0, sizeof sa);
+  sa.sa_handler = SIG_DFL;
+  sa.sa_flags = 0;
+  sigemptyset (&sa.sa_mask);
+  for (i = 1; i < NSIG; ++i)
+    sigaction (i, &sa, NULL);
+
+  /* Close all other file descriptors.  This ensures that we don't
+   * hold open (eg) pipes from the parent process.
+   */
+  max_fd = sysconf (_SC_OPEN_MAX);
+  if (max_fd == -1)
+    max_fd = 1024;
+  if (max_fd > 65536)
+    max_fd = 65536;          /* bound the amount of work we do here */
+  for (fd = 3; fd < max_fd; ++fd)
+    close (fd);
+
+  /* Clean up the environment. */
+  setenv ("LC_ALL", "C", 1);
+
+  /* Set the umask for all subcommands to something sensible (RHBZ#610880). */
+  umask (022);
+
+  /* Run the command. */
+  switch (cmd->style) {
+  case COMMAND_STYLE_EXECV:
+    execvp (cmd->argv.args[0], cmd->argv.args);
+    perror (cmd->argv.args[0]);
+    _exit (EXIT_FAILURE);
+
+  case COMMAND_STYLE_SYSTEM:
+    r = system (cmd->string.str);
+    if (r == -1) {
+      perror ("system");
+      _exit (EXIT_FAILURE);
+    }
+    if (WIFEXITED (r))
+      _exit (WEXITSTATUS (r));
+    if (WIFSIGNALED (r)) {
+      fprintf (stderr, "%s: received signal %d\n", cmd->string.str,
+               WTERMSIG (r));
+      _exit (EXIT_FAILURE);
+    }
+    if (WIFSTOPPED (r)) {
+      fprintf (stderr, "%s: stopped by signal %d\n", cmd->string.str,
+               WSTOPSIG (r));
+      _exit (EXIT_FAILURE);
+    }
+    _exit (EXIT_FAILURE);
+
+  case COMMAND_STYLE_NOT_SELECTED:
+  default:
+    abort ();
+  }
+  /*NOTREACHED*/
+
+ error:
+  if (errorfd[0] >= 0)
+    close (errorfd[0]);
+  if (errorfd[1] >= 0)
+    close (errorfd[1]);
+  if (outfd[0] >= 0)
+    close (outfd[0]);
+  if (outfd[1] >= 0)
+    close (outfd[1]);
+
+  return -1;
+}
+
+/* The loop which reads errors and output and directs it either
+ * to the log or to the stdout callback as appropriate.
+ */
+static int
+loop (struct command *cmd)
+{
+  fd_set rset, rset2;
+  int maxfd = -1, r;
+  size_t nr_fds = 0;
+  char buf[BUFSIZ];
+  ssize_t n;
+
+  FD_ZERO (&rset);
+
+  if (cmd->errorfd >= 0) {
+    FD_SET (cmd->errorfd, &rset);
+    maxfd = MAX (cmd->errorfd, maxfd);
+    nr_fds++;
+  }
+
+  if (cmd->outfd >= 0) {
+    FD_SET (cmd->outfd, &rset);
+    maxfd = MAX (cmd->outfd, maxfd);
+    nr_fds++;
+  }
+
+  while (nr_fds > 0) {
+    rset2 = rset;
+    r = select (maxfd+1, &rset2, NULL, NULL, NULL);
+    if (r == -1) {
+      if (errno == EINTR || errno == EAGAIN)
+        continue;
+      perrorf (cmd->g, "select");
+      return -1;
+    }
+
+    if (cmd->errorfd >= 0 && FD_ISSET (cmd->errorfd, &rset2)) {
+      /* Read output and send it to the log. */
+      n = read (cmd->errorfd, buf, sizeof buf);
+      if (n > 0)
+        guestfs___call_callbacks_message (cmd->g, GUESTFS_EVENT_APPLIANCE,
+                                          buf, n);
+      else if (n == 0) {
+        if (close (cmd->errorfd) == -1)
+          perrorf (cmd->g, "close: errorfd");
+        FD_CLR (cmd->errorfd, &rset);
+        cmd->errorfd = -1;
+        nr_fds--;
+      }
+      else if (n == -1) {
+        perrorf (cmd->g, "read: errorfd");
+        close (cmd->errorfd);
+        FD_CLR (cmd->errorfd, &rset);
+        cmd->errorfd = -1;
+        nr_fds--;
+      }
+    }
+
+    if (cmd->outfd >= 0 && FD_ISSET (cmd->outfd, &rset2)) {
+      /* Read the output, buffer it up to the end of the line, then
+       * pass it to the callback.
+       */
+      n = read (cmd->outfd, buf, sizeof buf);
+      if (n > 0) {
+        if (cmd->outbuf.add_data)
+          cmd->outbuf.add_data (cmd, buf, n);
+      }
+      else if (n == 0) {
+        if (cmd->outbuf.close_data)
+          cmd->outbuf.close_data (cmd);
+        if (close (cmd->outfd) == -1)
+          perrorf (cmd->g, "close: outfd");
+        FD_CLR (cmd->outfd, &rset);
+        cmd->outfd = -1;
+        nr_fds--;
+      }
+      else if (n == -1) {
+        perrorf (cmd->g, "read: outfd");
+        close (cmd->outfd);
+        FD_CLR (cmd->outfd, &rset);
+        cmd->outfd = -1;
+        nr_fds--;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+wait_command (struct command *cmd)
+{
+  int status;
+
+  if (waitpid (cmd->pid, &status, 0) == -1) {
+    perrorf (cmd->g, "waitpid");
+    return -1;
+  }
+
+  cmd->pid = 0;
+
+  return status;
+}
+
+/* Fork, run the command, loop over the output, and waitpid.
+ *
+ * Returns the exit status.  Test it using WIF* macros.
+ *
+ * On error: Calls error(g) and returns -1.
+ */
+int
+guestfs___cmd_run (struct command *cmd)
+{
+  finish_command (cmd);
+
+  if (cmd->g->verbose)
+    debug_command (cmd);
+
+  if (run_command (cmd) == -1)
+    return -1;
+
+  if (loop (cmd) == -1)
+    return -1;
+
+  return wait_command (cmd);
+}
+
+void
+guestfs___cmd_close (struct command *cmd)
+{
+  size_t i;
+
+  switch (cmd->style) {
+  case COMMAND_STYLE_NOT_SELECTED:
+    /* nothing */
+    break;
+
+  case COMMAND_STYLE_EXECV:
+    for (i = 0; i < cmd->argv.len; ++i)
+      free (cmd->argv.args[i]);
+    free (cmd->argv.args);
+    break;
+
+  case COMMAND_STYLE_SYSTEM:
+    free (cmd->string.str);
+    break;
+
+  default:
+    abort ();
+  }
+
+  if (cmd->errorfd >= 0)
+    close (cmd->errorfd);
+
+  if (cmd->outfd >= 0)
+    close (cmd->outfd);
+
+  free (cmd->outbuf.buffer);
+
+  if (cmd->pid > 0)
+    waitpid (cmd->pid, NULL, 0);
+
+  free (cmd);
+}
+
+/* Deal with buffering stdout for the callback. */
+static void
+process_line_buffer (struct command *cmd, int closed)
+{
+  guestfs_h *g = cmd->g;
+  char *p;
+  size_t len, newlen;
+
+  while (cmd->outbuf.len > 0) {
+    /* Length of the next line. */
+    p = strchr (cmd->outbuf.buffer, '\n');
+    if (p != NULL) {            /* Got a whole line. */
+      len = p - cmd->outbuf.buffer;
+      newlen = cmd->outbuf.len - len - 1;
+    }
+    else if (closed) {          /* Consume rest of input even if no \n found. */
+      len = cmd->outbuf.len;
+      newlen = 0;
+    }
+    else                        /* Need to wait for more input. */
+      break;
+
+    /* Call the callback with the next line. */
+    cmd->outbuf.buffer[len] = '\0';
+    cmd->stdout_callback (g, cmd->stdout_data, cmd->outbuf.buffer, len);
+
+    /* Remove the consumed line from the buffer. */
+    cmd->outbuf.len = newlen;
+    memmove (cmd->outbuf.buffer, cmd->outbuf.buffer + len + 1, newlen);
+
+    /* Keep the buffer \0 terminated. */
+    cmd->outbuf.buffer[newlen] = '\0';
+  }
+}
+
+static void
+add_line_buffer (struct command *cmd, const char *buf, size_t len)
+{
+  guestfs_h *g = cmd->g;
+  size_t oldlen;
+
+  /* Append the new content to the end of the current buffer.  Keep
+   * the buffer \0 terminated to make things simple when processing
+   * the buffer.
+   */
+  oldlen = cmd->outbuf.len;
+  cmd->outbuf.len += len;
+  cmd->outbuf.buffer = safe_realloc (g, cmd->outbuf.buffer,
+                                     cmd->outbuf.len + 1 /* for \0 */);
+  memcpy (cmd->outbuf.buffer + oldlen, buf, len);
+  cmd->outbuf.buffer[cmd->outbuf.len] = '\0';
+
+  process_line_buffer (cmd, 0);
+}
+
+static void
+close_line_buffer (struct command *cmd)
+{
+  process_line_buffer (cmd, 1);
+}
+
+static void
+add_unbuffered (struct command *cmd, const char *buf, size_t len)
+{
+  cmd->stdout_callback (cmd->g, cmd->stdout_data, buf, len);
+}
+
+static void
+add_whole_buffer (struct command *cmd, const char *buf, size_t len)
+{
+  guestfs_h *g = cmd->g;
+  size_t oldlen;
+
+  /* Append the new content to the end of the current buffer. */
+  oldlen = cmd->outbuf.len;
+  cmd->outbuf.len += len;
+  cmd->outbuf.buffer = safe_realloc (g, cmd->outbuf.buffer, cmd->outbuf.len);
+  memcpy (cmd->outbuf.buffer + oldlen, buf, len);
+}
+
+static void
+close_whole_buffer (struct command *cmd)
+{
+  cmd->stdout_callback (cmd->g, cmd->stdout_data,
+                        cmd->outbuf.buffer, cmd->outbuf.len);
+}
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 90954f1..cf810e1 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -583,4 +583,22 @@ extern void guestfs___free_fuse (guestfs_h *g);
 extern virConnectPtr guestfs___open_libvirt_connection (guestfs_h *g, const char *uri, unsigned int flags);
 #endif
 
+/* command.c */
+struct command;
+typedef void (*cmd_stdout_callback) (guestfs_h *g, void *data, const char *line, size_t len);
+extern struct command *guestfs___new_command (guestfs_h *g);
+extern void guestfs___cmd_add_arg (struct command *, const char *arg);
+extern void guestfs___cmd_add_arg_format (struct command *, const char *fs, ...)
+  __attribute__((format (printf,2,3)));
+extern void guestfs___cmd_add_string_unquoted (struct command *, const char *str);
+extern void guestfs___cmd_add_string_quoted (struct command *, const char *str);
+extern void guestfs___cmd_set_stdout_callback (struct command *, cmd_stdout_callback stdout_callback, void *data, unsigned flags);
+#define CMD_STDOUT_FLAG_LINE_BUFFER    0
+#define CMD_STDOUT_FLAG_UNBUFFERED      1
+#define CMD_STDOUT_FLAG_WHOLE_BUFFER    2
+extern void guestfs___cmd_set_stderr_to_stdout (struct command *);
+extern void guestfs___cmd_clear_capture_errors (struct command *);
+extern int guestfs___cmd_run (struct command *);
+extern void guestfs___cmd_close (struct command *);
+
 #endif /* GUESTFS_INTERNAL_H_ */
-- 
1.7.11.4




More information about the Libguestfs mailing list