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

Commit dc58b0a3 authored by Patrick Baumann's avatar Patrick Baumann Committed by Todd Kennedy
Browse files

Adds atomic install support to adb

This change adds an install-atomic command to adb that is shorthand for
creating an atomic install session and an individual session for each
APK supplied to the command.

Bug: 109941548
Test: run command with multiple APKs, observe atomic install
Change-Id: I2817a1ed2d312925d9c7bd621e6c82670a6275fd
parent d75913a3
Loading
Loading
Loading
Loading
+191 −0
Original line number Diff line number Diff line
@@ -501,6 +501,197 @@ finalize_session:
    return EXIT_FAILURE;
}

int install_multi_package(int argc, const char** argv) {
    // Find all APK arguments starting at end.
    // All other arguments passed through verbatim.
    int first_apk = -1;
    for (int i = argc - 1; i >= 0; i--) {
        const char* file = argv[i];
        if (android::base::EndsWithIgnoreCase(file, ".apk")) {
            first_apk = i;
        } else {
            break;
        }
    }

    if (first_apk == -1) error_exit("need APK file on command line");

    if (use_legacy_install()) {
        fprintf(stderr, "adb: multi-package install is not supported on this device\n");
        return EXIT_FAILURE;
    }
    std::string install_cmd = "exec:cmd package";

    std::string multi_package_cmd =
            android::base::StringPrintf("%s install-create --multi-package", install_cmd.c_str());

    // Create multi-package install session
    std::string error;
    int fd = adb_connect(multi_package_cmd, &error);
    if (fd < 0) {
        fprintf(stderr, "adb: connect error for create multi-package: %s\n", error.c_str());
        return EXIT_FAILURE;
    }
    char buf[BUFSIZ];
    read_status_line(fd, buf, sizeof(buf));
    adb_close(fd);

    int parent_session_id = -1;
    if (!strncmp("Success", buf, 7)) {
        char* start = strrchr(buf, '[');
        char* end = strrchr(buf, ']');
        if (start && end) {
            *end = '\0';
            parent_session_id = strtol(start + 1, nullptr, 10);
        }
    }
    if (parent_session_id < 0) {
        fprintf(stderr, "adb: failed to create multi-package session\n");
        fputs(buf, stderr);
        return EXIT_FAILURE;
    }

    fprintf(stdout, "Created parent session ID %d.\n", parent_session_id);

    std::vector<int> session_ids;

    // Valid session, now create the individual sessions and stream the APKs
    int success = EXIT_FAILURE;
    std::string individual_cmd =
            android::base::StringPrintf("%s install-create", install_cmd.c_str());
    std::string all_session_ids = "";
    for (int i = 1; i < first_apk; i++) {
        individual_cmd += " " + escape_arg(argv[i]);
    }
    std::string cmd = "";
    for (int i = first_apk; i < argc; i++) {
        // Create individual install session
        fd = adb_connect(individual_cmd, &error);
        if (fd < 0) {
            fprintf(stderr, "adb: connect error for create: %s\n", error.c_str());
            goto finalize_multi_package_session;
        }
        char buf[BUFSIZ];
        read_status_line(fd, buf, sizeof(buf));
        adb_close(fd);

        int session_id = -1;
        if (!strncmp("Success", buf, 7)) {
            char* start = strrchr(buf, '[');
            char* end = strrchr(buf, ']');
            if (start && end) {
                *end = '\0';
                session_id = strtol(start + 1, nullptr, 10);
            }
        }
        if (session_id < 0) {
            fprintf(stderr, "adb: failed to create multi-package session\n");
            fputs(buf, stderr);
            goto finalize_multi_package_session;
        }

        fprintf(stdout, "Created child session ID %d.\n", session_id);
        session_ids.push_back(session_id);

        const char* file = argv[i];
        struct stat sb;
        if (stat(file, &sb) == -1) {
            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
            goto finalize_multi_package_session;
        }

        std::string cmd =
                android::base::StringPrintf("%s install-write -S %" PRIu64 " %d %d_%s -",
                                            install_cmd.c_str(), static_cast<uint64_t>(sb.st_size),
                                            session_id, i, android::base::Basename(file).c_str());

        int localFd = adb_open(file, O_RDONLY);
        if (localFd < 0) {
            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
            goto finalize_multi_package_session;
        }

        std::string error;
        int remoteFd = adb_connect(cmd, &error);
        if (remoteFd < 0) {
            fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
            adb_close(localFd);
            goto finalize_multi_package_session;
        }

        copy_to_file(localFd, remoteFd);
        read_status_line(remoteFd, buf, sizeof(buf));

        adb_close(localFd);
        adb_close(remoteFd);

        if (strncmp("Success", buf, 7)) {
            fprintf(stderr, "adb: failed to write %s\n", file);
            fputs(buf, stderr);
            goto finalize_multi_package_session;
        }

        all_session_ids += android::base::StringPrintf(" %d", session_id);
    }

    cmd = android::base::StringPrintf("%s install-add-session %d%s", install_cmd.c_str(),
                                      parent_session_id, all_session_ids.c_str());
    fd = adb_connect(cmd, &error);
    if (fd < 0) {
        fprintf(stderr, "adb: connect error for create: %s\n", error.c_str());
        goto finalize_multi_package_session;
    }
    read_status_line(fd, buf, sizeof(buf));
    adb_close(fd);

    if (strncmp("Success", buf, 7)) {
        fprintf(stderr, "adb: failed to link sessions (%s)\n", cmd.c_str());
        fputs(buf, stderr);
        goto finalize_multi_package_session;
    }

    // no failures means we can proceed with the assumption of success
    success = 0;

finalize_multi_package_session:
    // Commit session if we streamed everything okay; otherwise abandon
    std::string service =
            android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
                                        success == 0 ? "commit" : "abandon", parent_session_id);
    fd = adb_connect(service, &error);
    if (fd < 0) {
        fprintf(stderr, "adb: connect error for finalize: %s\n", error.c_str());
        return EXIT_FAILURE;
    }
    read_status_line(fd, buf, sizeof(buf));
    adb_close(fd);

    if (!strncmp("Success", buf, 7)) {
        fputs(buf, stdout);
        if (success == 0) {
            return 0;
        }
    } else {
        fprintf(stderr, "adb: failed to finalize session\n");
        fputs(buf, stderr);
    }

    // try to abandon all remaining sessions
    for (std::size_t i = 0; i < session_ids.size(); i++) {
        service = android::base::StringPrintf("%s install-abandon %d", install_cmd.c_str(),
                                              session_ids[i]);
        fprintf(stderr, "Attempting to abandon session ID %d\n", session_ids[i]);
        fd = adb_connect(service, &error);
        if (fd < 0) {
            fprintf(stderr, "adb: connect error for finalize: %s\n", error.c_str());
            continue;
        }
        read_status_line(fd, buf, sizeof(buf));
        adb_close(fd);
    }
    return EXIT_FAILURE;
}

