diff options
Diffstat (limited to 'adb')
-rw-r--r-- | adb/Android.bp | 15 | ||||
-rw-r--r-- | adb/client/adb_client.h | 5 | ||||
-rw-r--r-- | adb/client/adb_install.cpp | 578 | ||||
-rw-r--r-- | adb/client/adb_install.h | 29 | ||||
-rw-r--r-- | adb/client/commandline.cpp | 319 | ||||
-rw-r--r-- | adb/client/commandline.h | 2 | ||||
-rw-r--r-- | adb/client/fastdeploy.cpp | 365 | ||||
-rw-r--r-- | adb/client/fastdeploy.h | 37 | ||||
-rw-r--r-- | adb/client/fastdeploycallbacks.cpp | 118 | ||||
-rw-r--r-- | adb/client/fastdeploycallbacks.h | 40 | ||||
-rw-r--r-- | adb/fastdeploy/Android.bp | 43 | ||||
-rwxr-xr-x | adb/fastdeploy/deployagent/deployagent.sh | 7 | ||||
-rw-r--r-- | adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java | 295 | ||||
-rw-r--r-- | adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java | 35 | ||||
-rw-r--r-- | adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java | 186 | ||||
-rw-r--r-- | adb/fastdeploy/deploypatchgenerator/manifest.txt | 1 | ||||
-rw-r--r-- | adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java | 207 | ||||
-rw-r--r-- | adb/fastdeploy/proto/ApkEntry.proto | 18 |
18 files changed, 1991 insertions, 309 deletions
diff --git a/adb/Android.bp b/adb/Android.bp index 41e752fa6..3685687ab 100644 --- a/adb/Android.bp +++ b/adb/Android.bp @@ -138,6 +138,8 @@ cc_library_host_static { "client/usb_libusb.cpp", "client/usb_dispatch.cpp", "client/transport_mdns.cpp", + "client/fastdeploy.cpp", + "client/fastdeploycallbacks.cpp", ], target: { @@ -170,6 +172,12 @@ cc_library_host_static { "libdiagnose_usb", "libmdnssd", "libusb", + "libandroidfw", + "libziparchive", + "libz", + "libutils", + "liblog", + "libcutils", ], } @@ -237,6 +245,7 @@ cc_binary_host { "client/file_sync_client.cpp", "client/main.cpp", "client/console.cpp", + "client/adb_install.cpp", "client/line_printer.cpp", "shell_service_protocol.cpp", ], @@ -251,6 +260,12 @@ cc_binary_host { "liblog", "libmdnssd", "libusb", + "libandroidfw", + "libziparchive", + "libz", + "libutils", + "liblog", + "libcutils", ], stl: "libc++_static", diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h index fca435e38..d4675396f 100644 --- a/adb/client/adb_client.h +++ b/adb/client/adb_client.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef _ADB_CLIENT_H_ -#define _ADB_CLIENT_H_ +#pragma once #include "adb.h" #include "sysdeps.h" @@ -63,5 +62,3 @@ std::string format_host_command(const char* _Nonnull command); // Get the feature set of the current preferred transport. bool adb_get_feature_set(FeatureSet* _Nonnull feature_set, std::string* _Nonnull error); - -#endif 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 +} diff --git a/adb/client/adb_install.h b/adb/client/adb_install.h new file mode 100644 index 000000000..e9410a9ec --- /dev/null +++ b/adb/client/adb_install.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef ADB_INSTALL_H +#define ADB_INSTALL_H + +#include "fastdeploy.h" + +int install_app(int argc, const char** argv); +int install_multiple_app(int argc, const char** argv); +int uninstall_app(int argc, const char** argv); + +int delete_device_file(const std::string& filename); +int delete_host_file(const std::string& filename); + +#endif diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp index 41ac663b6..b55ae95e2 100644 --- a/adb/client/commandline.cpp +++ b/adb/client/commandline.cpp @@ -52,23 +52,19 @@ #include "adb.h" #include "adb_auth.h" #include "adb_client.h" +#include "adb_install.h" #include "adb_io.h" #include "adb_unique_fd.h" #include "adb_utils.h" #include "bugreport.h" #include "client/file_sync_client.h" #include "commandline.h" +#include "fastdeploy.h" #include "services.h" #include "shell_protocol.h" #include "sysdeps/chrono.h" #include "sysdeps/memory.h" -static int install_app(int argc, const char** argv); -static int install_multiple_app(int argc, const char** argv); -static int uninstall_app(int argc, const char** argv); -static int install_app_legacy(int argc, const char** argv); -static int uninstall_app_legacy(int argc, const char** argv); - extern int gListenAll; DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr); @@ -160,6 +156,17 @@ static void help() { " -p: partial application install (install-multiple only)\n" " -g: grant all runtime permissions\n" " --instant: cause the app to be installed as an ephemeral install app\n" + " --no-streaming: always push APK to device and invoke Package Manager as separate steps\n" + " --streaming: force streaming APK directly into Package Manager\n" + " -f/--fastdeploy: use fast deploy (only valid with -r)\n" + " --no-fastdeploy: prevent use of fast deploy (only valid with -r)\n" + " --force-agent: force update of deployment agent when using fast deploy\n" + " --date-check-agent: update deployment agent when local version is newer and using fast deploy\n" + " --version-check-agent: update deployment agent when local version has different version code and using fast deploy\n" +#ifndef _WIN32 + " --local-agent: locate agent files from local source build (instead of SDK location)\n" +#endif + //TODO--installlog <filename> " uninstall [-k] PACKAGE\n" " remove this app package from the device\n" " '-k': keep the data and cache directories\n" @@ -306,21 +313,6 @@ int read_and_dump(int fd, bool use_shell_protocol = false, return callback->Done(exit_code); } -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 void stdinout_raw_prologue(int inFd, int outFd, int& old_stdin_mode, int& old_stdout_mode) { if (inFd == STDIN_FILENO) { stdin_raw_init(); @@ -361,7 +353,7 @@ static void stdinout_raw_epilogue(int inFd, int outFd, int old_stdin_mode, int o #endif } -static void copy_to_file(int inFd, int outFd) { +void copy_to_file(int inFd, int outFd) { constexpr size_t BUFSIZE = 32 * 1024; std::vector<char> buf(BUFSIZE); int len; @@ -1315,16 +1307,6 @@ static bool _is_valid_ack_reply_fd(const int ack_reply_fd) { #endif } -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); -} - int adb_commandline(int argc, const char** argv) { bool no_daemon = false; bool is_daemon = false; @@ -1706,9 +1688,6 @@ int adb_commandline(int argc, const char** argv) { } else if (!strcmp(argv[0], "install")) { if (argc < 2) return syntax_error("install requires an argument"); - if (_use_legacy_install()) { - return install_app_legacy(argc, argv); - } return install_app(argc, argv); } else if (!strcmp(argv[0], "install-multiple")) { @@ -1717,9 +1696,6 @@ int adb_commandline(int argc, const char** argv) { } else if (!strcmp(argv[0], "uninstall")) { if (argc < 2) return syntax_error("uninstall requires an argument"); - if (_use_legacy_install()) { - return uninstall_app_legacy(argc, argv); - } return uninstall_app(argc, argv); } else if (!strcmp(argv[0], "sync")) { @@ -1844,270 +1820,3 @@ int adb_commandline(int argc, const char** argv) { syntax_error("unknown command %s", argv[0]); return 1; } - -static int uninstall_app(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 install_app(int argc, const char** argv) { - // 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); - } - - 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_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") || - android::base::EndsWithIgnoreCase(file, ".dm")) { - 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 %s -", install_cmd.c_str(), - static_cast<uint64_t>(sb.st_size), session_id, 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; -} - -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_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) */ - int i; - for (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); -} - -static int delete_file(const std::string& filename) { - std::string cmd = "rm -f " + escape_arg(filename); - return send_shell_command(cmd); -} - -static int install_app_legacy(int argc, const char** argv) { - static const char *const DATA_DEST = "/data/local/tmp/%s"; - static const char *const SD_DEST = "/sdcard/tmp/%s"; - const char* where = DATA_DEST; - - 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()); - 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_file(apk_dest); - return result; -} diff --git a/adb/client/commandline.h b/adb/client/commandline.h index 3aa03a723..6cfd4f791 100644 --- a/adb/client/commandline.h +++ b/adb/client/commandline.h @@ -96,6 +96,8 @@ extern DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK; int adb_commandline(int argc, const char** argv); +void copy_to_file(int inFd, int outFd); + // Connects to the device "shell" service with |command| and prints the // resulting output. // if |callback| is non-null, stdout/stderr output will be handled by it. diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp new file mode 100644 index 000000000..cd42b5600 --- /dev/null +++ b/adb/client/fastdeploy.cpp @@ -0,0 +1,365 @@ +/* + * 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. + */ + +#include <androidfw/ResourceTypes.h> +#include <androidfw/ZipFileRO.h> +#include <libgen.h> +#include <algorithm> + +#include "client/file_sync_client.h" +#include "commandline.h" +#include "fastdeploy.h" +#include "fastdeploycallbacks.h" +#include "utils/String16.h" + +const long kRequiredAgentVersion = 0x00000001; + +const char* kDeviceAgentPath = "/data/local/tmp/"; + +long get_agent_version() { + std::vector<char> versionOutputBuffer; + std::vector<char> versionErrorBuffer; + + int statusCode = capture_shell_command("/data/local/tmp/deployagent.sh version", + &versionOutputBuffer, &versionErrorBuffer); + long version = -1; + + if (statusCode == 0 && versionOutputBuffer.size() > 0) { + version = strtol((char*)versionOutputBuffer.data(), NULL, 16); + } + + return version; +} + +int get_device_api_level() { + std::vector<char> sdkVersionOutputBuffer; + std::vector<char> sdkVersionErrorBuffer; + int api_level = -1; + + int statusCode = capture_shell_command("getprop ro.build.version.sdk", &sdkVersionOutputBuffer, + &sdkVersionErrorBuffer); + if (statusCode == 0 && statusCode == 0 && sdkVersionOutputBuffer.size() > 0) { + api_level = strtol((char*)sdkVersionOutputBuffer.data(), NULL, 10); + } + + return api_level; +} + +// local_path - must start with a '/' and be relative to $ANDROID_PRODUCT_OUT +static bool get_agent_component_host_path(bool use_localagent, const char* adb_path, + const char* local_path, const char* sdk_path, + std::string* output_path) { + std::string mutable_adb_path = adb_path; + const char* adb_dir = dirname(&mutable_adb_path[0]); + if (adb_dir == nullptr) { + return false; + } + + if (use_localagent) { + const char* product_out = getenv("ANDROID_PRODUCT_OUT"); + if (product_out == nullptr) { + return false; + } + *output_path = android::base::StringPrintf("%s%s", product_out, local_path); + return true; + } else { + *output_path = android::base::StringPrintf("%s%s", adb_dir, sdk_path); + return true; + } + return false; +} + +static bool deploy_agent(bool checkTimeStamps, bool use_localagent, const char* adb_path) { + std::vector<const char*> srcs; + + std::string agent_jar_path; + if (get_agent_component_host_path(use_localagent, adb_path, "/system/framework/deployagent.jar", + "/deployagent.jar", &agent_jar_path)) { + srcs.push_back(agent_jar_path.c_str()); + } else { + return false; + } + + std::string agent_sh_path; + if (get_agent_component_host_path(use_localagent, adb_path, "/system/bin/deployagent.sh", + "/deployagent.sh", &agent_sh_path)) { + srcs.push_back(agent_sh_path.c_str()); + } else { + return false; + } + + if (do_sync_push(srcs, kDeviceAgentPath, checkTimeStamps)) { + // on windows the shell script might have lost execute permission + // so need to set this explicitly + const char* kChmodCommandPattern = "chmod 777 %sdeployagent.sh"; + std::string chmodCommand = + android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentPath); + int ret = send_shell_command(chmodCommand.c_str()); + return (ret == 0); + } else { + return false; + } +} + +bool update_agent(FastDeploy_AgentUpdateStrategy agentUpdateStrategy, bool use_localagent, + const char* adb_path) { + long agent_version = get_agent_version(); + switch (agentUpdateStrategy) { + case FastDeploy_AgentUpdateAlways: + if (deploy_agent(false, use_localagent, adb_path) == false) { + return false; + } + break; + case FastDeploy_AgentUpdateNewerTimeStamp: + if (deploy_agent(true, use_localagent, adb_path) == false) { + return false; + } + break; + case FastDeploy_AgentUpdateDifferentVersion: + if (agent_version != kRequiredAgentVersion) { + if (agent_version < 0) { + printf("Could not detect agent on device, deploying\n"); + } else { + printf("Device agent version is (%ld), (%ld) is required, re-deploying\n", + agent_version, kRequiredAgentVersion); + } + if (deploy_agent(false, use_localagent, adb_path) == false) { + return false; + } + } + break; + } + + agent_version = get_agent_version(); + return (agent_version == kRequiredAgentVersion); +} + +static std::string get_string_from_utf16(const char16_t* input, int input_len) { + ssize_t utf8_length = utf16_to_utf8_length(input, input_len); + if (utf8_length <= 0) { + return {}; + } + + std::string utf8; + utf8.resize(utf8_length); + utf16_to_utf8(input, input_len, &*utf8.begin(), utf8_length + 1); + return utf8; +} + +// output is required to point to a valid output string (non-null) +static bool get_packagename_from_apk(const char* apkPath, std::string* output) { + using namespace android; + + ZipFileRO* zipFile = ZipFileRO::open(apkPath); + if (zipFile == nullptr) { + return false; + } + + ZipEntryRO entry = zipFile->findEntryByName("AndroidManifest.xml"); + if (entry == nullptr) { + return false; + } + + uint32_t manifest_len = 0; + if (!zipFile->getEntryInfo(entry, NULL, &manifest_len, NULL, NULL, NULL, NULL)) { + return false; + } + + std::vector<char> manifest_data(manifest_len); + if (!zipFile->uncompressEntry(entry, manifest_data.data(), manifest_len)) { + return false; + } + + ResXMLTree tree; + status_t setto_status = tree.setTo(manifest_data.data(), manifest_len, true); + if (setto_status != NO_ERROR) { + return false; + } + + ResXMLParser::event_code_t code; + while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && + code != ResXMLParser::END_DOCUMENT) { + switch (code) { + case ResXMLParser::START_TAG: { + size_t element_name_length; + const char16_t* element_name = tree.getElementName(&element_name_length); + if (element_name == nullptr) { + continue; + } + + std::u16string element_name_string(element_name, element_name_length); + if (element_name_string == u"manifest") { + for (int i = 0; i < (int)tree.getAttributeCount(); i++) { + size_t attribute_name_length; + const char16_t* attribute_name_text = + tree.getAttributeName(i, &attribute_name_length); + if (attribute_name_text == nullptr) { + continue; + } + std::u16string attribute_name_string(attribute_name_text, + attribute_name_length); + + if (attribute_name_string == u"package") { + size_t attribute_value_length; + const char16_t* attribute_value_text = + tree.getAttributeStringValue(i, &attribute_value_length); + if (attribute_value_text == nullptr) { + continue; + } + *output = get_string_from_utf16(attribute_value_text, + attribute_value_length); + return true; + } + } + } + break; + } + default: + break; + } + } + + return false; +} + +int extract_metadata(const char* apkPath, FILE* outputFp) { + std::string packageName; + if (get_packagename_from_apk(apkPath, &packageName) == false) { + return -1; + } + + const char* kAgentExtractCommandPattern = "/data/local/tmp/deployagent.sh extract %s"; + std::string extractCommand = + android::base::StringPrintf(kAgentExtractCommandPattern, packageName.c_str()); + + std::vector<char> extractErrorBuffer; + int statusCode; + DeployAgentFileCallback cb(outputFp, &extractErrorBuffer, &statusCode); + int ret = send_shell_command(extractCommand.c_str(), false, &cb); + + if (ret == 0) { + return cb.getBytesWritten(); + } + + return ret; +} + +// output is required to point to a valid output string (non-null) +static bool patch_generator_command(bool use_localagent, const char* adb_path, + std::string* output) { + if (use_localagent) { + // This should never happen on a Windows machine + const char* kGeneratorCommandPattern = "java -jar %s/framework/deploypatchgenerator.jar"; + const char* host_out = getenv("ANDROID_HOST_OUT"); + if (host_out == nullptr) { + return false; + } + *output = android::base::StringPrintf(kGeneratorCommandPattern, host_out, host_out); + return true; + } else { + const char* kGeneratorCommandPattern = R"(java -jar "%s/deploypatchgenerator.jar")"; + std::string mutable_adb_path = adb_path; + const char* adb_dir = dirname(&mutable_adb_path[0]); + if (adb_dir == nullptr) { + return false; + } + + *output = android::base::StringPrintf(kGeneratorCommandPattern, adb_dir, adb_dir); + return true; + } + return false; +} + +int create_patch(const char* apkPath, const char* metadataPath, const char* patchPath, + bool use_localagent, const char* adb_path) { + const char* kGeneratePatchCommandPattern = R"(%s "%s" "%s" > "%s")"; + std::string patch_generator_command_string; + if (patch_generator_command(use_localagent, adb_path, &patch_generator_command_string) == + false) { + return 1; + } + std::string generatePatchCommand = android::base::StringPrintf( + kGeneratePatchCommandPattern, patch_generator_command_string.c_str(), apkPath, + metadataPath, patchPath); + return system(generatePatchCommand.c_str()); +} + +std::string get_patch_path(const char* apkPath) { + std::string packageName; + if (get_packagename_from_apk(apkPath, &packageName) == false) { + return ""; + } + std::string patchDevicePath = + android::base::StringPrintf("%s%s.patch", kDeviceAgentPath, packageName.c_str()); + return patchDevicePath; +} + +int apply_patch_on_device(const char* apkPath, const char* patchPath, const char* outputPath) { + const std::string kAgentApplyCommandPattern = + "/data/local/tmp/deployagent.sh apply %s %s -o %s"; + + std::string packageName; + if (get_packagename_from_apk(apkPath, &packageName) == false) { + return -1; + } + std::string patchDevicePath = get_patch_path(apkPath); + + std::vector<const char*> srcs = {patchPath}; + bool push_ok = do_sync_push(srcs, patchDevicePath.c_str(), false); + + if (!push_ok) { + return -1; + } + + std::string applyPatchCommand = + android::base::StringPrintf(kAgentApplyCommandPattern.c_str(), packageName.c_str(), + patchDevicePath.c_str(), outputPath); + return send_shell_command(applyPatchCommand); +} + +int install_patch(const char* apkPath, const char* patchPath, int argc, const char** argv) { + const std::string kAgentApplyCommandPattern = + "/data/local/tmp/deployagent.sh apply %s %s -pm %s"; + + std::string packageName; + if (get_packagename_from_apk(apkPath, &packageName) == false) { + return -1; + } + + std::vector<const char*> srcs; + std::string patchDevicePath = + android::base::StringPrintf("%s%s.patch", kDeviceAgentPath, packageName.c_str()); + srcs.push_back(patchPath); + bool push_ok = do_sync_push(srcs, patchDevicePath.c_str(), false); + + if (!push_ok) { + return -1; + } + + std::vector<unsigned char> applyOutputBuffer; + std::vector<unsigned char> applyErrorBuffer; + std::string argsString; + + for (int i = 0; i < argc; i++) { + argsString.append(argv[i]); + argsString.append(" "); + } + + std::string applyPatchCommand = + android::base::StringPrintf(kAgentApplyCommandPattern.c_str(), packageName.c_str(), + patchDevicePath.c_str(), argsString.c_str()); + return send_shell_command(applyPatchCommand); +} diff --git a/adb/client/fastdeploy.h b/adb/client/fastdeploy.h new file mode 100644 index 000000000..d8acd303a --- /dev/null +++ b/adb/client/fastdeploy.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include "adb.h" + +typedef enum EFastDeploy_AgentUpdateStrategy { + FastDeploy_AgentUpdateAlways, + FastDeploy_AgentUpdateNewerTimeStamp, + FastDeploy_AgentUpdateDifferentVersion +} FastDeploy_AgentUpdateStrategy; + +static constexpr int kFastDeployMinApi = 24; + +int get_device_api_level(); +bool update_agent(FastDeploy_AgentUpdateStrategy agentUpdateStrategy, bool use_localagent, + const char* adb_path); +int extract_metadata(const char* apkPath, FILE* outputFp); +int create_patch(const char* apkPath, const char* metadataPath, const char* patchPath, + bool use_localagent, const char* adb_path); +int apply_patch_on_device(const char* apkPath, const char* patchPath, const char* outputPath); +int install_patch(const char* apkPath, const char* patchPath, int argc, const char** argv); +std::string get_patch_path(const char* apkPath); diff --git a/adb/client/fastdeploycallbacks.cpp b/adb/client/fastdeploycallbacks.cpp new file mode 100644 index 000000000..6c9a21fde --- /dev/null +++ b/adb/client/fastdeploycallbacks.cpp @@ -0,0 +1,118 @@ +/* + * 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 <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "client/file_sync_client.h" +#include "commandline.h" +#include "sysdeps.h" + +#include "fastdeploycallbacks.h" + +static void appendBuffer(std::vector<char>* buffer, const char* input, int length) { + if (buffer != NULL) { + buffer->insert(buffer->end(), input, input + length); + } +} + +class DeployAgentBufferCallback : public StandardStreamsCallbackInterface { + public: + DeployAgentBufferCallback(std::vector<char>* outBuffer, std::vector<char>* errBuffer, + int* statusCode); + + virtual void OnStdout(const char* buffer, int length); + virtual void OnStderr(const char* buffer, int length); + virtual int Done(int status); + + private: + std::vector<char>* mpOutBuffer; + std::vector<char>* mpErrBuffer; + int* mpStatusCode; +}; + +int capture_shell_command(const char* command, std::vector<char>* outBuffer, + std::vector<char>* errBuffer) { + int statusCode; + DeployAgentBufferCallback cb(outBuffer, errBuffer, &statusCode); + int ret = send_shell_command(command, false, &cb); + + if (ret == 0) { + return statusCode; + } else { + return ret; + } +} + +DeployAgentFileCallback::DeployAgentFileCallback(FILE* outputFile, std::vector<char>* errBuffer, + int* statusCode) { + mpOutFile = outputFile; + mpErrBuffer = errBuffer; + mpStatusCode = statusCode; + mBytesWritten = 0; +} + +void DeployAgentFileCallback::OnStdout(const char* buffer, int length) { + if (mpOutFile != NULL) { + int bytes_written = fwrite(buffer, 1, length, mpOutFile); + if (bytes_written != length) { + printf("Write error %d\n", bytes_written); + } + mBytesWritten += bytes_written; + } +} + +void DeployAgentFileCallback::OnStderr(const char* buffer, int length) { + appendBuffer(mpErrBuffer, buffer, length); +} + +int DeployAgentFileCallback::Done(int status) { + if (mpStatusCode != NULL) { + *mpStatusCode = status; + } + return 0; +} + +int DeployAgentFileCallback::getBytesWritten() { + return mBytesWritten; +} + +DeployAgentBufferCallback::DeployAgentBufferCallback(std::vector<char>* outBuffer, + std::vector<char>* errBuffer, + int* statusCode) { + mpOutBuffer = outBuffer; + mpErrBuffer = errBuffer; + mpStatusCode = statusCode; +} + +void DeployAgentBufferCallback::OnStdout(const char* buffer, int length) { + appendBuffer(mpOutBuffer, buffer, length); +} + +void DeployAgentBufferCallback::OnStderr(const char* buffer, int length) { + appendBuffer(mpErrBuffer, buffer, length); +} + +int DeployAgentBufferCallback::Done(int status) { + if (mpStatusCode != NULL) { + *mpStatusCode = status; + } + return 0; +} diff --git a/adb/client/fastdeploycallbacks.h b/adb/client/fastdeploycallbacks.h new file mode 100644 index 000000000..b428b50bb --- /dev/null +++ b/adb/client/fastdeploycallbacks.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#pragma once + +#include <vector> +#include "commandline.h" + +class DeployAgentFileCallback : public StandardStreamsCallbackInterface { + public: + DeployAgentFileCallback(FILE* outputFile, std::vector<char>* errBuffer, int* statusCode); + + virtual void OnStdout(const char* buffer, int length); + virtual void OnStderr(const char* buffer, int length); + virtual int Done(int status); + + int getBytesWritten(); + + private: + FILE* mpOutFile; + std::vector<char>* mpErrBuffer; + int mBytesWritten; + int* mpStatusCode; +}; + +int capture_shell_command(const char* command, std::vector<char>* outBuffer, + std::vector<char>* errBuffer); diff --git a/adb/fastdeploy/Android.bp b/adb/fastdeploy/Android.bp new file mode 100644 index 000000000..30f47308b --- /dev/null +++ b/adb/fastdeploy/Android.bp @@ -0,0 +1,43 @@ +// +// 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. +// + +java_library { + name: "deployagent", + sdk_version: "24", + srcs: ["deployagent/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"], + static_libs: ["apkzlib_zip"], + proto: { + type: "lite", + } +} + +cc_prebuilt_binary { + name: "deployagent.sh", + + srcs: ["deployagent/deployagent.sh"], + required: ["deployagent"], + device_supported: true, +} + +java_binary_host { + name: "deploypatchgenerator", + srcs: ["deploypatchgenerator/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"], + static_libs: ["apkzlib"], + manifest: "deploypatchgenerator/manifest.txt", + proto: { + type: "full", + } +} diff --git a/adb/fastdeploy/deployagent/deployagent.sh b/adb/fastdeploy/deployagent/deployagent.sh new file mode 100755 index 000000000..4f17eb701 --- /dev/null +++ b/adb/fastdeploy/deployagent/deployagent.sh @@ -0,0 +1,7 @@ +# Script to start "deployagent" on the device, which has a very rudimentary +# shell. +# +base=/data/local/tmp +export CLASSPATH=$base/deployagent.jar +exec app_process $base com.android.fastdeploy.DeployAgent "$@" + diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java new file mode 100644 index 000000000..cd6f16802 --- /dev/null +++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java @@ -0,0 +1,295 @@ +/* + * 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. + */ + +package com.android.fastdeploy; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Set; + +import com.android.fastdeploy.APKMetaData; +import com.android.fastdeploy.PatchUtils; + +public final class DeployAgent { + private static final int BUFFER_SIZE = 128 * 1024; + private static final int AGENT_VERSION = 0x00000001; + + public static void main(String[] args) { + int exitCode = 0; + try { + if (args.length < 1) { + showUsage(0); + } + + String commandString = args[0]; + + if (commandString.equals("extract")) { + if (args.length != 2) { + showUsage(1); + } + + String packageName = args[1]; + extractMetaData(packageName); + } else if (commandString.equals("apply")) { + if (args.length < 4) { + showUsage(1); + } + + String packageName = args[1]; + String patchPath = args[2]; + String outputParam = args[3]; + + InputStream deltaInputStream = null; + if (patchPath.compareTo("-") == 0) { + deltaInputStream = System.in; + } else { + deltaInputStream = new FileInputStream(patchPath); + } + + if (outputParam.equals("-o")) { + OutputStream outputStream = null; + if (args.length > 4) { + String outputPath = args[4]; + if (!outputPath.equals("-")) { + outputStream = new FileOutputStream(outputPath); + } + } + if (outputStream == null) { + outputStream = System.out; + } + File deviceFile = getFileFromPackageName(packageName); + writePatchToStream( + new RandomAccessFile(deviceFile, "r"), deltaInputStream, outputStream); + } else if (outputParam.equals("-pm")) { + String[] sessionArgs = null; + if (args.length > 4) { + int numSessionArgs = args.length-4; + sessionArgs = new String[numSessionArgs]; + for (int i=0 ; i<numSessionArgs ; i++) { + sessionArgs[i] = args[i+4]; + } + } + exitCode = applyPatch(packageName, deltaInputStream, sessionArgs); + } + } else if (commandString.equals("version")) { + System.out.printf("0x%08X\n", AGENT_VERSION); + } else { + showUsage(1); + } + } catch (Exception e) { + System.err.println("Error: " + e); + e.printStackTrace(); + System.exit(2); + } + System.exit(exitCode); + } + + private static void showUsage(int exitCode) { + System.err.println( + "usage: deployagent <command> [<args>]\n\n" + + "commands:\n" + + "version get the version\n" + + "extract PKGNAME extract an installed package's metadata\n" + + "apply PKGNAME PATCHFILE [-o|-pm] apply a patch from PATCHFILE (- for stdin) to an installed package\n" + + " -o <FILE> directs output to FILE, default or - for stdout\n" + + " -pm <ARGS> directs output to package manager, passes <ARGS> to 'pm install-create'\n" + ); + + System.exit(exitCode); + } + + private static Process executeCommand(String command) throws IOException { + try { + Process p; + p = Runtime.getRuntime().exec(command); + p.waitFor(); + return p; + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return null; + } + + private static File getFileFromPackageName(String packageName) throws IOException { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("pm list packages -f " + packageName); + + Process p = executeCommand(commandBuilder.toString()); + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + + String packagePrefix = "package:"; + String line = ""; + while ((line = reader.readLine()) != null) { + int packageIndex = line.indexOf(packagePrefix); + int equalsIndex = line.indexOf("=" + packageName); + return new File(line.substring(packageIndex + packagePrefix.length(), equalsIndex)); + } + + return null; + } + + private static void extractMetaData(String packageName) throws IOException { + File apkFile = getFileFromPackageName(packageName); + APKMetaData apkMetaData = PatchUtils.getAPKMetaData(apkFile); + apkMetaData.writeDelimitedTo(System.out); + } + + private static int createInstallSession(String[] args) throws IOException { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append("pm install-create "); + for (int i=0 ; args != null && i<args.length ; i++) { + commandBuilder.append(args[i] + " "); + } + + Process p = executeCommand(commandBuilder.toString()); + + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = ""; + String successLineStart = "Success: created install session ["; + String successLineEnd = "]"; + while ((line = reader.readLine()) != null) { + if (line.startsWith(successLineStart) && line.endsWith(successLineEnd)) { + return Integer.parseInt(line.substring(successLineStart.length(), line.lastIndexOf(successLineEnd))); + } + } + + return -1; + } + + private static int commitInstallSession(int sessionId) throws IOException { + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append(String.format("pm install-commit %d -- - ", sessionId)); + Process p = executeCommand(commandBuilder.toString()); + return p.exitValue(); + } + + private static int applyPatch(String packageName, InputStream deltaStream, String[] sessionArgs) + throws IOException, PatchFormatException { + File deviceFile = getFileFromPackageName(packageName); + int sessionId = createInstallSession(sessionArgs); + if (sessionId < 0) { + System.err.println("PM Create Session Failed"); + return -1; + } + + int writeExitCode = writePatchedDataToSession(new RandomAccessFile(deviceFile, "r"), deltaStream, sessionId); + + if (writeExitCode == 0) { + return commitInstallSession(sessionId); + } else { + return -1; + } + } + + private static long writePatchToStream(RandomAccessFile oldData, InputStream patchData, + OutputStream outputStream) throws IOException, PatchFormatException { + long newSize = readPatchHeader(patchData); + long bytesWritten = writePatchedDataToStream(oldData, newSize, patchData, outputStream); + outputStream.flush(); + if (bytesWritten != newSize) { + throw new PatchFormatException(String.format( + "output size mismatch (expected %ld but wrote %ld)", newSize, bytesWritten)); + } + return bytesWritten; + } + + private static long readPatchHeader(InputStream patchData) + throws IOException, PatchFormatException { + byte[] signatureBuffer = new byte[PatchUtils.SIGNATURE.length()]; + try { + PatchUtils.readFully(patchData, signatureBuffer, 0, signatureBuffer.length); + } catch (IOException e) { + throw new PatchFormatException("truncated signature"); + } + + String signature = new String(signatureBuffer, 0, signatureBuffer.length, "US-ASCII"); + if (!PatchUtils.SIGNATURE.equals(signature)) { + throw new PatchFormatException("bad signature"); + } + + long newSize = PatchUtils.readBsdiffLong(patchData); + if (newSize < 0 || newSize > Integer.MAX_VALUE) { + throw new PatchFormatException("bad newSize"); + } + + return newSize; + } + + // Note that this function assumes patchData has been seek'ed to the start of the delta stream + // (i.e. the signature has already been read by readPatchHeader). For a stream that points to the + // start of a patch file call writePatchToStream + private static long writePatchedDataToStream(RandomAccessFile oldData, long newSize, + InputStream patchData, OutputStream outputStream) throws IOException { + long newDataBytesWritten = 0; + byte[] buffer = new byte[BUFFER_SIZE]; + + while (newDataBytesWritten < newSize) { + long copyLen = PatchUtils.readFormattedLong(patchData); + if (copyLen > 0) { + PatchUtils.pipe(patchData, outputStream, buffer, (int) copyLen); + } + + long oldDataOffset = PatchUtils.readFormattedLong(patchData); + long oldDataLen = PatchUtils.readFormattedLong(patchData); + oldData.seek(oldDataOffset); + if (oldDataLen > 0) { + PatchUtils.pipe(oldData, outputStream, buffer, (int) oldDataLen); + } + + newDataBytesWritten += copyLen + oldDataLen; + } + + return newDataBytesWritten; + } + + private static int writePatchedDataToSession(RandomAccessFile oldData, InputStream patchData, int sessionId) + throws IOException, PatchFormatException { + try { + Process p; + long newSize = readPatchHeader(patchData); + StringBuilder commandBuilder = new StringBuilder(); + commandBuilder.append(String.format("pm install-write -S %d %d -- -", newSize, sessionId)); + + String command = commandBuilder.toString(); + p = Runtime.getRuntime().exec(command); + + OutputStream sessionOutputStream = p.getOutputStream(); + long bytesWritten = writePatchedDataToStream(oldData, newSize, patchData, sessionOutputStream); + sessionOutputStream.flush(); + p.waitFor(); + if (bytesWritten != newSize) { + throw new PatchFormatException( + String.format("output size mismatch (expected %d but wrote %)", newSize, bytesWritten)); + } + return p.exitValue(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return -1; + } +} diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java new file mode 100644 index 000000000..f0655f325 --- /dev/null +++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchFormatException.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package com.android.fastdeploy; + +class PatchFormatException extends Exception { + /** + * Constructs a new exception with the specified message. + * @param message the message + */ + public PatchFormatException(String message) { super(message); } + + /** + * Constructs a new exception with the specified message and cause. + * @param message the message + * @param cause the cause of the error + */ + public PatchFormatException(String message, Throwable cause) { + super(message); + initCause(cause); + } +} diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java new file mode 100644 index 000000000..f0f00e16c --- /dev/null +++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java @@ -0,0 +1,186 @@ +/* + * 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. + */ + +package com.android.fastdeploy; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +import com.android.tools.build.apkzlib.zip.ZFile; +import com.android.tools.build.apkzlib.zip.ZFileOptions; +import com.android.tools.build.apkzlib.zip.StoredEntry; +import com.android.tools.build.apkzlib.zip.StoredEntryType; +import com.android.tools.build.apkzlib.zip.CentralDirectoryHeaderCompressInfo; +import com.android.tools.build.apkzlib.zip.CentralDirectoryHeader; + +import com.android.fastdeploy.APKMetaData; +import com.android.fastdeploy.APKEntry; + +class PatchUtils { + private static final long NEGATIVE_MASK = 1L << 63; + private static final long NEGATIVE_LONG_SIGN_MASK = 1L << 63; + public static final String SIGNATURE = "HAMADI/IHD"; + + private static long getOffsetFromEntry(StoredEntry entry) { + return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize(); + } + + public static APKMetaData getAPKMetaData(File apkFile) throws IOException { + APKMetaData.Builder apkEntriesBuilder = APKMetaData.newBuilder(); + ZFileOptions options = new ZFileOptions(); + ZFile zFile = new ZFile(apkFile, options); + + ArrayList<StoredEntry> metaDataEntries = new ArrayList<StoredEntry>(); + + for (StoredEntry entry : zFile.entries()) { + if (entry.getType() != StoredEntryType.FILE) { + continue; + } + metaDataEntries.add(entry); + } + + Collections.sort(metaDataEntries, new Comparator<StoredEntry>() { + private long getOffsetFromEntry(StoredEntry entry) { + return PatchUtils.getOffsetFromEntry(entry); + } + + @Override + public int compare(StoredEntry lhs, StoredEntry rhs) { + // -1 - less than, 1 - greater than, 0 - equal, all inversed for descending + return Long.compare(getOffsetFromEntry(lhs), getOffsetFromEntry(rhs)); + } + }); + + for (StoredEntry entry : metaDataEntries) { + CentralDirectoryHeader cdh = entry.getCentralDirectoryHeader(); + CentralDirectoryHeaderCompressInfo cdhci = cdh.getCompressionInfoWithWait(); + + APKEntry.Builder entryBuilder = APKEntry.newBuilder(); + entryBuilder.setCrc32(cdh.getCrc32()); + entryBuilder.setFileName(cdh.getName()); + entryBuilder.setCompressedSize(cdhci.getCompressedSize()); + entryBuilder.setUncompressedSize(cdh.getUncompressedSize()); + entryBuilder.setDataOffset(getOffsetFromEntry(entry)); + + apkEntriesBuilder.addEntries(entryBuilder); + apkEntriesBuilder.build(); + } + return apkEntriesBuilder.build(); + } + + /** + * Writes a 64-bit signed integer to the specified {@link OutputStream}. The least significant + * byte is written first and the most significant byte is written last. + * @param value the value to write + * @param outputStream the stream to write to + */ + static void writeFormattedLong(final long value, OutputStream outputStream) throws IOException { + long y = value; + if (y < 0) { + y = (-y) | NEGATIVE_MASK; + } + + for (int i = 0; i < 8; ++i) { + outputStream.write((byte) (y & 0xff)); + y >>>= 8; + } + } + + /** + * Reads a 64-bit signed integer written by {@link #writeFormattedLong(long, OutputStream)} from + * the specified {@link InputStream}. + * @param inputStream the stream to read from + */ + static long readFormattedLong(InputStream inputStream) throws IOException { + long result = 0; + for (int bitshift = 0; bitshift < 64; bitshift += 8) { + result |= ((long) inputStream.read()) << bitshift; + } + + if ((result - NEGATIVE_MASK) > 0) { + result = (result & ~NEGATIVE_MASK) * -1; + } + return result; + } + + static final long readBsdiffLong(InputStream in) throws PatchFormatException, IOException { + long result = 0; + for (int bitshift = 0; bitshift < 64; bitshift += 8) { + result |= ((long) in.read()) << bitshift; + } + + if (result == NEGATIVE_LONG_SIGN_MASK) { + // "Negative zero", which is valid in signed-magnitude format. + // NB: No sane patch generator should ever produce such a value. + throw new PatchFormatException("read negative zero"); + } + + if ((result & NEGATIVE_LONG_SIGN_MASK) != 0) { + result = -(result & ~NEGATIVE_LONG_SIGN_MASK); + } + + return result; + } + + static void readFully(final InputStream in, final byte[] destination, final int startAt, + final int numBytes) throws IOException { + int numRead = 0; + while (numRead < numBytes) { + int readNow = in.read(destination, startAt + numRead, numBytes - numRead); + if (readNow == -1) { + throw new IOException("truncated input stream"); + } + numRead += readNow; + } + } + + static void pipe(final InputStream in, final OutputStream out, final byte[] buffer, + long copyLength) throws IOException { + while (copyLength > 0) { + int maxCopy = Math.min(buffer.length, (int) copyLength); + readFully(in, buffer, 0, maxCopy); + out.write(buffer, 0, maxCopy); + copyLength -= maxCopy; + } + } + + static void pipe(final RandomAccessFile in, final OutputStream out, final byte[] buffer, + long copyLength) throws IOException { + while (copyLength > 0) { + int maxCopy = Math.min(buffer.length, (int) copyLength); + in.readFully(buffer, 0, maxCopy); + out.write(buffer, 0, maxCopy); + copyLength -= maxCopy; + } + } + + static void fill(byte value, final OutputStream out, final byte[] buffer, long fillLength) + throws IOException { + while (fillLength > 0) { + int maxCopy = Math.min(buffer.length, (int) fillLength); + Arrays.fill(buffer, 0, maxCopy, value); + out.write(buffer, 0, maxCopy); + fillLength -= maxCopy; + } + } +} diff --git a/adb/fastdeploy/deploypatchgenerator/manifest.txt b/adb/fastdeploy/deploypatchgenerator/manifest.txt new file mode 100644 index 000000000..5c0050597 --- /dev/null +++ b/adb/fastdeploy/deploypatchgenerator/manifest.txt @@ -0,0 +1 @@ +Main-Class: com.android.fastdeploy.DeployPatchGenerator diff --git a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java new file mode 100644 index 000000000..5577364c3 --- /dev/null +++ b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java @@ -0,0 +1,207 @@ +/* + * 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. + */ + +package com.android.fastdeploy; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.StringBuilder; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.ArrayList; + +import java.nio.charset.StandardCharsets; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.AbstractMap.SimpleEntry; + +import com.android.fastdeploy.APKMetaData; +import com.android.fastdeploy.APKEntry; + +public final class DeployPatchGenerator { + private static final int BUFFER_SIZE = 128 * 1024; + + public static void main(String[] args) { + try { + if (args.length < 2) { + showUsage(0); + } + + boolean verbose = false; + if (args.length > 2) { + String verboseFlag = args[2]; + if (verboseFlag.compareTo("--verbose") == 0) { + verbose = true; + } + } + + StringBuilder sb = null; + String apkPath = args[0]; + String deviceMetadataPath = args[1]; + File hostFile = new File(apkPath); + + List<APKEntry> deviceZipEntries = getMetadataFromFile(deviceMetadataPath); + if (verbose) { + sb = new StringBuilder(); + for (APKEntry entry : deviceZipEntries) { + APKEntryToString(entry, sb); + } + System.err.println("Device Entries (" + deviceZipEntries.size() + ")"); + System.err.println(sb.toString()); + } + + List<APKEntry> hostFileEntries = PatchUtils.getAPKMetaData(hostFile).getEntriesList(); + if (verbose) { + sb = new StringBuilder(); + for (APKEntry entry : hostFileEntries) { + APKEntryToString(entry, sb); + } + System.err.println("Host Entries (" + hostFileEntries.size() + ")"); + System.err.println(sb.toString()); + } + + List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet = + getIdenticalContents(deviceZipEntries, hostFileEntries); + reportIdenticalContents(identicalContentsEntrySet, hostFile); + + if (verbose) { + sb = new StringBuilder(); + for (SimpleEntry<APKEntry, APKEntry> identicalEntry : identicalContentsEntrySet) { + APKEntry entry = identicalEntry.getValue(); + APKEntryToString(entry, sb); + } + System.err.println("Identical Entries (" + identicalContentsEntrySet.size() + ")"); + System.err.println(sb.toString()); + } + + createPatch(identicalContentsEntrySet, hostFile, System.out); + } catch (Exception e) { + System.err.println("Error: " + e); + e.printStackTrace(); + System.exit(2); + } + System.exit(0); + } + + private static void showUsage(int exitCode) { + System.err.println("usage: deploypatchgenerator <apkpath> <deviceapkmetadata> [--verbose]"); + System.err.println(""); + System.exit(exitCode); + } + + private static void APKEntryToString(APKEntry entry, StringBuilder outputString) { + outputString.append(String.format("Filename: %s\n", entry.getFileName())); + outputString.append(String.format("CRC32: 0x%08X\n", entry.getCrc32())); + outputString.append(String.format("Data Offset: %d\n", entry.getDataOffset())); + outputString.append(String.format("Compressed Size: %d\n", entry.getCompressedSize())); + outputString.append(String.format("Uncompressed Size: %d\n", entry.getUncompressedSize())); + } + + private static List<APKEntry> getMetadataFromFile(String deviceMetadataPath) throws IOException { + InputStream is = new FileInputStream(new File(deviceMetadataPath)); + APKMetaData apkMetaData = APKMetaData.parseDelimitedFrom(is); + return apkMetaData.getEntriesList(); + } + + private static List<SimpleEntry<APKEntry, APKEntry>> getIdenticalContents( + List<APKEntry> deviceZipEntries, List<APKEntry> hostZipEntries) throws IOException { + List<SimpleEntry<APKEntry, APKEntry>> identicalContents = + new ArrayList<SimpleEntry<APKEntry, APKEntry>>(); + + for (APKEntry deviceZipEntry : deviceZipEntries) { + for (APKEntry hostZipEntry : hostZipEntries) { + if (deviceZipEntry.getCrc32() == hostZipEntry.getCrc32()) { + identicalContents.add(new SimpleEntry(deviceZipEntry, hostZipEntry)); + } + } + } + + Collections.sort(identicalContents, new Comparator<SimpleEntry<APKEntry, APKEntry>>() { + @Override + public int compare( + SimpleEntry<APKEntry, APKEntry> p1, SimpleEntry<APKEntry, APKEntry> p2) { + return Long.compare(p1.getValue().getDataOffset(), p2.getValue().getDataOffset()); + } + }); + + return identicalContents; + } + + private static void reportIdenticalContents( + List<SimpleEntry<APKEntry, APKEntry>> identicalContentsEntrySet, File hostFile) + throws IOException { + long totalEqualBytes = 0; + int totalEqualFiles = 0; + for (SimpleEntry<APKEntry, APKEntry> entries : identicalContentsEntrySet) { + APKEntry hostAPKEntry = entries.getValue(); + totalEqualBytes += hostAPKEntry.getCompressedSize(); + totalEqualFiles++; + } + + float savingPercent = (float) (totalEqualBytes * 100) / hostFile.length(); + + System.err.println("Detected " + totalEqualFiles + " equal APK entries"); + System.err.println(totalEqualBytes + " bytes are equal out of " + hostFile.length() + " (" + + savingPercent + "%)"); + } + + static void createPatch(List<SimpleEntry<APKEntry, APKEntry>> zipEntrySimpleEntrys, + File hostFile, OutputStream patchStream) throws IOException, PatchFormatException { + FileInputStream hostFileInputStream = new FileInputStream(hostFile); + + patchStream.write(PatchUtils.SIGNATURE.getBytes(StandardCharsets.US_ASCII)); + PatchUtils.writeFormattedLong(hostFile.length(), patchStream); + + byte[] buffer = new byte[BUFFER_SIZE]; + long totalBytesWritten = 0; + Iterator<SimpleEntry<APKEntry, APKEntry>> entrySimpleEntryIterator = + zipEntrySimpleEntrys.iterator(); + while (entrySimpleEntryIterator.hasNext()) { + SimpleEntry<APKEntry, APKEntry> entrySimpleEntry = entrySimpleEntryIterator.next(); + APKEntry deviceAPKEntry = entrySimpleEntry.getKey(); + APKEntry hostAPKEntry = entrySimpleEntry.getValue(); + + long newDataLen = hostAPKEntry.getDataOffset() - totalBytesWritten; + long oldDataOffset = deviceAPKEntry.getDataOffset(); + long oldDataLen = deviceAPKEntry.getCompressedSize(); + + PatchUtils.writeFormattedLong(newDataLen, patchStream); + PatchUtils.pipe(hostFileInputStream, patchStream, buffer, newDataLen); + PatchUtils.writeFormattedLong(oldDataOffset, patchStream); + PatchUtils.writeFormattedLong(oldDataLen, patchStream); + + long skip = hostFileInputStream.skip(oldDataLen); + if (skip != oldDataLen) { + throw new PatchFormatException("skip error: attempted to skip " + oldDataLen + + " bytes but return code was " + skip); + } + totalBytesWritten += oldDataLen + newDataLen; + } + long remainderLen = hostFile.length() - totalBytesWritten; + PatchUtils.writeFormattedLong(remainderLen, patchStream); + PatchUtils.pipe(hostFileInputStream, patchStream, buffer, remainderLen); + PatchUtils.writeFormattedLong(0, patchStream); + PatchUtils.writeFormattedLong(0, patchStream); + patchStream.flush(); + } +} diff --git a/adb/fastdeploy/proto/ApkEntry.proto b/adb/fastdeploy/proto/ApkEntry.proto new file mode 100644 index 000000000..9460d1510 --- /dev/null +++ b/adb/fastdeploy/proto/ApkEntry.proto @@ -0,0 +1,18 @@ +syntax = "proto2"; + +package com.android.fastdeploy; + +option java_package = "com.android.fastdeploy"; +option java_multiple_files = true; + +message APKEntry { + required int64 crc32 = 1; + required string fileName = 2; + required int64 dataOffset = 3; + required int64 compressedSize = 4; + required int64 uncompressedSize = 5; +} + +message APKMetaData { + repeated APKEntry entries = 1; +} |