Moved some functions to DumpstateUtil.h.

dumpstate_board() is been refactored into a HIDL interface, and the HIDL
implementations will need help functions to dump files and run commands into
a file descriptor.

BUG: 31982882
Test: dumpstate_test passes
Test: manual verification

Change-Id: I7a32f0ac236dae34fd85abe47bed0e52a34c5f36
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index e11bf30..5b0ced9 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -22,6 +22,27 @@
         liblog \
         libselinux \
         libutils
+COMMON_STATIC_LIBRARIES := \
+        libdumpstateutil \
+        $(COMMON_ZIP_LIBRARIES)
+
+# ====================#
+# libdumpstateutil #
+# ====================#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libdumpstateutil
+
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_SRC_FILES := \
+        utils.cpp # TODO: temporary, until functions are moved to DumpstateUtil.cpp
+# TODO: include just what it uses once split from utils.cpp
+LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
+LOCAL_STATIC_LIBRARIES := $(COMMON_ZIP_LIBRARIES)
+
+include $(BUILD_STATIC_LIBRARY)
 
 # ====================#
 # libdumpstateheaders #
@@ -35,7 +56,7 @@
 LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := \
         $(COMMON_SHARED_LIBRARIES)
 LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := \
-        $(COMMON_ZIP_LIBRARIES)
+        $(COMMON_STATIC_LIBRARIES)
 # Soong requires that whats is on LOCAL_EXPORTED_ is also on LOCAL_
 LOCAL_SHARED_LIBRARIES := $(LOCAL_EXPORT_SHARED_LIBRARY_HEADERS)
 LOCAL_STATIC_LIBRARIES := $(LOCAL_EXPORT_STATIC_LIBRARY_HEADERS)
@@ -81,7 +102,7 @@
 
 LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
 
-LOCAL_STATIC_LIBRARIES := $(COMMON_ZIP_LIBRARIES)
+LOCAL_STATIC_LIBRARIES := $(COMMON_STATIC_LIBRARIES)
 
 LOCAL_HAL_STATIC_LIBRARIES := libdumpstate
 
@@ -106,7 +127,7 @@
         DumpstateService.cpp \
         tests/dumpstate_test.cpp
 
-LOCAL_STATIC_LIBRARIES := $(COMMON_ZIP_LIBRARIES) \
+LOCAL_STATIC_LIBRARIES := $(COMMON_STATIC_LIBRARIES) \
         libgmock
 
 LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
