Merge "Install ld.config.recovery.txt to $(TARGET_RECOVERY_ROOT_OUT)/system/etc."
diff --git a/adb/Android.bp b/adb/Android.bp
index 41e752f..3685687 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -138,6 +138,8 @@
"client/usb_libusb.cpp",
"client/usb_dispatch.cpp",
"client/transport_mdns.cpp",
+ "client/fastdeploy.cpp",
+ "client/fastdeploycallbacks.cpp",
],
target: {
@@ -170,6 +172,12 @@
"libdiagnose_usb",
"libmdnssd",
"libusb",
+ "libandroidfw",
+ "libziparchive",
+ "libz",
+ "libutils",
+ "liblog",
+ "libcutils",
],
}
@@ -237,6 +245,7 @@
"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 @@
"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 fca435e..d467539 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 @@
// 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 0000000..95574ed
--- /dev/null
+++ b/adb/client/adb_install.cpp
@@ -0,0 +1,554 @@
+/*
+ * 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>
+#include <android-base/test_utils.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);
+}
+
+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) {
+ TemporaryFile metadataTmpFile;
+ TemporaryFile patchTmpFile;
+
+ FILE* metadataFile = fopen(metadataTmpFile.path, "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, metadataTmpFile.path, patchTmpFile.path,
+ 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, patchTmpFile.path, 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);
+ 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());
+
+ TemporaryFile metadataTmpFile;
+ TemporaryFile patchTmpFile;
+
+ if (use_fastdeploy == true) {
+ FILE* metadataFile = fopen(metadataTmpFile.path, "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], metadataTmpFile.path,
+ patchTmpFile.path, 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], patchTmpFile.path, 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);
+ 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 0000000..e9410a9
--- /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 41ac663..b55ae95 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 @@
" -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 @@
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 @@
#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 @@
#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 @@
}
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 @@
}
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 @@
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 3aa03a7..6cfd4f7 100644
--- a/adb/client/commandline.h
+++ b/adb/client/commandline.h
@@ -96,6 +96,8 @@
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 0000000..cd42b56
--- /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 0000000..d8acd30
--- /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 0000000..6c9a21f
--- /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 0000000..b428b50
--- /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 0000000..30f4730
--- /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 0000000..4f17eb7
--- /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 0000000..cd6f168
--- /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 0000000..f0655f3
--- /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 0000000..f0f00e1
--- /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 0000000..5c00505
--- /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 0000000..5577364
--- /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 0000000..9460d15
--- /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;
+}
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index f2911e0..0c2e45c 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -493,21 +493,7 @@
return _fd_set_error_str(socket_local_server(name, namespace_id, type), error);
}
-inline int network_connect(const std::string& host, int port, int type,
- int timeout, std::string* error) {
- int getaddrinfo_error = 0;
- int fd = socket_network_client_timeout(host.c_str(), port, type, timeout,
- &getaddrinfo_error);
- if (fd != -1) {
- return fd;
- }
- if (getaddrinfo_error != 0) {
- *error = gai_strerror(getaddrinfo_error);
- } else {
- *error = strerror(errno);
- }
- return -1;
-}
+int network_connect(const std::string& host, int port, int type, int timeout, std::string* error);
static __inline__ int adb_socket_accept(int serverfd, struct sockaddr* addr, socklen_t *addrlen)
{
diff --git a/adb/sysdeps/posix/network.cpp b/adb/sysdeps/posix/network.cpp
index ecd1fd2..33ddb4e 100644
--- a/adb/sysdeps/posix/network.cpp
+++ b/adb/sysdeps/posix/network.cpp
@@ -17,11 +17,15 @@
#include "sysdeps/network.h"
#include <errno.h>
+#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string>
+#include <android-base/logging.h>
+#include <cutils/sockets.h>
+
#include "adb_unique_fd.h"
static void set_error(std::string* error) {
@@ -124,3 +128,19 @@
}
return rc;
}
+
+int network_connect(const std::string& host, int port, int type, int timeout, std::string* error) {
+ int getaddrinfo_error = 0;
+ int fd = socket_network_client_timeout(host.c_str(), port, type, timeout, &getaddrinfo_error);
+ if (fd != -1) {
+ return fd;
+ }
+ if (getaddrinfo_error != 0) {
+ *error = gai_strerror(getaddrinfo_error);
+ LOG(WARNING) << "failed to resolve host '" << host << "': " << *error;
+ } else {
+ *error = strerror(errno);
+ LOG(WARNING) << "failed to connect to '" << host << "': " << *error;
+ }
+ return -1;
+}
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index 1431252..8b8fd51 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -74,6 +74,7 @@
std::string host;
int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
if (!android::base::ParseNetAddress(address, &host, &port, &serial, response)) {
+ D("failed to parse address: '%s'", address.c_str());
return std::make_tuple(unique_fd(), port, serial);
}
@@ -103,6 +104,7 @@
return;
}
+ D("connection requested to '%s'", address.c_str());
unique_fd fd;
int port;
std::string serial;
diff --git a/debuggerd/libdebuggerd/open_files_list.cpp b/debuggerd/libdebuggerd/open_files_list.cpp
index 1fdf236..03e4e8e 100644
--- a/debuggerd/libdebuggerd/open_files_list.cpp
+++ b/debuggerd/libdebuggerd/open_files_list.cpp
@@ -75,7 +75,6 @@
ALOGE("failed to read fdsan table entry %zu: %s", i, strerror(errno));
return;
}
- ALOGE("fd %zu = %#" PRIx64, i, entry.close_tag.load());
if (entry.close_tag) {
(*list)[i].fdsan_owner = entry.close_tag.load();
}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index dc94952..db6d5d6 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -122,9 +122,9 @@
{ "dts", "dt.img", "dt.sig", "dts", true, false },
{ "odm", "odm.img", "odm.sig", "odm", true, false },
{ "product", "product.img", "product.sig", "product", true, false },
- { "product-services",
- "product-services.img",
- "product-services.sig",
+ { "product_services",
+ "product_services.img",
+ "product_services.sig",
"product_services",
true, true },
{ "recovery", "recovery.img", "recovery.sig", "recovery", true, false },
diff --git a/init/init.cpp b/init/init.cpp
index b550f1b..16564f4 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -114,8 +114,8 @@
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
- if (!parser.ParseConfig("/product-services/etc/init")) {
- late_import_paths.emplace_back("/product-services/etc/init");
+ if (!parser.ParseConfig("/product_services/etc/init")) {
+ late_import_paths.emplace_back("/product_services/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
diff --git a/init/property_service.cpp b/init/property_service.cpp
index cd2f630..5c8b92a 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -692,7 +692,7 @@
}
}
load_properties_from_file("/product/build.prop", NULL);
- load_properties_from_file("/product-services/build.prop", NULL);
+ load_properties_from_file("/product_services/build.prop", NULL);
load_properties_from_file("/odm/default.prop", NULL);
load_properties_from_file("/vendor/default.prop", NULL);
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index af8f0a2..bd5f26f 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -149,7 +149,7 @@
{ 00444, AID_ROOT, AID_ROOT, 0, oem_conf_dir + 1 },
{ 00444, AID_ROOT, AID_ROOT, 0, oem_conf_file + 1 },
{ 00600, AID_ROOT, AID_ROOT, 0, "product/build.prop" },
- { 00600, AID_ROOT, AID_ROOT, 0, "product-services/build.prop" },
+ { 00600, AID_ROOT, AID_ROOT, 0, "product_services/build.prop" },
{ 00750, AID_ROOT, AID_SHELL, 0, "sbin/fs_mgr" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/bin/crash_dump32" },
{ 00755, AID_ROOT, AID_SHELL, 0, "system/bin/crash_dump64" },
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 846da24..07f0797 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -94,9 +94,9 @@
LOCAL_POST_INSTALL_CMD += ; ln -sf /system/product $(TARGET_ROOT_OUT)/product
endif
ifdef BOARD_USES_PRODUCT_SERVICESIMAGE
- LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/product-services
+ LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/product_services
else
- LOCAL_POST_INSTALL_CMD += ; ln -sf /system/product-services $(TARGET_ROOT_OUT)/product-services
+ LOCAL_POST_INSTALL_CMD += ; ln -sf /system/product_services $(TARGET_ROOT_OUT)/product_services
endif
ifdef BOARD_USES_METADATA_PARTITION
LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/metadata
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index 620f0fd..f2892f8 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -79,8 +79,8 @@
namespace.default.asan.search.paths += /system/${LIB}
namespace.default.asan.search.paths += /data/asan/product/${LIB}
namespace.default.asan.search.paths += /product/${LIB}
-namespace.default.asan.search.paths += /data/asan/product-services/${LIB}
-namespace.default.asan.search.paths += /product-services/${LIB}
+namespace.default.asan.search.paths += /data/asan/product_services/${LIB}
+namespace.default.asan.search.paths += /product_services/${LIB}
namespace.default.asan.permitted.paths = /data
namespace.default.asan.permitted.paths += /system/${LIB}/drm
diff --git a/storaged/include/storaged_uid_monitor.h b/storaged/include/storaged_uid_monitor.h
index 3a718fa..fffb3d2 100644
--- a/storaged/include/storaged_uid_monitor.h
+++ b/storaged/include/storaged_uid_monitor.h
@@ -68,31 +68,33 @@
struct uid_record {
string name;
- struct uid_io_usage ios;
+ uid_io_usage ios;
};
struct uid_records {
uint64_t start_ts;
- vector<struct uid_record> entries;
+ vector<uid_record> entries;
};
class uid_monitor {
private:
FRIEND_TEST(storaged_test, uid_monitor);
+ FRIEND_TEST(storaged_test, load_uid_io_proto);
+
// last dump from /proc/uid_io/stats, uid -> uid_info
- unordered_map<uint32_t, uid_info> last_uid_io_stats;
+ unordered_map<uint32_t, uid_info> last_uid_io_stats_;
// current io usage for next report, app name -> uid_io_usage
- unordered_map<string, struct uid_io_usage> curr_io_stats;
+ unordered_map<string, uid_io_usage> curr_io_stats_;
// io usage records, end timestamp -> {start timestamp, vector of records}
- map<uint64_t, struct uid_records> io_history;
+ map<uint64_t, uid_records> io_history_;
// charger ON/OFF
- charger_stat_t charger_stat;
+ charger_stat_t charger_stat_;
// protects curr_io_stats, last_uid_io_stats, records and charger_stat
- Mutex uidm_mutex;
+ Mutex uidm_mutex_;
// start time for IO records
- uint64_t start_ts;
+ uint64_t start_ts_;
// true if UID_IO_STATS_PATH is accessible
- const bool enable;
+ const bool enabled_;
// reads from /proc/uid_io/stats
unordered_map<uint32_t, uid_info> get_uid_io_stats_locked();
@@ -103,6 +105,10 @@
// writes io_history to protobuf
void update_uid_io_proto(unordered_map<int, StoragedProto>* protos);
+ // Ensure that io_history_ can append |n| items without exceeding
+ // MAX_UID_RECORDS_SIZE in size.
+ void maybe_shrink_history_for_items(size_t nitems);
+
public:
uid_monitor();
// called by storaged main thread
@@ -110,16 +116,20 @@
// called by storaged -u
unordered_map<uint32_t, uid_info> get_uid_io_stats();
// called by dumpsys
- map<uint64_t, struct uid_records> dump(
+ map<uint64_t, uid_records> dump(
double hours, uint64_t threshold, bool force_report);
// called by battery properties listener
void set_charger_state(charger_stat_t stat);
// called by storaged periodic_chore or dump with force_report
- bool enabled() { return enable; };
+ bool enabled() { return enabled_; };
void report(unordered_map<int, StoragedProto>* protos);
// restores io_history from protobuf
- void load_uid_io_proto(const UidIOUsage& proto);
+ void load_uid_io_proto(userid_t user_id, const UidIOUsage& proto);
void clear_user_history(userid_t user_id);
+
+ map<uint64_t, uid_records>& io_history() { return io_history_; }
+
+ static constexpr int MAX_UID_RECORDS_SIZE = 1000 * 48; // 1000 uids in 48 hours
};
#endif /* _STORAGED_UID_MONITOR_H_ */
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index f346c38..77c6167 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -194,7 +194,7 @@
return;
}
- mUidm.load_uid_io_proto(proto.uid_io_usage());
+ mUidm.load_uid_io_proto(user_id, proto.uid_io_usage());
if (user_id == USER_SYSTEM) {
storage_info->load_perf_history_proto(proto.perf_history());
diff --git a/storaged/storaged_uid_monitor.cpp b/storaged/storaged_uid_monitor.cpp
index 5745782..55380ba 100644
--- a/storaged/storaged_uid_monitor.cpp
+++ b/storaged/storaged_uid_monitor.cpp
@@ -21,6 +21,7 @@
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <android/content/pm/IPackageManagerNative.h>
#include <android-base/file.h>
@@ -50,7 +51,7 @@
std::unordered_map<uint32_t, uid_info> uid_monitor::get_uid_io_stats()
{
- Mutex::Autolock _l(uidm_mutex);
+ Mutex::Autolock _l(uidm_mutex_);
return get_uid_io_stats_locked();
};
@@ -178,10 +179,10 @@
uid_io_stats[u.uid].name = std::to_string(u.uid);
uids.push_back(u.uid);
uid_names.push_back(&uid_io_stats[u.uid].name);
- if (last_uid_io_stats.find(u.uid) == last_uid_io_stats.end()) {
+ if (last_uid_io_stats_.find(u.uid) == last_uid_io_stats_.end()) {
refresh_uid_names = true;
} else {
- uid_io_stats[u.uid].name = last_uid_io_stats[u.uid].name;
+ uid_io_stats[u.uid].name = last_uid_io_stats_[u.uid].name;
}
} else {
task_info t;
@@ -200,8 +201,6 @@
namespace {
-const int MAX_UID_RECORDS_SIZE = 1000 * 48; // 1000 uids in 48 hours
-
inline size_t history_size(
const std::map<uint64_t, struct uid_records>& history)
{
@@ -218,12 +217,12 @@
{
// remove records more than 5 days old
if (curr_ts > 5 * DAY_TO_SEC) {
- auto it = io_history.lower_bound(curr_ts - 5 * DAY_TO_SEC);
- io_history.erase(io_history.begin(), it);
+ auto it = io_history_.lower_bound(curr_ts - 5 * DAY_TO_SEC);
+ io_history_.erase(io_history_.begin(), it);
}
struct uid_records new_records;
- for (const auto& p : curr_io_stats) {
+ for (const auto& p : curr_io_stats_) {
struct uid_record record = {};
record.name = p.first;
if (!p.second.uid_ios.is_zero()) {
@@ -237,23 +236,26 @@
}
}
- curr_io_stats.clear();
- new_records.start_ts = start_ts;
- start_ts = curr_ts;
+ curr_io_stats_.clear();
+ new_records.start_ts = start_ts_;
+ start_ts_ = curr_ts;
if (new_records.entries.empty())
return;
// make some room for new records
- ssize_t overflow = history_size(io_history) +
- new_records.entries.size() - MAX_UID_RECORDS_SIZE;
- while (overflow > 0 && io_history.size() > 0) {
- auto del_it = io_history.begin();
- overflow -= del_it->second.entries.size();
- io_history.erase(io_history.begin());
- }
+ maybe_shrink_history_for_items(new_records.entries.size());
- io_history[curr_ts] = new_records;
+ io_history_[curr_ts] = new_records;
+}
+
+void uid_monitor::maybe_shrink_history_for_items(size_t nitems) {
+ ssize_t overflow = history_size(io_history_) + nitems - MAX_UID_RECORDS_SIZE;
+ while (overflow > 0 && io_history_.size() > 0) {
+ auto del_it = io_history_.begin();
+ overflow -= del_it->second.entries.size();
+ io_history_.erase(io_history_.begin());
+ }
}
std::map<uint64_t, struct uid_records> uid_monitor::dump(
@@ -263,7 +265,7 @@
report(nullptr);
}
- Mutex::Autolock _l(uidm_mutex);
+ Mutex::Autolock _l(uidm_mutex_);
std::map<uint64_t, struct uid_records> dump_records;
uint64_t first_ts = 0;
@@ -272,7 +274,7 @@
first_ts = time(NULL) - hours * HOUR_TO_SEC;
}
- for (auto it = io_history.lower_bound(first_ts); it != io_history.end(); ++it) {
+ for (auto it = io_history_.lower_bound(first_ts); it != io_history_.end(); ++it) {
const std::vector<struct uid_record>& recs = it->second.entries;
struct uid_records filtered;
@@ -311,29 +313,29 @@
for (const auto& it : uid_io_stats) {
const uid_info& uid = it.second;
- if (curr_io_stats.find(uid.name) == curr_io_stats.end()) {
- curr_io_stats[uid.name] = {};
+ if (curr_io_stats_.find(uid.name) == curr_io_stats_.end()) {
+ curr_io_stats_[uid.name] = {};
}
- struct uid_io_usage& usage = curr_io_stats[uid.name];
+ struct uid_io_usage& usage = curr_io_stats_[uid.name];
usage.user_id = multiuser_get_user_id(uid.uid);
int64_t fg_rd_delta = uid.io[FOREGROUND].read_bytes -
- last_uid_io_stats[uid.uid].io[FOREGROUND].read_bytes;
+ last_uid_io_stats_[uid.uid].io[FOREGROUND].read_bytes;
int64_t bg_rd_delta = uid.io[BACKGROUND].read_bytes -
- last_uid_io_stats[uid.uid].io[BACKGROUND].read_bytes;
+ last_uid_io_stats_[uid.uid].io[BACKGROUND].read_bytes;
int64_t fg_wr_delta = uid.io[FOREGROUND].write_bytes -
- last_uid_io_stats[uid.uid].io[FOREGROUND].write_bytes;
+ last_uid_io_stats_[uid.uid].io[FOREGROUND].write_bytes;
int64_t bg_wr_delta = uid.io[BACKGROUND].write_bytes -
- last_uid_io_stats[uid.uid].io[BACKGROUND].write_bytes;
+ last_uid_io_stats_[uid.uid].io[BACKGROUND].write_bytes;
- usage.uid_ios.bytes[READ][FOREGROUND][charger_stat] +=
+ usage.uid_ios.bytes[READ][FOREGROUND][charger_stat_] +=
(fg_rd_delta < 0) ? 0 : fg_rd_delta;
- usage.uid_ios.bytes[READ][BACKGROUND][charger_stat] +=
+ usage.uid_ios.bytes[READ][BACKGROUND][charger_stat_] +=
(bg_rd_delta < 0) ? 0 : bg_rd_delta;
- usage.uid_ios.bytes[WRITE][FOREGROUND][charger_stat] +=
+ usage.uid_ios.bytes[WRITE][FOREGROUND][charger_stat_] +=
(fg_wr_delta < 0) ? 0 : fg_wr_delta;
- usage.uid_ios.bytes[WRITE][BACKGROUND][charger_stat] +=
+ usage.uid_ios.bytes[WRITE][BACKGROUND][charger_stat_] +=
(bg_wr_delta < 0) ? 0 : bg_wr_delta;
for (const auto& task_it : uid.tasks) {
@@ -341,34 +343,34 @@
const pid_t pid = task_it.first;
const std::string& comm = task_it.second.comm;
int64_t task_fg_rd_delta = task.io[FOREGROUND].read_bytes -
- last_uid_io_stats[uid.uid].tasks[pid].io[FOREGROUND].read_bytes;
+ last_uid_io_stats_[uid.uid].tasks[pid].io[FOREGROUND].read_bytes;
int64_t task_bg_rd_delta = task.io[BACKGROUND].read_bytes -
- last_uid_io_stats[uid.uid].tasks[pid].io[BACKGROUND].read_bytes;
+ last_uid_io_stats_[uid.uid].tasks[pid].io[BACKGROUND].read_bytes;
int64_t task_fg_wr_delta = task.io[FOREGROUND].write_bytes -
- last_uid_io_stats[uid.uid].tasks[pid].io[FOREGROUND].write_bytes;
+ last_uid_io_stats_[uid.uid].tasks[pid].io[FOREGROUND].write_bytes;
int64_t task_bg_wr_delta = task.io[BACKGROUND].write_bytes -
- last_uid_io_stats[uid.uid].tasks[pid].io[BACKGROUND].write_bytes;
+ last_uid_io_stats_[uid.uid].tasks[pid].io[BACKGROUND].write_bytes;
io_usage& task_usage = usage.task_ios[comm];
- task_usage.bytes[READ][FOREGROUND][charger_stat] +=
+ task_usage.bytes[READ][FOREGROUND][charger_stat_] +=
(task_fg_rd_delta < 0) ? 0 : task_fg_rd_delta;
- task_usage.bytes[READ][BACKGROUND][charger_stat] +=
+ task_usage.bytes[READ][BACKGROUND][charger_stat_] +=
(task_bg_rd_delta < 0) ? 0 : task_bg_rd_delta;
- task_usage.bytes[WRITE][FOREGROUND][charger_stat] +=
+ task_usage.bytes[WRITE][FOREGROUND][charger_stat_] +=
(task_fg_wr_delta < 0) ? 0 : task_fg_wr_delta;
- task_usage.bytes[WRITE][BACKGROUND][charger_stat] +=
+ task_usage.bytes[WRITE][BACKGROUND][charger_stat_] +=
(task_bg_wr_delta < 0) ? 0 : task_bg_wr_delta;
}
}
- last_uid_io_stats = uid_io_stats;
+ last_uid_io_stats_ = uid_io_stats;
}
void uid_monitor::report(unordered_map<int, StoragedProto>* protos)
{
if (!enabled()) return;
- Mutex::Autolock _l(uidm_mutex);
+ Mutex::Autolock _l(uidm_mutex_);
update_curr_io_stats_locked();
add_records_locked(time(NULL));
@@ -408,7 +410,7 @@
void uid_monitor::update_uid_io_proto(unordered_map<int, StoragedProto>* protos)
{
- for (const auto& item : io_history) {
+ for (const auto& item : io_history_) {
const uint64_t& end_ts = item.first;
const struct uid_records& recs = item.second;
unordered_map<userid_t, UidIOItem*> user_items;
@@ -448,9 +450,9 @@
void uid_monitor::clear_user_history(userid_t user_id)
{
- Mutex::Autolock _l(uidm_mutex);
+ Mutex::Autolock _l(uidm_mutex_);
- for (auto& item : io_history) {
+ for (auto& item : io_history_) {
vector<uid_record>* entries = &item.second.entries;
entries->erase(
remove_if(entries->begin(), entries->end(),
@@ -459,27 +461,42 @@
entries->end());
}
- for (auto it = io_history.begin(); it != io_history.end(); ) {
+ for (auto it = io_history_.begin(); it != io_history_.end(); ) {
if (it->second.entries.empty()) {
- it = io_history.erase(it);
+ it = io_history_.erase(it);
} else {
it++;
}
}
}
-void uid_monitor::load_uid_io_proto(const UidIOUsage& uid_io_proto)
+void uid_monitor::load_uid_io_proto(userid_t user_id, const UidIOUsage& uid_io_proto)
{
if (!enabled()) return;
- Mutex::Autolock _l(uidm_mutex);
+ Mutex::Autolock _l(uidm_mutex_);
for (const auto& item_proto : uid_io_proto.uid_io_items()) {
const UidIORecords& records_proto = item_proto.records();
- struct uid_records* recs = &io_history[item_proto.end_ts()];
+ struct uid_records* recs = &io_history_[item_proto.end_ts()];
+
+ // It's possible that the same uid_io_proto file gets loaded more than
+ // once, for example, if system_server crashes. In this case we avoid
+ // adding duplicate entries, so we build a quick way to check for
+ // duplicates.
+ std::unordered_set<std::string> existing_uids;
+ for (const auto& rec : recs->entries) {
+ if (rec.ios.user_id == user_id) {
+ existing_uids.emplace(rec.name);
+ }
+ }
recs->start_ts = records_proto.start_ts();
for (const auto& rec_proto : records_proto.entries()) {
+ if (existing_uids.find(rec_proto.uid_name()) != existing_uids.end()) {
+ continue;
+ }
+
struct uid_record record;
record.name = rec_proto.uid_name();
record.ios.user_id = rec_proto.user_id();
@@ -492,29 +509,35 @@
}
recs->entries.push_back(record);
}
+
+ // We already added items, so this will just cull down to the maximum
+ // length. We do not remove anything if there is only one entry.
+ if (io_history_.size() > 1) {
+ maybe_shrink_history_for_items(0);
+ }
}
}
void uid_monitor::set_charger_state(charger_stat_t stat)
{
- Mutex::Autolock _l(uidm_mutex);
+ Mutex::Autolock _l(uidm_mutex_);
- if (charger_stat == stat) {
+ if (charger_stat_ == stat) {
return;
}
update_curr_io_stats_locked();
- charger_stat = stat;
+ charger_stat_ = stat;
}
void uid_monitor::init(charger_stat_t stat)
{
- charger_stat = stat;
+ charger_stat_ = stat;
- start_ts = time(NULL);
- last_uid_io_stats = get_uid_io_stats();
+ start_ts_ = time(NULL);
+ last_uid_io_stats_ = get_uid_io_stats();
}
uid_monitor::uid_monitor()
- : enable(!access(UID_IO_STATS_PATH, R_OK)) {
+ : enabled_(!access(UID_IO_STATS_PATH, R_OK)) {
}
diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp
index ec47b65..64009c2 100644
--- a/storaged/tests/storaged_test.cpp
+++ b/storaged/tests/storaged_test.cpp
@@ -443,8 +443,9 @@
TEST(storaged_test, uid_monitor) {
uid_monitor uidm;
+ auto& io_history = uidm.io_history();
- uidm.io_history[200] = {
+ io_history[200] = {
.start_ts = 100,
.entries = {
{ "app1", {
@@ -466,7 +467,7 @@
},
};
- uidm.io_history[300] = {
+ io_history[300] = {
.start_ts = 200,
.entries = {
{ "app1", {
@@ -526,9 +527,9 @@
EXPECT_EQ(user_1_item_1.records().entries(0).user_id(), 1UL);
EXPECT_EQ(user_1_item_1.records().entries(0).uid_io().wr_fg_chg_off(), 1000UL);
- uidm.io_history.clear();
+ io_history.clear();
- uidm.io_history[300] = {
+ io_history[300] = {
.start_ts = 200,
.entries = {
{ "app1", {
@@ -539,7 +540,7 @@
},
};
- uidm.io_history[400] = {
+ io_history[400] = {
.start_ts = 300,
.entries = {
{ "app1", {
@@ -550,16 +551,16 @@
},
};
- uidm.load_uid_io_proto(protos[0].uid_io_usage());
- uidm.load_uid_io_proto(protos[1].uid_io_usage());
+ uidm.load_uid_io_proto(0, protos[0].uid_io_usage());
+ uidm.load_uid_io_proto(1, protos[1].uid_io_usage());
- EXPECT_EQ(uidm.io_history.size(), 3UL);
- EXPECT_EQ(uidm.io_history.count(200), 1UL);
- EXPECT_EQ(uidm.io_history.count(300), 1UL);
- EXPECT_EQ(uidm.io_history.count(400), 1UL);
+ EXPECT_EQ(io_history.size(), 3UL);
+ EXPECT_EQ(io_history.count(200), 1UL);
+ EXPECT_EQ(io_history.count(300), 1UL);
+ EXPECT_EQ(io_history.count(400), 1UL);
- EXPECT_EQ(uidm.io_history[200].start_ts, 100UL);
- const vector<struct uid_record>& entries_0 = uidm.io_history[200].entries;
+ EXPECT_EQ(io_history[200].start_ts, 100UL);
+ const vector<struct uid_record>& entries_0 = io_history[200].entries;
EXPECT_EQ(entries_0.size(), 3UL);
EXPECT_EQ(entries_0[0].name, "app1");
EXPECT_EQ(entries_0[0].ios.user_id, 0UL);
@@ -572,8 +573,8 @@
EXPECT_EQ(entries_0[2].ios.uid_ios.bytes[WRITE][FOREGROUND][CHARGER_ON], 1000UL);
EXPECT_EQ(entries_0[2].ios.uid_ios.bytes[READ][FOREGROUND][CHARGER_ON], 1000UL);
- EXPECT_EQ(uidm.io_history[300].start_ts, 200UL);
- const vector<struct uid_record>& entries_1 = uidm.io_history[300].entries;
+ EXPECT_EQ(io_history[300].start_ts, 200UL);
+ const vector<struct uid_record>& entries_1 = io_history[300].entries;
EXPECT_EQ(entries_1.size(), 3UL);
EXPECT_EQ(entries_1[0].name, "app1");
EXPECT_EQ(entries_1[0].ios.user_id, 0UL);
@@ -585,8 +586,8 @@
EXPECT_EQ(entries_1[2].ios.user_id, 1UL);
EXPECT_EQ(entries_1[2].ios.uid_ios.bytes[WRITE][FOREGROUND][CHARGER_OFF], 1000UL);
- EXPECT_EQ(uidm.io_history[400].start_ts, 300UL);
- const vector<struct uid_record>& entries_2 = uidm.io_history[400].entries;
+ EXPECT_EQ(io_history[400].start_ts, 300UL);
+ const vector<struct uid_record>& entries_2 = io_history[400].entries;
EXPECT_EQ(entries_2.size(), 1UL);
EXPECT_EQ(entries_2[0].name, "app1");
EXPECT_EQ(entries_2[0].ios.user_id, 0UL);
@@ -615,14 +616,71 @@
uidm.clear_user_history(0);
- EXPECT_EQ(uidm.io_history.size(), 2UL);
- EXPECT_EQ(uidm.io_history.count(200), 1UL);
- EXPECT_EQ(uidm.io_history.count(300), 1UL);
+ EXPECT_EQ(io_history.size(), 2UL);
+ EXPECT_EQ(io_history.count(200), 1UL);
+ EXPECT_EQ(io_history.count(300), 1UL);
- EXPECT_EQ(uidm.io_history[200].entries.size(), 1UL);
- EXPECT_EQ(uidm.io_history[300].entries.size(), 1UL);
+ EXPECT_EQ(io_history[200].entries.size(), 1UL);
+ EXPECT_EQ(io_history[300].entries.size(), 1UL);
uidm.clear_user_history(1);
- EXPECT_EQ(uidm.io_history.size(), 0UL);
+ EXPECT_EQ(io_history.size(), 0UL);
+}
+
+TEST(storaged_test, load_uid_io_proto) {
+ uid_monitor uidm;
+ auto& io_history = uidm.io_history();
+
+ static const uint64_t kProtoTime = 200;
+ io_history[kProtoTime] = {
+ .start_ts = 100,
+ .entries = {
+ { "app1", {
+ .user_id = 0,
+ .uid_ios.bytes[WRITE][FOREGROUND][CHARGER_ON] = 1000,
+ }
+ },
+ { "app2", {
+ .user_id = 0,
+ .uid_ios.bytes[READ][FOREGROUND][CHARGER_OFF] = 2000,
+ }
+ },
+ { "app3", {
+ .user_id = 0,
+ .uid_ios.bytes[READ][FOREGROUND][CHARGER_OFF] = 3000,
+ }
+ },
+ },
+ };
+
+ unordered_map<int, StoragedProto> protos;
+ uidm.update_uid_io_proto(&protos);
+ ASSERT_EQ(protos.size(), size_t(1));
+
+ // Loading the same proto many times should not add duplicate entries.
+ UidIOUsage user_0 = protos[0].uid_io_usage();
+ for (size_t i = 0; i < 10000; i++) {
+ uidm.load_uid_io_proto(0, user_0);
+ }
+ ASSERT_EQ(io_history.size(), size_t(1));
+ ASSERT_EQ(io_history[kProtoTime].entries.size(), size_t(3));
+
+ // Create duplicate entries until we go over the limit.
+ auto record = io_history[kProtoTime];
+ io_history.clear();
+ for (size_t i = 0; i < uid_monitor::MAX_UID_RECORDS_SIZE * 2; i++) {
+ if (i == kProtoTime) {
+ continue;
+ }
+ io_history[i] = record;
+ }
+ ASSERT_GT(io_history.size(), size_t(uid_monitor::MAX_UID_RECORDS_SIZE));
+
+ // After loading, the history should be truncated.
+ for (auto& item : *user_0.mutable_uid_io_items()) {
+ item.set_end_ts(io_history.size());
+ }
+ uidm.load_uid_io_proto(0, user_0);
+ ASSERT_LE(io_history.size(), size_t(uid_monitor::MAX_UID_RECORDS_SIZE));
}