Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd9a5031 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Moved some functions to DumpstateUtil.h."

parents f360899b bda15a00
Loading
Loading
Loading
Loading
+24 −3
Original line number Diff line number Diff line
@@ -22,6 +22,27 @@ COMMON_SHARED_LIBRARIES := \
        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_MODULE := libdumpstateheaders
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_MODULE := dumpstate

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 @@ LOCAL_SRC_FILES := $(COMMON_SRC_FILES) \
        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)
+144 −0
Original line number Diff line number Diff line
/*
 * 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_
+3 −2
Original line number Diff line number Diff line
@@ -90,7 +90,7 @@ static int RunCommand(const std::string& title, const std::vector<std::string>&
    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);
}
@@ -683,7 +683,8 @@ void Dumpstate::PrintHeader() const {
    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_,
+4 −113
Original line number Diff line number Diff line
@@ -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
@@ -52,28 +53,6 @@
extern "C" {
#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.
 *
@@ -101,88 +80,6 @@ class DurationReporter {
    DISALLOW_COPY_AND_ASSIGN(DurationReporter);
};

/*
 * 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.
 *
@@ -272,6 +169,8 @@ class Dumpstate {
    friend class DumpstateTest;

  public:
    static CommandOptions DEFAULT_DUMPSYS;

    static Dumpstate& GetInstance();

    /*
@@ -316,8 +215,7 @@ class Dumpstate {
     * 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 @@ class Dumpstate {
    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_;

+58 −40
Original line number Diff line number Diff line
@@ -97,12 +97,12 @@ static const int STATS_MAX_N_RUNS = 1000;
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 @@ CommandOptions CommandOptions::CommandOptionsBuilder::Build() {
    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::CommandOptionsValues::CommandOptionsValues(long timeout)
CommandOptions::CommandOptions(const CommandOptionsValues& values) : values(values) {
}

long CommandOptions::Timeout() const {
int64_t CommandOptions::Timeout() const {
    return values.timeout_;
}

@@ -166,7 +166,7 @@ std::string CommandOptions::LoggingMessage() const {
    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 @@ void do_showmap(int pid, const char *name) {
}

// 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 @@ static int _dump_file_from_fd(const std::string& title, const char* path, int fd
            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 @@ static int _dump_file_from_fd(const std::string& title, const char* path, int fd
        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,25 +714,14 @@ static int _dump_file_from_fd(const std::string& title, const char* path, int fd
    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;
}

int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
    DurationReporter duration_reporter(title);
    if (IsDryRun()) {
        if (!title.empty()) {
            printf("------ %s (%s) ------\n", title.c_str(), path.c_str());
            printf("\t(skipped on dry run)\n");
        }
        UpdateProgress(WEIGHT_FILE);
        return 0;
    }
    return JustDumpFile(title, path);
}

int Dumpstate::JustDumpFile(const std::string& title, const std::string& path) const {
// 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;
@@ -744,7 +732,24 @@ int Dumpstate::JustDumpFile(const std::string& title, const std::string& path) c
        }
        return -1;
    }
    return _dump_file_from_fd(title, path.c_str(), fd);
    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()) {
        if (!title.empty()) {
            printf("------ %s (%s) ------\n", title.c_str(), path.c_str());
            printf("\t(skipped on dry run)\n");
        }
        UpdateProgress(WEIGHT_FILE);
        return 0;
    }
    return DumpFileToFd(title, STDOUT_FILENO, path);
}

int read_file_as_long(const char *path, long int *output) {
@@ -855,7 +860,7 @@ int dump_file_from_fd(const char *title, const char *path, int fd) {
        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 @@ int Dumpstate::RunCommand(const std::string& title, const std::vector<std::strin
        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 @@ int Dumpstate::RunCommand(const std::string& title, const std::vector<std::strin
        }
    }
    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 @@ int Dumpstate::RunCommand(const std::string& title, const std::vector<std::strin
        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 @@ int Dumpstate::RunCommand(const std::string& title, const std::vector<std::strin
    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 @@ int Dumpstate::JustRunCommand(const char* command, const char* path, std::vector
            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 @@ int Dumpstate::JustRunCommand(const char* command, const char* path, std::vector
        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 @@ int Dumpstate::JustRunCommand(const char* command, const char* path, std::vector
    /* 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) {
+1 −1

File changed.

Contains only whitespace changes.

Loading