diff --git a/cmds/dumpstate/DumpstateUtil.h b/cmds/dumpstate/DumpstateUtil.h
new file mode 100644
index 0000000..9c60f0d
--- /dev/null
+++ b/cmds/dumpstate/DumpstateUtil.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef FRAMEWORK_NATIVE_CMD_DUMPSTATE_UTIL_H_
+#define FRAMEWORK_NATIVE_CMD_DUMPSTATE_UTIL_H_
+
+/*
+ * Defines the Linux user that should be executing a command.
+ */
+enum RootMode {
+    /* Explicitly change the `uid` and `gid` to be `shell`.*/
+    DROP_ROOT,
+    /* Don't change the `uid` and `gid`. */
+    DONT_DROP_ROOT,
+    /* Prefix the command with `/PATH/TO/su root`. Won't work non user builds. */
+    SU_ROOT
+};
+
+/*
+ * Defines what should happen with the `stdout` stream of a command.
+ */
+enum StdoutMode {
+    /* Don't change `stdout`. */
+    NORMAL_STDOUT,
+    /* Redirect `stdout` to `stderr`. */
+    REDIRECT_TO_STDERR
+};
+
+/*
+ * Value object used to set command options.
+ *
+ * Typically constructed using a builder with chained setters. Examples:
+ *
+ *  CommandOptions::WithTimeout(20).AsRoot().Build();
+ *  CommandOptions::WithTimeout(10).Always().RedirectStderr().Build();
+ *
+ * Although the builder could be used to dynamically set values. Example:
+ *
+ *  CommandOptions::CommandOptionsBuilder options =
+ *  CommandOptions::WithTimeout(10);
+ *  if (!is_user_build()) {
+ *    options.AsRoot();
+ *  }
+ *  RunCommand("command", {"args"}, options.Build());
+ */
+class CommandOptions {
+  private:
+    class CommandOptionsValues {
+      private:
+        CommandOptionsValues(int64_t timeout);
+
+        int64_t timeout_;
+        bool always_;
+        RootMode root_mode_;
+        StdoutMode stdout_mode_;
+        std::string logging_message_;
+
+        friend class CommandOptions;
+        friend class CommandOptionsBuilder;
+    };
+
+    CommandOptions(const CommandOptionsValues& values);
+
+    const CommandOptionsValues values;
+
+  public:
+    class CommandOptionsBuilder {
+      public:
+        /* Sets the command to always run, even on `dry-run` mode. */
+        CommandOptionsBuilder& Always();
+        /* Sets the command's RootMode as `SU_ROOT` */
+        CommandOptionsBuilder& AsRoot();
+        /* Sets the command's RootMode as `DROP_ROOT` */
+        CommandOptionsBuilder& DropRoot();
+        /* Sets the command's StdoutMode `REDIRECT_TO_STDERR` */
+        CommandOptionsBuilder& RedirectStderr();
+        /* When not empty, logs a message before executing the command.
+         * Must contain a `%s`, which will be replaced by the full command line, and end on `\n`. */
+        CommandOptionsBuilder& Log(const std::string& message);
+        /* Builds the command options. */
+        CommandOptions Build();
+
+      private:
+        CommandOptionsBuilder(int64_t timeout);
+        CommandOptionsValues values;
+        friend class CommandOptions;
+    };
+
+    /** Gets the command timeout, in seconds. */
+    int64_t Timeout() const;
+    /* Checks whether the command should always be run, even on dry-run mode. */
+    bool Always() const;
+    /** Gets the RootMode of the command. */
+    RootMode RootMode() const;
+    /** Gets the StdoutMode of the command. */
+    StdoutMode StdoutMode() const;
+    /** Gets the logging message header, it any. */
+    std::string LoggingMessage() const;
+
+    /** Creates a builder with the requied timeout. */
+    static CommandOptionsBuilder WithTimeout(int64_t timeout);
+
+    // Common options.
+    static CommandOptions DEFAULT;
+    static CommandOptions AS_ROOT_5;
+    static CommandOptions AS_ROOT_10;
+    static CommandOptions AS_ROOT_20;
+};
+
+/*
+ * Forks a command, waits for it to finish, and returns its status.
+ *
+ * |fd| file descriptor that receives the command's 'stdout'.
+ * |full_command| array containing the command (first entry) and its arguments.
+ * Must contain at least one element.
+ * |options| optional argument defining the command's behavior.
+ * |description| optional description of the command to be used on log messages. If empty,
+ * the command path (without arguments) will be used instead.
+ */
+int RunCommandToFd(int fd, const std::vector<const char*>& full_command,
+                   const CommandOptions& options = CommandOptions::DEFAULT,
+                   const std::string& description = "");
+
+/*
+ * Dumps the contents of a file into a file descriptor.
+ *
+ * |fd| file descriptor where the file is dumped into.
+ * |path| location of the file to be dumped.
+ */
+int DumpFileToFd(int fd, const std::string& path);
+
+#endif  // FRAMEWORK_NATIVE_CMD_DUMPSTATE_UTIL_H_
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index ea70fe5..6348579 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -90,7 +90,7 @@
     return ds.RunCommand(title, fullCommand, options);
 }
 static void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsysArgs,
-                       const CommandOptions& options = CommandOptions::DEFAULT_DUMPSYS,
+                       const CommandOptions& options = Dumpstate::DEFAULT_DUMPSYS,
                        long dumpsysTimeout = 0) {
     return ds.RunDumpsys(title, dumpsysArgs, options, dumpsysTimeout);
 }
