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

Commit dcb8f3f6 authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Snap for 5381581 from 178e86bb to qt-release

Change-Id: I7a7b664077600a544ddeff47021cdc06dfc40c75
parents ffde3047 178e86bb
Loading
Loading
Loading
Loading
+109 −48
Original line number Diff line number Diff line
@@ -131,6 +131,19 @@ static const std::string ANR_FILE_PREFIX = "anr_";
// TODO: temporary variables and functions used during C++ refactoring
static Dumpstate& ds = Dumpstate::GetInstance();

#define RETURN_IF_USER_DENIED_CONSENT()                                                        \
    if (ds.IsUserConsentDenied()) {                                                            \
        MYLOGE("Returning early as user denied consent to share bugreport with calling app."); \
        return Dumpstate::RunStatus::USER_CONSENT_DENIED;                                      \
    }

// Runs func_ptr, but checks user consent before and after running it. Returns USER_CONSENT_DENIED
// if consent is found to be denied.
#define RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(func_ptr, ...) \
    RETURN_IF_USER_DENIED_CONSENT();                        \
    func_ptr(__VA_ARGS__);                                  \
    RETURN_IF_USER_DENIED_CONSENT();

namespace android {
namespace os {
namespace {
@@ -1054,7 +1067,7 @@ static void DumpIpAddrAndRules() {
    RunCommand("IP RULES v6", {"ip", "-6", "rule", "show"});
}

static void RunDumpsysTextByPriority(const std::string& title, int priority,
static Dumpstate::RunStatus RunDumpsysTextByPriority(const std::string& title, int priority,
                                                     std::chrono::milliseconds timeout,
                                                     std::chrono::milliseconds service_timeout) {
    auto start = std::chrono::steady_clock::now();
@@ -1064,6 +1077,7 @@ static void RunDumpsysTextByPriority(const std::string& title, int priority,
    Dumpsys::setServiceArgs(args, /* asProto = */ false, priority);
    Vector<String16> services = dumpsys.listServices(priority, /* supports_proto = */ false);
    for (const String16& service : services) {
        RETURN_IF_USER_DENIED_CONSENT();
        std::string path(title);
        path.append(" - ").append(String8(service).c_str());
        DumpstateSectionReporter section_reporter(path, ds.listener_, ds.report_section_);
@@ -1089,6 +1103,7 @@ static void RunDumpsysTextByPriority(const std::string& title, int priority,
            break;
        }
    }
    return Dumpstate::RunStatus::OK;
}

static void RunDumpsysText(const std::string& title, int priority,
@@ -1101,7 +1116,7 @@ static void RunDumpsysText(const std::string& title, int priority,
}

/* Dump all services registered with Normal or Default priority. */
static void RunDumpsysTextNormalPriority(const std::string& title,
static Dumpstate::RunStatus RunDumpsysTextNormalPriority(const std::string& title,
                                                         std::chrono::milliseconds timeout,
                                                         std::chrono::milliseconds service_timeout) {
    DurationReporter duration_reporter(title);
@@ -1109,16 +1124,19 @@ static void RunDumpsysTextNormalPriority(const std::string& title,
    fsync(STDOUT_FILENO);
    RunDumpsysTextByPriority(title, IServiceManager::DUMP_FLAG_PRIORITY_NORMAL, timeout,
                             service_timeout);
    RunDumpsysTextByPriority(title, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT, timeout,

    RETURN_IF_USER_DENIED_CONSENT();

    return RunDumpsysTextByPriority(title, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT, timeout,
                                    service_timeout);
}

static void RunDumpsysProto(const std::string& title, int priority,
static Dumpstate::RunStatus RunDumpsysProto(const std::string& title, int priority,
                                            std::chrono::milliseconds timeout,
                                            std::chrono::milliseconds service_timeout) {
    if (!ds.IsZipping()) {
        MYLOGD("Not dumping %s because it's not a zipped bugreport\n", title.c_str());
        return;
        return Dumpstate::RunStatus::OK;
    }
    sp<android::IServiceManager> sm = defaultServiceManager();
    Dumpsys dumpsys(sm.get());
@@ -1129,6 +1147,7 @@ static void RunDumpsysProto(const std::string& title, int priority,
    auto start = std::chrono::steady_clock::now();
    Vector<String16> services = dumpsys.listServices(priority, /* supports_proto = */ true);
    for (const String16& service : services) {
        RETURN_IF_USER_DENIED_CONSENT();
        std::string path(kProtoPath);
        path.append(String8(service).c_str());
        if (priority == IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL) {
@@ -1157,31 +1176,41 @@ static void RunDumpsysProto(const std::string& title, int priority,
            break;
        }
    }
    return Dumpstate::RunStatus::OK;
}

// Runs dumpsys on services that must dump first and will take less than 100ms to dump.
static void RunDumpsysCritical() {
static Dumpstate::RunStatus RunDumpsysCritical() {
    RunDumpsysText("DUMPSYS CRITICAL", IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL,
                   /* timeout= */ 5s, /* service_timeout= */ 500ms);
    RunDumpsysProto("DUMPSYS CRITICAL PROTO", IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL,

    RETURN_IF_USER_DENIED_CONSENT();

    return RunDumpsysProto("DUMPSYS CRITICAL PROTO", IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL,
                           /* timeout= */ 5s, /* service_timeout= */ 500ms);
}

// Runs dumpsys on services that must dump first but can take up to 250ms to dump.
static void RunDumpsysHigh() {
static Dumpstate::RunStatus RunDumpsysHigh() {
    // TODO meminfo takes ~10s, connectivity takes ~5sec to dump. They are both
    // high priority. Reduce timeout once they are able to dump in a shorter time or
    // moved to a parallel task.
    RunDumpsysText("DUMPSYS HIGH", IServiceManager::DUMP_FLAG_PRIORITY_HIGH,
                   /* timeout= */ 90s, /* service_timeout= */ 30s);
    RunDumpsysProto("DUMPSYS HIGH PROTO", IServiceManager::DUMP_FLAG_PRIORITY_HIGH,

    RETURN_IF_USER_DENIED_CONSENT();

    return RunDumpsysProto("DUMPSYS HIGH PROTO", IServiceManager::DUMP_FLAG_PRIORITY_HIGH,
                           /* timeout= */ 5s, /* service_timeout= */ 1s);
}

// Runs dumpsys on services that must dump but can take up to 10s to dump.
static void RunDumpsysNormal() {
static Dumpstate::RunStatus RunDumpsysNormal() {
    RunDumpsysTextNormalPriority("DUMPSYS", /* timeout= */ 90s, /* service_timeout= */ 10s);
    RunDumpsysProto("DUMPSYS PROTO", IServiceManager::DUMP_FLAG_PRIORITY_NORMAL,

    RETURN_IF_USER_DENIED_CONSENT();

    return RunDumpsysProto("DUMPSYS PROTO", IServiceManager::DUMP_FLAG_PRIORITY_NORMAL,
                           /* timeout= */ 90s, /* service_timeout= */ 10s);
}

@@ -1244,9 +1273,16 @@ static void DumpHals() {
    }
}

static void dumpstate() {
// Dumps various things. Returns early with status USER_CONSENT_DENIED if user denies consent
// via the consent they are shown. Ignores other errors that occur while running various
// commands. The consent checking is currently done around long running tasks, which happen to
// be distributed fairly evenly throughout the function.
static Dumpstate::RunStatus dumpstate() {
    DurationReporter duration_reporter("DUMPSTATE");

    // Dump various things. Note that anything that takes "long" (i.e. several seconds) should
    // check intermittently (if it's intrerruptable like a foreach on pids) and/or should be wrapped
    // in a consent check (via RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK).
    dump_dev_files("TRUSTY VERSION", "/sys/bus/platform/drivers/trusty", "trusty_version");
    RunCommand("UPTIME", {"uptime"});
    DumpBlockStatFiles();
@@ -1254,7 +1290,9 @@ static void dumpstate() {
    DumpFile("MEMORY INFO", "/proc/meminfo");
    RunCommand("CPU INFO", {"top", "-b", "-n", "1", "-H", "-s", "6", "-o",
                            "pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"});
    RunCommand("PROCRANK", {"procrank"}, AS_ROOT_20);

    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunCommand, "PROCRANK", {"procrank"}, AS_ROOT_20);

    DumpFile("VIRTUAL MEMORY STATS", "/proc/vmstat");
    DumpFile("VMALLOC INFO", "/proc/vmallocinfo");
    DumpFile("SLAB INFO", "/proc/slabinfo");
@@ -1269,7 +1307,9 @@ static void dumpstate() {

    RunCommand("PROCESSES AND THREADS",
               {"ps", "-A", "-T", "-Z", "-O", "pri,nice,rtprio,sched,pcy,time"});
    RunCommand("LIBRANK", {"librank"}, CommandOptions::AS_ROOT);

    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunCommand, "LIBRANK", {"librank"},
                                         CommandOptions::AS_ROOT);

    DumpHals();

@@ -1290,7 +1330,9 @@ static void dumpstate() {
    }

    RunCommand("LIST OF OPEN FILES", {"lsof"}, CommandOptions::AS_ROOT);
    for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");

    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(for_each_pid, do_showmap, "SMAPS OF ALL PROCESSES");

    for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
    for_each_pid(show_showtime, "PROCESS TIMES (pid cmd user system iowait+percentage)");

@@ -1328,7 +1370,7 @@ static void dumpstate() {
    RunCommand("IPv6 ND CACHE", {"ip", "-6", "neigh", "show"});
    RunCommand("MULTICAST ADDRESSES", {"ip", "maddr"});

    RunDumpsysHigh();
    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysHigh);

    RunCommand("SYSTEM PROPERTIES", {"getprop"});

@@ -1351,7 +1393,7 @@ static void dumpstate() {
        ds.AddDir(WMTRACE_DATA_DIR, false);
    }

    ds.DumpstateBoard();
    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(ds.DumpstateBoard);

    /* Migrate the ril_dumpstate to a device specific dumpstate? */
    int rilDumpstateTimeout = android::base::GetIntProperty("ril.dumpstate.timeout", 0);
@@ -1371,14 +1413,16 @@ static void dumpstate() {
    printf("== Android Framework Services\n");
    printf("========================================================\n");

    RunDumpsysNormal();
    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysNormal);

    printf("========================================================\n");
    printf("== Checkins\n");
    printf("========================================================\n");

    RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"});
    RunDumpsys("CHECKIN MEMINFO", {"meminfo", "--checkin"});

    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsys, "CHECKIN MEMINFO", {"meminfo", "--checkin"});

    RunDumpsys("CHECKIN NETSTATS", {"netstats", "--checkin"});
    RunDumpsys("CHECKIN PROCSTATS", {"procstats", "-c"});
    RunDumpsys("CHECKIN USAGESTATS", {"usagestats", "-c"});
@@ -1442,19 +1486,27 @@ static void dumpstate() {
    printf("========================================================\n");
    // This differs from the usual dumpsys stats, which is the stats report data.
    RunDumpsys("STATSDSTATS", {"stats", "--metadata"});
    return Dumpstate::RunStatus::OK;
}

/* Dumps state for the default case. Returns true if everything went fine. */
static bool DumpstateDefault() {
/*
 * Dumps state for the default case; drops root after it's no longer necessary.
 *
 * Returns RunStatus::OK if everything went fine.
 * Returns RunStatus::ERROR if there was an error.
 * Returns RunStatus::USER_DENIED_CONSENT if user explicitly denied consent to sharing the bugreport
 * with the caller.
 */
static Dumpstate::RunStatus DumpstateDefault() {
    // Try to dump anrd trace if the daemon is running.
    dump_anrd_trace();

    // Invoking the following dumpsys calls before dump_traces() to try and
    // Invoking the following dumpsys calls before DumpTraces() to try and
    // keep the system stats as close to its initial state as possible.
    RunDumpsysCritical();
    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysCritical);

    /* collect stack traces from Dalvik and native processes (needs root) */
    dump_traces_path = ds.DumpTraces();
    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(ds.DumpTraces, &dump_traces_path);

    /* Run some operations that require root. */
    ds.tombstone_data_ = GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX, !ds.IsZipping());
@@ -1490,11 +1542,11 @@ static bool DumpstateDefault() {
    }

    if (!DropRootUser()) {
        return false;
        return Dumpstate::RunStatus::ERROR;
    }

    dumpstate();
    return true;
    RETURN_IF_USER_DENIED_CONSENT();
    return dumpstate();
}

// This method collects common dumpsys for telephony and wifi
@@ -1584,7 +1636,7 @@ static void DumpstateWifiOnly() {
    printf("========================================================\n");
}

const char* Dumpstate::DumpTraces() {
Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
    DurationReporter duration_reporter("DUMP TRACES");

    const std::string temp_file_pattern = "/data/anr/dumptrace_XXXXXX";
@@ -1600,7 +1652,7 @@ const char* Dumpstate::DumpTraces() {
    android::base::unique_fd fd(mkostemp(file_name_buf.get(), O_APPEND | O_CLOEXEC));
    if (fd < 0) {
        MYLOGE("mkostemp on pattern %s: %s\n", file_name_buf.get(), strerror(errno));
        return nullptr;
        return RunStatus::OK;
    }

    // Nobody should have access to this temporary file except dumpstate, but we
@@ -1610,13 +1662,13 @@ const char* Dumpstate::DumpTraces() {
    const int chmod_ret = fchmod(fd, 0666);
    if (chmod_ret < 0) {
        MYLOGE("fchmod on %s failed: %s\n", file_name_buf.get(), strerror(errno));
        return nullptr;
        return RunStatus::OK;
    }

    std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
    if (proc.get() == nullptr) {
        MYLOGE("opendir /proc failed: %s\n", strerror(errno));
        return nullptr;
        return RunStatus::OK;
    }

    // Number of times process dumping has timed out. If we encounter too many
@@ -1628,6 +1680,7 @@ const char* Dumpstate::DumpTraces() {

    struct dirent* d;
    while ((d = readdir(proc.get()))) {
        RETURN_IF_USER_DENIED_CONSENT();
        int pid = atoi(d->d_name);
        if (pid <= 0) {
            continue;
@@ -1689,7 +1742,8 @@ const char* Dumpstate::DumpTraces() {
        MYLOGE("Warning: no Dalvik processes found to dump stacks\n");
    }

    return file_name_buf.release();
    *path = file_name_buf.release();
    return RunStatus::OK;
}

void Dumpstate::DumpstateBoard() {
@@ -1730,6 +1784,7 @@ void Dumpstate::DumpstateBoard() {
        return;
    }

    // TODO(128270426): Check for consent in between?
    for (size_t i = 0; i < paths.size(); i++) {
        MYLOGI("Calling IDumpstateDevice implementation using path %s\n", paths[i].c_str());

@@ -2587,9 +2642,12 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
        DumpstateWifiOnly();
    } else {
        // Dump state for the default case. This also drops root.
        if (!DumpstateDefault()) {
            // Something went wrong.
            return RunStatus::ERROR;
        RunStatus s = DumpstateDefault();
        if (s != RunStatus::OK) {
            if (s == RunStatus::USER_CONSENT_TIMED_OUT) {
                HandleUserConsentDenied();
            }
            return s;
        }
    }

@@ -2626,9 +2684,7 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
            MYLOGI(
                "Did not receive user consent yet."
                " Will not copy the bugreport artifacts to caller.\n");
            // TODO(b/111441001):
            // 1. cancel outstanding requests
            // 2. check for result more frequently
            // TODO(b/111441001): cancel outstanding requests
        }
    }

@@ -2683,6 +2739,11 @@ void Dumpstate::CheckUserConsent(int32_t calling_uid, const android::String16& c
    }
}

bool Dumpstate::IsUserConsentDenied() const {
    return ds.consent_callback_ != nullptr &&
           ds.consent_callback_->getResult() == UserConsentResult::DENIED;
}

void Dumpstate::CleanupFiles() {
    android::os::UnlinkAndLogOnError(tmp_path_);
    android::os::UnlinkAndLogOnError(screenshot_path_);
+12 −2
Original line number Diff line number Diff line
@@ -291,8 +291,11 @@ class Dumpstate {
    // TODO: temporary method until Dumpstate object is properly set
    void SetProgress(std::unique_ptr<Progress> progress);

    // Dumps Dalvik and native stack traces, return the trace file location (nullptr if none).
    const char* DumpTraces();
    // Dumps Dalvik and native stack traces, sets the trace file location to path
    // if it succeeded.
    // Note that it returns early if user consent is denied with status USER_CONSENT_DENIED.
    // Returns OK in all other cases.
    RunStatus DumpTraces(const char** path);

    void DumpstateBoard();

@@ -330,6 +333,13 @@ class Dumpstate {
    /* Sets runtime options. */
    void SetOptions(std::unique_ptr<DumpOptions> options);

    /*
     * Returns true if user consent is necessary and has been denied.
     * Consent is only necessary if the caller has asked to copy over the bugreport to a file they
     * provided.
     */
    bool IsUserConsentDenied() const;

    /*
     * Structure to hold options that determine the behavior of dumpstate.
     */
+18 −4
Original line number Diff line number Diff line
@@ -102,14 +102,16 @@ DurationReporter::DurationReporter(const std::string& title, bool logcat_only)

DurationReporter::~DurationReporter() {
    if (!title_.empty()) {
        uint64_t elapsed = Nanotime() - started_;
        MYLOGD("Duration of '%s': %.3fs\n", title_.c_str(), (float)elapsed / NANOS_PER_SEC);
        float elapsed = (float)(Nanotime() - started_) / NANOS_PER_SEC;
        if (elapsed < .5f) {
            return;
        }
        MYLOGD("Duration of '%s': %.2fs\n", title_.c_str(), elapsed);
        if (logcat_only_) {
            return;
        }
        // Use "Yoda grammar" to make it easier to grep|sort sections.
        printf("------ %.3fs was the duration of '%s' ------\n", (float)elapsed / NANOS_PER_SEC,
               title_.c_str());
        printf("------ %.3fs was the duration of '%s' ------\n", elapsed, title_.c_str());
    }
}

@@ -278,6 +280,12 @@ static void __for_each_pid(void (*helper)(int, const char *, void *), const char

    if (header) printf("\n------ %s ------\n", header);
    while ((de = readdir(d))) {
        if (ds.IsUserConsentDenied()) {
            MYLOGE(
                "Returning early because user denied consent to share bugreport with calling app.");
            closedir(d);
            return;
        }
        int pid;
        int fd;
        char cmdpath[255];
@@ -350,6 +358,12 @@ static void for_each_tid_helper(int pid, const char *cmdline, void *arg) {
    func(pid, pid, cmdline);

    while ((de = readdir(d))) {
        if (ds.IsUserConsentDenied()) {
            MYLOGE(
                "Returning early because user denied consent to share bugreport with calling app.");
            closedir(d);
            return;
        }
        int tid;
        int fd;
        char commpath[255];
+1 −1
Original line number Diff line number Diff line
@@ -608,7 +608,7 @@ static unique_fd create_profile(uid_t uid, const std::string& profile, int32_t f
    // the app uid. If we cannot do that, there's no point in returning the fd
    // since dex2oat/profman will fail with SElinux denials.
    if (fchown(fd.get(), uid, uid) < 0) {
        PLOG(ERROR) << "Could not chwon profile " << profile;
        PLOG(ERROR) << "Could not chown profile " << profile;
        return invalid_unique_fd();
    }
    return fd;
+0 −71
Original line number Diff line number Diff line
@@ -41,23 +41,6 @@ using android::base::StringPrintf;
namespace android {
namespace installd {

// Configuration for bind-mounted Bionic artifacts.

static constexpr const char* kLinkerMountPoint = "/bionic/bin/linker";
static constexpr const char* kRuntimeLinkerPath = "/apex/com.android.runtime/bin/linker";

static constexpr const char* kBionicLibsMountPointDir = "/bionic/lib/";
static constexpr const char* kRuntimeBionicLibsDir = "/apex/com.android.runtime/lib/bionic/";

static constexpr const char* kLinkerMountPoint64 = "/bionic/bin/linker64";
static constexpr const char* kRuntimeLinkerPath64 = "/apex/com.android.runtime/bin/linker64";

static constexpr const char* kBionicLibsMountPointDir64 = "/bionic/lib64/";
static constexpr const char* kRuntimeBionicLibsDir64 = "/apex/com.android.runtime/lib64/bionic/";

static const std::vector<std::string> kBionicLibFileNames = {"libc.so", "libm.so", "libdl.so"};


static void CloseDescriptor(int fd) {
    if (fd >= 0) {
        int result = close(fd);
@@ -94,43 +77,6 @@ static void DeactivateApexPackages(const std::vector<apex::ApexFile>& active_pac
    }
}

// Copied from system/core/init/mount_namespace.cpp.
static bool BindMount(const std::string& source, const std::string& mount_point,
                      bool recursive = false) {
    unsigned long mountflags = MS_BIND;
    if (recursive) {
        mountflags |= MS_REC;
    }
    if (mount(source.c_str(), mount_point.c_str(), nullptr, mountflags, nullptr) == -1) {
        PLOG(ERROR) << "Could not bind-mount " << source << " to " << mount_point;
        return false;
    }
    return true;
}

// Copied from system/core/init/mount_namespace.cpp and and adjusted (bind
// mounts are not made private, as the /postinstall is already private (see
// `android::installd::otapreopt_chroot`).
static bool BindMountBionic(const std::string& linker_source, const std::string& lib_dir_source,
                            const std::string& linker_mount_point,
                            const std::string& lib_mount_dir) {
    if (access(linker_source.c_str(), F_OK) != 0) {
        PLOG(INFO) << linker_source << " does not exist. Skipping mounting Bionic there.";
        return true;
    }
    if (!BindMount(linker_source, linker_mount_point)) {
        return false;
    }
    for (const auto& libname : kBionicLibFileNames) {
        std::string mount_point = lib_mount_dir + libname;
        std::string source = lib_dir_source + libname;
        if (!BindMount(source, mount_point)) {
            return false;
        }
    }
    return true;
}

// Entry for otapreopt_chroot. Expected parameters are:
//   [cmd] [status-fd] [target-slot] "dexopt" [dexopt-params]
// The file descriptor denoted by status-fd will be closed. The rest of the parameters will
@@ -274,23 +220,6 @@ static int otapreopt_chroot(const int argc, char **arg) {
    // the Android Runtime APEX, as it is required by otapreopt to run dex2oat.
    std::vector<apex::ApexFile> active_packages = ActivateApexPackages();

    // Bind-mount Bionic artifacts from the Runtime APEX.
    // This logic is copied and adapted from system/core/init/mount_namespace.cpp.
    if (!BindMountBionic(kRuntimeLinkerPath, kRuntimeBionicLibsDir, kLinkerMountPoint,
                         kBionicLibsMountPointDir)) {
        LOG(ERROR) << "Failed to mount 32-bit Bionic artifacts from the Runtime APEX.";
        // Clean up and exit.
        DeactivateApexPackages(active_packages);
        exit(215);
    }
    if (!BindMountBionic(kRuntimeLinkerPath64, kRuntimeBionicLibsDir64, kLinkerMountPoint64,
                         kBionicLibsMountPointDir64)) {
        LOG(ERROR) << "Failed to mount 64-bit Bionic artifacts from the Runtime APEX.";
        // Clean up and exit.
        DeactivateApexPackages(active_packages);
        exit(216);
    }

    // Now go on and run otapreopt.

    // Incoming:  cmd + status-fd + target-slot + cmd...      | Incoming | = argc
Loading