diff options
Diffstat (limited to 'adb/client/adb_install.cpp')
-rw-r--r-- | adb/client/adb_install.cpp | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp new file mode 100644 index 000000000..d965a57fe --- /dev/null +++ b/adb/client/adb_install.cpp @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2018 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. + */ + +#define TRACE_TAG ADB + +#include <stdio.h> +#include <stdlib.h> + +#include "adb.h" +#include "adb_client.h" +#include "adb_install.h" +#include "adb_utils.h" +#include "client/file_sync_client.h" +#include "commandline.h" +#include "fastdeploy.h" +#include "sysdeps.h" + +#include <algorithm> +#include <iostream> +#include <string> +#include <vector> + +#include <unistd.h> + +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/strings.h> + +static bool _use_legacy_install() { + FeatureSet features; + std::string error; + if (!adb_get_feature_set(&features, &error)) { + fprintf(stderr, "error: %s\n", error.c_str()); + return true; + } + return !CanUseFeature(features, kFeatureCmd); +} + +static int pm_command(int argc, const char** argv) { + std::string cmd = "pm"; + + while (argc-- > 0) { + cmd += " " + escape_arg(*argv++); + } + + return send_shell_command(cmd); +} + +static int uninstall_app_streamed(int argc, const char** argv) { + // 'adb uninstall' takes the same arguments as 'cmd package uninstall' on device + std::string cmd = "cmd package"; + while (argc-- > 0) { + // deny the '-k' option until the remaining data/cache can be removed with adb/UI + if (strcmp(*argv, "-k") == 0) { + printf("The -k option uninstalls the application while retaining the " + "data/cache.\n" + "At the moment, there is no way to remove the remaining data.\n" + "You will have to reinstall the application with the same " + "signature, and fully " + "uninstall it.\n" + "If you truly wish to continue, execute 'adb shell cmd package " + "uninstall -k'.\n"); + return EXIT_FAILURE; + } + cmd += " " + escape_arg(*argv++); + } + + return send_shell_command(cmd); +} + +static int uninstall_app_legacy(int argc, const char** argv) { + /* if the user choose the -k option, we refuse to do it until devices are + out with the option to uninstall the remaining data somehow (adb/ui) */ + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-k")) { + printf("The -k option uninstalls the application while retaining the " + "data/cache.\n" + "At the moment, there is no way to remove the remaining data.\n" + "You will have to reinstall the application with the same " + "signature, and fully " + "uninstall it.\n" + "If you truly wish to continue, execute 'adb shell pm uninstall " + "-k'\n."); + return EXIT_FAILURE; + } + } + + /* 'adb uninstall' takes the same arguments as 'pm uninstall' on device */ + return pm_command(argc, argv); +} + +int uninstall_app(int argc, const char** argv) { + if (_use_legacy_install()) { + return uninstall_app_legacy(argc, argv); + } + return uninstall_app_streamed(argc, argv); +} + +static void read_status_line(int fd, char* buf, size_t count) { + count--; + while (count > 0) { + int len = adb_read(fd, buf, count); + if (len <= 0) { + break; + } + + buf += len; + count -= len; + } + *buf = '\0'; +} + +static int delete_device_patch_file(const char* apkPath) { + std::string patchDevicePath = get_patch_path(apkPath); + return delete_device_file(patchDevicePath); +} + +std::string get_temp_host_filename() { +#ifdef _WIN32 + CHAR temp_path[MAX_PATH]; + CHAR temp_file_path[MAX_PATH]; + DWORD temp_path_result = GetTempPathA(MAX_PATH, temp_path); + if (temp_path_result == 0) { + printf("Error determining temp path\n"); + return std::string(""); + } else { + DWORD temp_file_name_result = GetTempFileNameA(temp_path, "", 0, temp_file_path); + if (temp_file_name_result == 0) { + printf("Error determining temp filename\n"); + return std::string(""); + } + return std::string(temp_file_path); + } +#else + return std::tmpnam(nullptr); +#endif +} + +static int install_app_streamed(int argc, const char** argv, bool use_fastdeploy, + bool use_localagent, const char* adb_path) { + printf("Performing Streamed Install\n"); + + // The last argument must be the APK file + const char* file = argv[argc - 1]; + if (!android::base::EndsWithIgnoreCase(file, ".apk")) { + return syntax_error("filename doesn't end .apk: %s", file); + } + + if (use_fastdeploy == true) { + std::string metadataTmpPath = get_temp_host_filename(); + std::string patchTmpPath = get_temp_host_filename(); + + FILE* metadataFile = fopen(metadataTmpPath.c_str(), "wb"); + int metadata_len = extract_metadata(file, metadataFile); + fclose(metadataFile); + + int result = -1; + if (metadata_len <= 0) { + printf("failed to extract metadata %d\n", metadata_len); + return 1; + } else { + int create_patch_result = create_patch(file, metadataTmpPath.c_str(), + patchTmpPath.c_str(), use_localagent, adb_path); + if (create_patch_result != 0) { + printf("Patch creation failure, error code: %d\n", create_patch_result); + result = create_patch_result; + goto cleanup_streamed_apk; + } else { + std::vector<const char*> pm_args; + // pass all but 1st (command) and last (apk path) parameters through to pm for + // session creation + for (int i = 1; i < argc - 1; i++) { + pm_args.push_back(argv[i]); + } + int apply_patch_result = + install_patch(file, patchTmpPath.c_str(), pm_args.size(), pm_args.data()); + if (apply_patch_result != 0) { + printf("Patch application failure, error code: %d\n", apply_patch_result); + result = apply_patch_result; + goto cleanup_streamed_apk; + } + } + } + + cleanup_streamed_apk: + delete_device_patch_file(file); + delete_host_file(metadataTmpPath); + delete_host_file(patchTmpPath); + return result; + } else { + struct stat sb; + if (stat(file, &sb) == -1) { + fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno)); + return 1; + } + + int localFd = adb_open(file, O_RDONLY); + if (localFd < 0) { + fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno)); + return 1; + } + + std::string error; + std::string cmd = "exec:cmd package"; + + // don't copy the APK name, but, copy the rest of the arguments as-is + while (argc-- > 1) { + cmd += " " + escape_arg(std::string(*argv++)); + } + + // add size parameter [required for streaming installs] + // do last to override any user specified value + cmd += " " + android::base::StringPrintf("-S %" PRIu64, static_cast<uint64_t>(sb.st_size)); + + int remoteFd = adb_connect(cmd, &error); + if (remoteFd < 0) { + fprintf(stderr, "adb: connect error for write: %s\n", error.c_str()); + adb_close(localFd); + return 1; + } + + char buf[BUFSIZ]; + copy_to_file(localFd, remoteFd); + read_status_line(remoteFd, buf, sizeof(buf)); + + adb_close(localFd); + adb_close(remoteFd); + + if (!strncmp("Success", buf, 7)) { + fputs(buf, stdout); + return 0; + } + fprintf(stderr, "adb: failed to install %s: %s", file, buf); + return 1; + } +} + +static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy, bool use_localagent, + const char* adb_path) { + static const char* const DATA_DEST = "/data/local/tmp/%s"; + static const char* const SD_DEST = "/sdcard/tmp/%s"; + const char* where = DATA_DEST; + + printf("Performing Push Install\n"); + + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-s")) { + where = SD_DEST; + } + } + + // Find last APK argument. + // All other arguments passed through verbatim. + int last_apk = -1; + for (int i = argc - 1; i >= 0; i--) { + if (android::base::EndsWithIgnoreCase(argv[i], ".apk")) { + last_apk = i; + break; + } + } + + if (last_apk == -1) return syntax_error("need APK file on command line"); + + int result = -1; + std::vector<const char*> apk_file = {argv[last_apk]}; + std::string apk_dest = + android::base::StringPrintf(where, android::base::Basename(argv[last_apk]).c_str()); + + std::string metadataTmpPath = get_temp_host_filename(); + std::string patchTmpPath = get_temp_host_filename(); + + if (use_fastdeploy == true) { + FILE* metadataFile = fopen(metadataTmpPath.c_str(), "wb"); + int metadata_len = extract_metadata(apk_file[0], metadataFile); + fclose(metadataFile); + + if (metadata_len <= 0) { + printf("failed to extract metadata %d\n", metadata_len); + return 1; + } else { + int create_patch_result = create_patch(apk_file[0], metadataTmpPath.c_str(), + patchTmpPath.c_str(), use_localagent, adb_path); + if (create_patch_result != 0) { + printf("Patch creation failure, error code: %d\n", create_patch_result); + result = create_patch_result; + goto cleanup_apk; + } else { + int apply_patch_result = + apply_patch_on_device(apk_file[0], patchTmpPath.c_str(), apk_dest.c_str()); + if (apply_patch_result != 0) { + printf("Patch application failure, error code: %d\n", apply_patch_result); + result = apply_patch_result; + goto cleanup_apk; + } + } + } + } else { + if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk; + } + + argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */ + result = pm_command(argc, argv); + +cleanup_apk: + delete_device_patch_file(apk_file[0]); + delete_device_file(apk_dest); + delete_host_file(metadataTmpPath); + delete_host_file(patchTmpPath); + return result; +} + +int install_app(int argc, const char** argv) { + std::vector<int> processedArgIndicies; + enum installMode { + INSTALL_DEFAULT, + INSTALL_PUSH, + INSTALL_STREAM + } installMode = INSTALL_DEFAULT; + bool use_fastdeploy = false; + bool is_reinstall = false; + bool use_localagent = false; + FastDeploy_AgentUpdateStrategy agent_update_strategy = FastDeploy_AgentUpdateDifferentVersion; + + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--streaming")) { + processedArgIndicies.push_back(i); + installMode = INSTALL_STREAM; + } else if (!strcmp(argv[i], "--no-streaming")) { + processedArgIndicies.push_back(i); + installMode = INSTALL_PUSH; + } else if (!strcmp(argv[i], "-r")) { + // Note that this argument is not added to processedArgIndicies because it + // must be passed through to pm + is_reinstall = true; + } else if (!strcmp(argv[i], "--fastdeploy")) { + processedArgIndicies.push_back(i); + use_fastdeploy = true; + } else if (!strcmp(argv[i], "--no-fastdeploy")) { + processedArgIndicies.push_back(i); + use_fastdeploy = false; + } else if (!strcmp(argv[i], "-f")) { + processedArgIndicies.push_back(i); + use_fastdeploy = true; + } else if (!strcmp(argv[i], "--force-agent")) { + processedArgIndicies.push_back(i); + agent_update_strategy = FastDeploy_AgentUpdateAlways; + } else if (!strcmp(argv[i], "--date-check-agent")) { + processedArgIndicies.push_back(i); + agent_update_strategy = FastDeploy_AgentUpdateNewerTimeStamp; + } else if (!strcmp(argv[i], "--version-check-agent")) { + processedArgIndicies.push_back(i); + agent_update_strategy = FastDeploy_AgentUpdateDifferentVersion; +#ifndef _WIN32 + } else if (!strcmp(argv[i], "--local-agent")) { + processedArgIndicies.push_back(i); + use_localagent = true; +#endif + } + // TODO: --installlog <filename> + } + + if (installMode == INSTALL_DEFAULT) { + if (_use_legacy_install()) { + installMode = INSTALL_PUSH; + } else { + installMode = INSTALL_STREAM; + } + } + + if (installMode == INSTALL_STREAM && _use_legacy_install() == true) { + return syntax_error("Attempting to use streaming install on unsupported deivce."); + } + + if (use_fastdeploy == true && is_reinstall == false) { + printf("Fast Deploy is only available with -r.\n"); + use_fastdeploy = false; + } + + if (use_fastdeploy == true && get_device_api_level() < kFastDeployMinApi) { + printf("Fast Deploy is only compatible with devices of API version %d or higher, " + "ignoring.\n", + kFastDeployMinApi); + use_fastdeploy = false; + } + + std::vector<const char*> passthrough_argv; + for (int i = 0; i < argc; i++) { + if (std::find(processedArgIndicies.begin(), processedArgIndicies.end(), i) == + processedArgIndicies.end()) { + passthrough_argv.push_back(argv[i]); + } + } + + std::string adb_path = android::base::GetExecutablePath(); + + if (adb_path.length() == 0) { + return 1; + } + if (use_fastdeploy == true) { + bool agent_up_to_date = + update_agent(agent_update_strategy, use_localagent, adb_path.c_str()); + if (agent_up_to_date == false) { + printf("Failed to update agent, exiting\n"); + return 1; + } + } + + switch (installMode) { + case INSTALL_PUSH: + return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(), + use_fastdeploy, use_localagent, adb_path.c_str()); + case INSTALL_STREAM: + return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(), + use_fastdeploy, use_localagent, adb_path.c_str()); + case INSTALL_DEFAULT: + default: + return 1; + } +} + +int install_multiple_app(int argc, const char** argv) { + // Find all APK arguments starting at end. + // All other arguments passed through verbatim. + int first_apk = -1; + uint64_t total_size = 0; + for (int i = argc - 1; i >= 0; i--) { + const char* file = argv[i]; + + if (android::base::EndsWithIgnoreCase(file, ".apk")) { + struct stat sb; + if (stat(file, &sb) != -1) total_size += sb.st_size; + first_apk = i; + } else { + break; + } + } + + if (first_apk == -1) return syntax_error("need APK file on command line"); + + std::string install_cmd; + if (_use_legacy_install()) { + install_cmd = "exec:pm"; + } else { + install_cmd = "exec:cmd package"; + } + + std::string cmd = android::base::StringPrintf("%s install-create -S %" PRIu64, + install_cmd.c_str(), total_size); + for (int i = 1; i < first_apk; i++) { + cmd += " " + escape_arg(argv[i]); + } + + // Create install session + std::string error; + int fd = adb_connect(cmd, &error); + if (fd < 0) { + fprintf(stderr, "adb: connect error for create: %s\n", error.c_str()); + return EXIT_FAILURE; + } + 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 session\n"); + fputs(buf, stderr); + return EXIT_FAILURE; + } + + // Valid session, now stream the APKs + int success = 1; + for (int i = first_apk; i < argc; i++) { + 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)); + success = 0; + goto finalize_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)); + success = 0; + goto finalize_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); + success = 0; + goto finalize_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); + success = 0; + goto finalize_session; + } + } + +finalize_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 ? "commit" : "abandon", 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); + return 0; + } + fprintf(stderr, "adb: failed to finalize session\n"); + fputs(buf, stderr); + return EXIT_FAILURE; +} + +int delete_device_file(const std::string& filename) { + std::string cmd = "rm -f " + escape_arg(filename); + return send_shell_command(cmd); +} + +int delete_host_file(const std::string& filename) { +#ifdef _WIN32 + BOOL delete_return = DeleteFileA(filename.c_str()); + if (delete_return != 0) { + return 0; + } else { + DWORD last_error = GetLastError(); + printf("Error [%ld] deleting: %s\n", last_error, filename.c_str()); + return delete_return; + } +#else + std::string cmd = "rm -f " + escape_arg(filename); + return system(cmd.c_str()); +#endif +} |