@@ -680,7 +680,8 @@
     printf("Network: %s\n", network.c_str());
 
     printf("Kernel: ");
-    JustDumpFile("", "/proc/version");
+    fflush(stdout);
+    DumpFileToFd(STDOUT_FILENO, "/proc/version");
     printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
     printf("Bugreport format version: %s\n", version_.c_str());
     printf("Dumpstate info: id=%d pid=%d dry_run=%d args=%s extra_options=%s\n", id_, pid_,
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 3d3d7ed..9f23a39 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -40,6 +40,7 @@
 #include <android-base/macros.h>
 #include <ziparchive/zip_writer.h>
 
+#include "DumpstateUtil.h"
 #include "android/os/BnDumpstate.h"
 
 // Workaround for const char *args[MAX_ARGS_ARRAY_SIZE] variables until they're converted to
@@ -53,28 +54,6 @@
 #endif
 
 /*
- * Defines the Linux user that should be executing a command.
- */
-enum RootMode {
-    /* Explicitly change the `uid` and `gid` to be `shell`.*/
-    DROP_ROOT,
-    /* Don't change the `uid` and `gid`. */
-    DONT_DROP_ROOT,
-    /* Prefix the command with `/PATH/TO/su root`. Won't work non user builds. */
-    SU_ROOT
-};
-
-/*
- * Defines what should happen with the `stdout` stream of a command.
- */
-enum StdoutMode {
-    /* Don't change `stdout`. */
-    NORMAL_STDOUT,
-    /* Redirect `stdout` to `stderr`. */
-    REDIRECT_TO_STDERR
-};
-
-/*
  * Helper class used to report how long it takes for a section to finish.
  *
  * Typical usage:
@@ -102,88 +81,6 @@
 };
 
 /*
- * Value object used to set command options.
- *
- * Typically constructed using a builder with chained setters. Examples:
- *
- *  CommandOptions::WithTimeout(20).AsRoot().Build();
- *  CommandOptions::WithTimeout(10).Always().RedirectStderr().Build();
- *
- * Although the builder could be used to dynamically set values. Example:
- *
- *  CommandOptions::CommandOptionsBuilder options =
- *  CommandOptions::WithTimeout(10);
- *  if (!is_user_build()) {
- *    options.AsRoot();
- *  }
- *  RunCommand("command", {"args"}, options.Build());
- */
-class CommandOptions {
-  private:
-    class CommandOptionsValues {
-      private:
-        CommandOptionsValues(long timeout);
-
-        long timeout_;
-        bool always_;
-        RootMode root_mode_;
-        StdoutMode stdout_mode_;
-        std::string logging_message_;
-
-        friend class CommandOptions;
-        friend class CommandOptionsBuilder;
-    };
-
-    CommandOptions(const CommandOptionsValues& values);
-
-    const CommandOptionsValues values;
-
-  public:
-    class CommandOptionsBuilder {
-      public:
-        /* Sets the command to always run, even on `dry-run` mode. */
-        CommandOptionsBuilder& Always();
-        /* Sets the command's RootMode as `SU_ROOT` */
-        CommandOptionsBuilder& AsRoot();
-        /* Sets the command's RootMode as `DROP_ROOT` */
-        CommandOptionsBuilder& DropRoot();
-        /* Sets the command's StdoutMode `REDIRECT_TO_STDERR` */
-        CommandOptionsBuilder& RedirectStderr();
-        /* When not empty, logs a message before executing the command.
-         * Must contain a `%s`, which will be replaced by the full command line, and end on `\n`. */
-        CommandOptionsBuilder& Log(const std::string& message);
-        /* Builds the command options. */
-        CommandOptions Build();
-
-      private:
-        CommandOptionsBuilder(long timeout);
-        CommandOptionsValues values;
-        friend class CommandOptions;
-    };
-
-    /** Gets the command timeout, in seconds. */
-    long Timeout() const;
-    /* Checks whether the command should always be run, even on dry-run mode. */
-    bool Always() const;
-    /** Gets the RootMode of the command. */
-    RootMode RootMode() const;
-    /** Gets the StdoutMode of the command. */
-    StdoutMode StdoutMode() const;
-    /** Gets the logging message header, it any. */
-    std::string LoggingMessage() const;
-
-    /** Creates a builder with the requied timeout. */
-    static CommandOptionsBuilder WithTimeout(long timeout);
-
-    // Common options.
-    static CommandOptions DEFAULT;
-    static CommandOptions DEFAULT_DUMPSYS;
-    static CommandOptions AS_ROOT_5;
-    static CommandOptions AS_ROOT_10;
-    static CommandOptions AS_ROOT_20;
-};
-
-/*
  * Keeps track of current progress and estimated max, saving stats on file to tune up future runs.
  *
  * Each `dumpstate` section contributes to the total weight by an individual weight, so the overall
@@ -272,6 +169,8 @@
     friend class DumpstateTest;
 
   public:
+    static CommandOptions DEFAULT_DUMPSYS;
+
     static Dumpstate& GetInstance();
 
     /*
@@ -316,8 +215,7 @@
      * timeout from `options`)
      */
     void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsys_args,
-                    const CommandOptions& options = CommandOptions::DEFAULT_DUMPSYS,
-                    long dumpsys_timeout = 0);
+                    const CommandOptions& options = DEFAULT_DUMPSYS, long dumpsys_timeout = 0);
 
     /*
      * Prints the contents of a file.
@@ -454,13 +352,6 @@
     Dumpstate(const std::string& version = VERSION_CURRENT, bool dry_run = false,
               const std::string& build_type = "user");
 
-    // Internal version of RunCommand that just runs it, without updating progress.
-    int JustRunCommand(const char* command, const char* path, std::vector<const char*>& args,
-                       const CommandOptions& options) const;
-
-    // Internal version of RunCommand that just dumps it, without updating progress.
-    int JustDumpFile(const std::string& title, const std::string& path) const;
-
     // Whether this is a dry run.
     bool dry_run_;
 
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 0d68901..9613576 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -548,7 +548,7 @@
             android::base::StringPrintf("%d %d\n", expected_runs, expected_average);
         std::string actual_content;
         ASSERT_TRUE(android::base::ReadFileToString(path, &actual_content))
-            << "could not read statsfrom" << path;
+            << "could not read stats from " << path;
         ASSERT_THAT(actual_content, StrEq(expected_content)) << "invalid stats on " << path;
     }
 };
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index b5f328d..c322d9f 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -97,12 +97,12 @@
 static const long STATS_MAX_AVERAGE = 100000;
 
 CommandOptions CommandOptions::DEFAULT = CommandOptions::WithTimeout(10).Build();
-CommandOptions CommandOptions::DEFAULT_DUMPSYS = CommandOptions::WithTimeout(30).Build();
+CommandOptions Dumpstate::DEFAULT_DUMPSYS = CommandOptions::WithTimeout(30).Build();
 CommandOptions CommandOptions::AS_ROOT_5 = CommandOptions::WithTimeout(5).AsRoot().Build();
 CommandOptions CommandOptions::AS_ROOT_10 = CommandOptions::WithTimeout(10).AsRoot().Build();
 CommandOptions CommandOptions::AS_ROOT_20 = CommandOptions::WithTimeout(20).AsRoot().Build();
 
-CommandOptions::CommandOptionsBuilder::CommandOptionsBuilder(long timeout) : values(timeout) {
+CommandOptions::CommandOptionsBuilder::CommandOptionsBuilder(int64_t timeout) : values(timeout) {
 }
 
 CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::Always() {
@@ -135,7 +135,7 @@
     return CommandOptions(values);
 }
 
-CommandOptions::CommandOptionsValues::CommandOptionsValues(long timeout)
+CommandOptions::CommandOptionsValues::CommandOptionsValues(int64_t timeout)
     : timeout_(timeout),
       always_(false),
       root_mode_(DONT_DROP_ROOT),
@@ -146,7 +146,7 @@
 CommandOptions::CommandOptions(const CommandOptionsValues& values) : values(values) {
 }
 
-long CommandOptions::Timeout() const {
+int64_t CommandOptions::Timeout() const {
     return values.timeout_;
 }
 
@@ -166,7 +166,7 @@
     return values.logging_message_;
 }
 
-CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeout(long timeout) {
+CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeout(int64_t timeout) {
     return CommandOptions::CommandOptionsBuilder(timeout);
 }
 
@@ -657,9 +657,9 @@
 }
 
 // TODO: when converted to a Dumpstate function, it should be const
-static int _dump_file_from_fd(const std::string& title, const char* path, int fd) {
+static int _dump_file_from_fd_to_fd(const std::string& title, const char* path, int fd, int out_fd) {
     if (!title.empty()) {
-        printf("------ %s (%s", title.c_str(), path);
+        dprintf(out_fd, "------ %s (%s", title.c_str(), path);
 
         struct stat st;
         // Only show the modification time of non-device files.
@@ -671,9 +671,9 @@
             char stamp[80];
             time_t mtime = st.st_mtime;
             strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
-            printf(": %s", stamp);
+            dprintf(out_fd, ": %s", stamp);
         }
-        printf(") ------\n");
+        dprintf(out_fd, ") ------\n");
     }
 
     bool newline = false;
@@ -688,24 +688,23 @@
         uint64_t elapsed = DurationReporter::Nanotime();
         int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, NULL, NULL, &tm));
         if (ret == -1) {
-            printf("*** %s: select failed: %s\n", path, strerror(errno));
+            dprintf(out_fd, "*** %s: select failed: %s\n", path, strerror(errno));
             newline = true;
             break;
         } else if (ret == 0) {
             elapsed = DurationReporter::Nanotime() - elapsed;
-            printf("*** %s: Timed out after %.3fs\n", path,
-                   (float) elapsed / NANOS_PER_SEC);
+            dprintf(out_fd, "*** %s: Timed out after %.3fs\n", path, (float)elapsed / NANOS_PER_SEC);
             newline = true;
             break;
         } else {
             char buffer[65536];
             ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
             if (bytes_read > 0) {
-                fwrite(buffer, bytes_read, 1, stdout);
+                android::base::WriteFully(out_fd, buffer, bytes_read);
                 newline = (buffer[bytes_read-1] == '\n');
             } else {
                 if (bytes_read == -1) {
-                    printf("*** %s: Failed to read from fd: %s", path, strerror(errno));
+                    dprintf(out_fd, "*** %s: Failed to read from fd: %s", path, strerror(errno));
                     newline = true;
                 }
                 break;
@@ -715,11 +714,31 @@
     UpdateProgress(WEIGHT_FILE);
     close(fd);
 
-    if (!newline) printf("\n");
-    if (!title.empty()) printf("\n");
+    if (!newline) dprintf(out_fd, "\n");
+    if (!title.empty()) dprintf(out_fd, "\n");
     return 0;
 }
 
+// Internal function used by both DumpFile and DumpFileToFd - the former wants to print title
+// information, while the later doesn't.
+static int DumpFileToFd(const std::string& title, int out_fd, const std::string& path) {
+    int fd = TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+    if (fd < 0) {
+        int err = errno;
+        if (title.empty()) {
+            printf("*** Error dumping %s: %s\n", path.c_str(), strerror(err));
+        } else {
+            printf("*** Error dumping %s (%s): %s\n", path.c_str(), title.c_str(), strerror(err));
+        }
+        return -1;
+    }
+    return _dump_file_from_fd_to_fd(title, path.c_str(), fd, out_fd);
+}
+
+int DumpFileToFd(int out_fd, const std::string& path) {
+    return DumpFileToFd("", out_fd, path);
+}
+
 int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
     DurationReporter duration_reporter(title);
     if (IsDryRun()) {
@@ -730,21 +749,7 @@
         UpdateProgress(WEIGHT_FILE);
         return 0;
     }
-    return JustDumpFile(title, path);
-}
-
-int Dumpstate::JustDumpFile(const std::string& title, const std::string& path) const {
-    int fd = TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
-    if (fd < 0) {
-        int err = errno;
-        if (title.empty()) {
-            printf("*** Error dumping %s: %s\n", path.c_str(), strerror(err));
-        } else {
-            printf("*** Error dumping %s (%s): %s\n", path.c_str(), title.c_str(), strerror(err));
-        }
-        return -1;
-    }
-    return _dump_file_from_fd(title, path.c_str(), fd);
+    return DumpFileToFd(title, STDOUT_FILENO, path);
 }
 
 int read_file_as_long(const char *path, long int *output) {
@@ -855,7 +860,7 @@
         close(fd);
         return -1;
     }
-    return _dump_file_from_fd(title, path, fd);
+    return _dump_file_from_fd_to_fd(title, path, fd, STDOUT_FILENO);
 }
 
 bool waitpid_with_timeout(pid_t pid, int timeout_seconds, int* status) {
@@ -909,6 +914,8 @@
         return -1;
     }
 
+    // TODO: SU_ROOT logic must be moved to RunCommandToFd
+
     int size = full_command.size() + 1;  // null terminated
     int starting_index = 0;
     if (options.RootMode() == SU_ROOT) {
@@ -935,7 +942,6 @@
         }
     }
     args[i] = nullptr;
-    const char* path = args[0];
     const char* command = command_string.c_str();
 
     if (options.RootMode() == SU_ROOT && ds.IsUserBuild()) {
@@ -963,7 +969,7 @@
         return 0;
     }
 
-    int status = JustRunCommand(command, path, args, options);
+    int status = RunCommandToFd(STDOUT_FILENO, args, options, command);
 
     /* TODO: for now we're simplifying the progress calculation by using the
      * timeout as the weight. It's a good approximation for most cases, except when calling dumpsys,
@@ -978,8 +984,15 @@
     return status;
 }
 
-int Dumpstate::JustRunCommand(const char* command, const char* path, std::vector<const char*>& args,
-                              const CommandOptions& options) const {
+int RunCommandToFd(int fd, const std::vector<const char*>& full_command,
+                   const CommandOptions& options, const std::string& description) {
+    if (full_command.empty()) {
+        MYLOGE("No arguments on RunCommandToFd'\n");
+        return -1;
+    }
+    const char* path = full_command[0];
+    const char* command = description.empty() ? path : description.c_str();
+
     bool silent = (options.StdoutMode() == REDIRECT_TO_STDERR);
 
     uint64_t start = DurationReporter::Nanotime();
@@ -1001,9 +1014,13 @@
             return -1;
         }
 
+        if (STDOUT_FILENO != fd) {
+            TEMP_FAILURE_RETRY(dup2(fd, STDOUT_FILENO));
+            close(fd);
+        }
         if (silent) {
-            // Redirect stderr to stdout
-            dup2(STDERR_FILENO, STDOUT_FILENO);
+            // Redirect stderr to fd
+            dup2(STDERR_FILENO, fd);
         }
 
         /* make sure the child dies when dumpstate dies */
@@ -1015,11 +1032,10 @@
         sigact.sa_handler = SIG_IGN;
         sigaction(SIGPIPE, &sigact, NULL);
 
-        execvp(path, (char**)args.data());
+        execvp(path, (char**)full_command.data());
         // execvp's result will be handled after waitpid_with_timeout() below, but
         // if it failed, it's safer to exit dumpstate.
         MYLOGD("execvp on command '%s' failed (error: %s)\n", command, strerror(errno));
-        fflush(stdout);
         // Must call _exit (instead of exit), otherwise it will corrupt the zip
         // file.
         _exit(EXIT_FAILURE);
@@ -1028,6 +1044,8 @@
     /* handle parent case */
     int status;
     bool ret = waitpid_with_timeout(pid, options.Timeout(), &status);
+    fsync(fd);
+
     uint64_t elapsed = DurationReporter::Nanotime() - start;
     if (!ret) {
         if (errno == ETIMEDOUT) {