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
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 054cbac..bd5508c 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -501,6 +501,197 @@
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);
diff --git a/adb/client/adb_install.h b/adb/client/adb_install.h
index 5b6c4cb..9946604 100644
--- a/adb/client/adb_install.h
+++ b/adb/client/adb_install.h
@@ -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);
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 20fd036..1592c43 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -144,8 +144,11 @@
"\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 @@
} 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);