int delete_device_file(const std::string& filename) {
    std::string cmd = "rm -f " + escape_arg(filename);
    return send_shell_command(cmd);
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@

int install_app(int argc, const char** argv);
int install_multiple_app(int argc, const char** argv);
int install_multi_package(int argc, const char** argv);
int uninstall_app(int argc, const char** argv);

int delete_device_file(const std::string& filename);
+7 −1
Original line number Diff line number Diff line
@@ -144,8 +144,11 @@ static void help() {
        "\n"
        "app installation:\n"
        " install [-lrtsdg] [--instant] PACKAGE\n"
        "     push a single package to the device and install it\n"
        " install-multiple [-lrtsdpg] [--instant] PACKAGE...\n"
        "     push package(s) to the device and install them\n"
        "     push multiple APKs to the device for a single package and install them\n"
        " install-multi-package [-lrtsdpg] [--instant] PACKAGE...\n"
        "     push one or more packages to the device and install them atomically\n"
        "     -r: replace existing application\n"
        "     -t: allow test packages\n"
        "     -d: allow version code downgrade (debuggable packages only)\n"
@@ -1734,6 +1737,9 @@ int adb_commandline(int argc, const char** argv) {
    } else if (!strcmp(argv[0], "install-multiple")) {
        if (argc < 2) error_exit("install-multiple requires an argument");
        return install_multiple_app(argc, argv);
    } else if (!strcmp(argv[0], "install-multi-package")) {
        if (argc < 3) error_exit("install-multi-package requires an argument");
        return install_multi_package(argc, argv);
    } else if (!strcmp(argv[0], "uninstall")) {
        if (argc < 2) error_exit("uninstall requires an argument");
        return uninstall_app(argc, argv);