[Libguestfs] [PATCH 6/7] cmd: New internal API for read/writing to a subprocess via a pipe.

Richard W.M. Jones rjones at redhat.com
Tue Sep 29 11:54:53 UTC 2015


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




More information about the Libguestfs mailing list