Merge "Revert "fs_mgt: Fix remount failure in case of non-overlayfs system""
diff --git a/adb/Android.bp b/adb/Android.bp
index 47dafff..06cfcbf 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -27,6 +27,7 @@
"-DADB_HOST=1", // overridden by adbd_defaults
"-DALLOW_ADBD_ROOT=0", // overridden by adbd_defaults
"-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
+ "-DENABLE_FASTDEPLOY=1", // enable fast deploy
],
cpp_std: "experimental",
@@ -270,22 +271,33 @@
"client/console.cpp",
"client/adb_install.cpp",
"client/line_printer.cpp",
+ "client/fastdeploy.cpp",
+ "client/fastdeploycallbacks.cpp",
"shell_service_protocol.cpp",
],
+ generated_headers: [
+ "bin2c_fastdeployagent",
+ "bin2c_fastdeployagentscript"
+ ],
+
static_libs: [
"libadb_host",
+ "libandroidfw",
"libbase",
"libcutils",
"libcrypto_utils",
"libcrypto",
+ "libfastdeploy_host",
"libdiagnose_usb",
"liblog",
"libmdnssd",
+ "libprotobuf-cpp-lite",
"libusb",
"libutils",
"liblog",
- "libcutils",
+ "libziparchive",
+ "libz",
],
stl: "libc++_static",
@@ -295,10 +307,6 @@
// will violate ODR
shared_libs: [],
- required: [
- "deploypatchgenerator",
- ],
-
// Archive adb, adb.exe.
dist: {
targets: [
@@ -658,3 +666,90 @@
},
},
}
+
+// Note: using pipe for xxd to control the variable name generated
+// the default name used by xxd is the path to the input file.
+java_genrule {
+ name: "bin2c_fastdeployagent",
+ out: ["deployagent.inc"],
+ srcs: [":deployagent"],
+ cmd: "(echo 'unsigned char kDeployAgent[] = {' && xxd -i <$(in) && echo '};') > $(out)",
+}
+
+genrule {
+ name: "bin2c_fastdeployagentscript",
+ out: ["deployagentscript.inc"],
+ srcs: ["fastdeploy/deployagent/deployagent.sh"],
+ cmd: "(echo 'unsigned char kDeployAgentScript[] = {' && xxd -i <$(in) && echo '};') > $(out)",
+}
+
+cc_library_host_static {
+ name: "libfastdeploy_host",
+ defaults: ["adb_defaults"],
+ srcs: [
+ "fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp",
+ "fastdeploy/deploypatchgenerator/patch_utils.cpp",
+ "fastdeploy/proto/ApkEntry.proto",
+ ],
+ static_libs: [
+ "libadb_host",
+ "libandroidfw",
+ "libbase",
+ "libcutils",
+ "libcrypto_utils",
+ "libcrypto",
+ "libdiagnose_usb",
+ "liblog",
+ "libmdnssd",
+ "libusb",
+ "libutils",
+ "libziparchive",
+ "libz",
+ ],
+ stl: "libc++_static",
+ proto: {
+ type: "lite",
+ export_proto_headers: true,
+ },
+ target: {
+ windows: {
+ enabled: true,
+ shared_libs: ["AdbWinApi"],
+ },
+ },
+}
+
+cc_test_host {
+ name: "fastdeploy_test",
+ defaults: ["adb_defaults"],
+ srcs: [
+ "fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp",
+ "fastdeploy/deploypatchgenerator/patch_utils_test.cpp",
+ ],
+ static_libs: [
+ "libadb_host",
+ "libandroidfw",
+ "libbase",
+ "libcutils",
+ "libcrypto_utils",
+ "libcrypto",
+ "libdiagnose_usb",
+ "libfastdeploy_host",
+ "liblog",
+ "libmdnssd",
+ "libprotobuf-cpp-lite",
+ "libusb",
+ "libutils",
+ "libziparchive",
+ "libz",
+ ],
+ target: {
+ windows: {
+ enabled: true,
+ shared_libs: ["AdbWinApi"],
+ },
+ },
+ data: [
+ "fastdeploy/testdata/rotating_cube-release.apk",
+ ],
+}
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index f4e8664..fbae219 100644
--- a/adb/client/fastdeploy.cpp
+++ b/adb/client/fastdeploy.cpp
@@ -27,6 +27,9 @@
#include "androidfw/ZipFileRO.h"
#include "client/file_sync_client.h"
#include "commandline.h"
+#include "deployagent.inc" // Generated include via build rule.
+#include "deployagentscript.inc" // Generated include via build rule.
+#include "fastdeploy/deploypatchgenerator/deploy_patch_generator.h"
#include "fastdeploycallbacks.h"
#include "sysdeps.h"
@@ -35,6 +38,8 @@
static constexpr long kRequiredAgentVersion = 0x00000002;
static constexpr const char* kDeviceAgentPath = "/data/local/tmp/";
+static constexpr const char* kDeviceAgentFile = "/data/local/tmp/deployagent.jar";
+static constexpr const char* kDeviceAgentScript = "/data/local/tmp/deployagent";
static bool g_use_localagent = false;
@@ -71,46 +76,32 @@
g_use_localagent = use_localagent;
}
-// local_path - must start with a '/' and be relative to $ANDROID_PRODUCT_OUT
-static std::string get_agent_component_host_path(const char* local_path, const char* sdk_path) {
- std::string adb_dir = android::base::GetExecutableDirectory();
- if (adb_dir.empty()) {
- error_exit("Could not determine location of adb!");
- }
-
- if (g_use_localagent) {
- const char* product_out = getenv("ANDROID_PRODUCT_OUT");
- if (product_out == nullptr) {
- error_exit("Could not locate %s because $ANDROID_PRODUCT_OUT is not defined",
- local_path);
- }
- return android::base::StringPrintf("%s%s", product_out, local_path);
- } else {
- return adb_dir + sdk_path;
- }
-}
-
static bool deploy_agent(bool checkTimeStamps) {
std::vector<const char*> srcs;
- std::string jar_path =
- get_agent_component_host_path("/system/framework/deployagent.jar", "/deployagent.jar");
- std::string script_path =
- get_agent_component_host_path("/system/bin/deployagent", "/deployagent");
- srcs.push_back(jar_path.c_str());
- srcs.push_back(script_path.c_str());
-
- 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";
- std::string chmodCommand =
- android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentPath);
- int ret = send_shell_command(chmodCommand);
- if (ret != 0) {
- error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
- }
- } else {
- error_exit("Error pushing agent files to device");
+ // TODO: Deploy agent from bin2c directly instead of writing to disk first.
+ TemporaryFile tempAgent;
+ android::base::WriteFully(tempAgent.fd, kDeployAgent, sizeof(kDeployAgent));
+ srcs.push_back(tempAgent.path);
+ if (!do_sync_push(srcs, kDeviceAgentFile, checkTimeStamps)) {
+ error_exit("Failed to push fastdeploy agent to device.");
+ }
+ srcs.clear();
+ // TODO: Deploy agent from bin2c directly instead of writing to disk first.
+ TemporaryFile tempAgentScript;
+ android::base::WriteFully(tempAgentScript.fd, kDeployAgentScript, sizeof(kDeployAgentScript));
+ srcs.push_back(tempAgentScript.path);
+ if (!do_sync_push(srcs, kDeviceAgentScript, checkTimeStamps)) {
+ error_exit("Failed to push fastdeploy agent script to device.");
+ }
+ srcs.clear();
+ // on windows the shell script might have lost execute permission
+ // so need to set this explicitly
+ const char* kChmodCommandPattern = "chmod 777 %s";
+ std::string chmodCommand =
+ android::base::StringPrintf(kChmodCommandPattern, kDeviceAgentScript);
+ int ret = send_shell_command(chmodCommand);
+ if (ret != 0) {
+ error_exit("Error executing %s returncode: %d", chmodCommand.c_str(), ret);
}
return true;
@@ -238,34 +229,15 @@
}
}
-static std::string get_patch_generator_command() {
- if (g_use_localagent) {
- // This should never happen on a Windows machine
- const char* host_out = getenv("ANDROID_HOST_OUT");
- if (host_out == nullptr) {
- error_exit(
- "Could not locate deploypatchgenerator.jar because $ANDROID_HOST_OUT "
- "is not defined");
- }
- return android::base::StringPrintf("java -jar %s/framework/deploypatchgenerator.jar",
- host_out);
- }
-
- std::string adb_dir = android::base::GetExecutableDirectory();
- if (adb_dir.empty()) {
- error_exit("Could not locate deploypatchgenerator.jar");
- }
- return android::base::StringPrintf(R"(java -jar "%s/deploypatchgenerator.jar")",
- adb_dir.c_str());
-}
-
void create_patch(const char* apkPath, const char* metadataPath, const char* patchPath) {
- std::string generatePatchCommand = android::base::StringPrintf(
- R"(%s "%s" "%s" > "%s")", get_patch_generator_command().c_str(), apkPath, metadataPath,
- patchPath);
- int returnCode = system(generatePatchCommand.c_str());
- if (returnCode != 0) {
- error_exit("Executing %s returned %d", generatePatchCommand.c_str(), returnCode);
+ DeployPatchGenerator generator(false);
+ unique_fd patchFd(adb_open(patchPath, O_WRONLY | O_CREAT | O_CLOEXEC));
+ if (patchFd < 0) {
+ perror_exit("adb: failed to create %s", patchPath);
+ }
+ bool success = generator.CreatePatch(apkPath, metadataPath, patchFd);
+ if (!success) {
+ error_exit("Failed to create patch for %s", apkPath);
}
}
diff --git a/adb/daemon/shell_service.cpp b/adb/daemon/shell_service.cpp
index de97068..0fb14c4 100644
--- a/adb/daemon/shell_service.cpp
+++ b/adb/daemon/shell_service.cpp
@@ -222,7 +222,7 @@
bool Subprocess::ForkAndExec(std::string* error) {
unique_fd child_stdinout_sfd, child_stderr_sfd;
unique_fd parent_error_sfd, child_error_sfd;
- char pts_name[PATH_MAX];
+ const char* pts_name = nullptr;
if (command_.empty()) {
__android_log_security_bswrite(SEC_TAG_ADB_SHELL_INTERACTIVE, "");
@@ -283,10 +283,22 @@
cenv.push_back(nullptr);
if (type_ == SubprocessType::kPty) {
- int fd;
- pid_ = forkpty(&fd, pts_name, nullptr, nullptr);
+ unique_fd pty_master(posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC));
+ if (pty_master == -1) {
+ *error =
+ android::base::StringPrintf("failed to create pty master: %s", strerror(errno));
+ return false;
+ }
+ if (unlockpt(pty_master.get()) != 0) {
+ *error = android::base::StringPrintf("failed to unlockpt pty master: %s",
+ strerror(errno));
+ return false;
+ }
+
+ pid_ = fork();
+ pts_name = ptsname(pty_master.get());
if (pid_ > 0) {
- stdinout_sfd_.reset(fd);
+ stdinout_sfd_ = std::move(pty_master);
}
} else {
if (!CreateSocketpair(&stdinout_sfd_, &child_stdinout_sfd)) {
diff --git a/adb/fastdeploy/Android.bp b/adb/fastdeploy/Android.bp
index 1ba0de0..95e1a28 100644
--- a/adb/fastdeploy/Android.bp
+++ b/adb/fastdeploy/Android.bp
@@ -13,27 +13,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
-
java_binary {
name: "deployagent",
sdk_version: "24",
srcs: ["deployagent/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
static_libs: ["apkzlib_zip"],
- wrapper: "deployagent/deployagent.sh",
proto: {
type: "lite",
},
dex_preopt: {
enabled: false,
}
-}
-
-java_binary_host {
- name: "deploypatchgenerator",
- srcs: ["deploypatchgenerator/src/**/*.java", "deploylib/src/**/*.java", "proto/**/*.proto"],
- static_libs: ["apkzlib"],
- manifest: "deploypatchgenerator/manifest.txt",
- proto: {
- type: "full",
- }
-}
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
index 2d3b135..a8103c4 100644
--- a/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
+++ b/adb/fastdeploy/deployagent/src/com/android/fastdeploy/DeployAgent.java
@@ -181,7 +181,7 @@
private static void extractMetaData(String packageName) throws IOException {
File apkFile = getFileFromPackageName(packageName);
APKMetaData apkMetaData = PatchUtils.getAPKMetaData(apkFile);
- apkMetaData.writeDelimitedTo(System.out);
+ apkMetaData.writeTo(System.out);
}
private static int createInstallSession(String[] args) throws IOException {
@@ -223,7 +223,6 @@
}
int writeExitCode = writePatchedDataToSession(new RandomAccessFile(deviceFile, "r"), deltaStream, sessionId);
-
if (writeExitCode == 0) {
return commitInstallSession(sessionId);
} else {
@@ -285,7 +284,6 @@
if (oldDataLen > 0) {
PatchUtils.pipe(oldData, outputStream, buffer, (int) oldDataLen);
}
-
newDataBytesWritten += copyLen + oldDataLen;
}
diff --git a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
index f0f00e1..c60f9a6 100644
--- a/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
+++ b/adb/fastdeploy/deploylib/src/com/android/fastdeploy/PatchUtils.java
@@ -39,7 +39,7 @@
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";
+ public static final String SIGNATURE = "FASTDEPLOY";
private static long getOffsetFromEntry(StoredEntry entry) {
return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize();
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
new file mode 100644
index 0000000..22c9243
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 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 "deploy_patch_generator.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <algorithm>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "adb_unique_fd.h"
+#include "android-base/file.h"
+#include "patch_utils.h"
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+void DeployPatchGenerator::Log(const char* fmt, ...) {
+ if (!is_verbose_) {
+ return;
+ }
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ printf("\n");
+ va_end(ap);
+}
+
+void DeployPatchGenerator::APKEntryToLog(const APKEntry& entry) {
+ Log("Filename: %s", entry.filename().c_str());
+ Log("CRC32: 0x%08llX", entry.crc32());
+ Log("Data Offset: %lld", entry.dataoffset());
+ Log("Compressed Size: %lld", entry.compressedsize());
+ Log("Uncompressed Size: %lld", entry.uncompressedsize());
+}
+
+void DeployPatchGenerator::APKMetaDataToLog(const char* file, const APKMetaData& metadata) {
+ if (!is_verbose_) {
+ return;
+ }
+ Log("APK Metadata: %s", file);
+ for (int i = 0; i < metadata.entries_size(); i++) {
+ const APKEntry& entry = metadata.entries(i);
+ APKEntryToLog(entry);
+ }
+}
+
+void DeployPatchGenerator::ReportSavings(const std::vector<SimpleEntry>& identicalEntries,
+ uint64_t totalSize) {
+ long totalEqualBytes = 0;
+ int totalEqualFiles = 0;
+ for (size_t i = 0; i < identicalEntries.size(); i++) {
+ if (identicalEntries[i].deviceEntry != nullptr) {
+ totalEqualBytes += identicalEntries[i].localEntry->compressedsize();
+ totalEqualFiles++;
+ }
+ }
+ float savingPercent = (totalEqualBytes * 100.0f) / totalSize;
+ fprintf(stderr, "Detected %d equal APK entries\n", totalEqualFiles);
+ fprintf(stderr, "%ld bytes are equal out of %" PRIu64 " (%.2f%%)\n", totalEqualBytes, totalSize,
+ savingPercent);
+}
+
+void DeployPatchGenerator::GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
+ const char* localApkPath, borrowed_fd output) {
+ unique_fd input(adb_open(localApkPath, O_RDONLY | O_CLOEXEC));
+ size_t newApkSize = adb_lseek(input, 0L, SEEK_END);
+ adb_lseek(input, 0L, SEEK_SET);
+
+ PatchUtils::WriteSignature(output);
+ PatchUtils::WriteLong(newApkSize, output);
+ size_t currentSizeOut = 0;
+ // Write data from the host upto the first entry we have that matches a device entry. Then write
+ // the metadata about the device entry and repeat for all entries that match on device. Finally
+ // write out any data left. If the device and host APKs are exactly the same this ends up
+ // writing out zip metadata from the local APK followed by offsets to the data to use from the
+ // device APK.
+ for (auto&& entry : entriesToUseOnDevice) {
+ int64_t deviceDataOffset = entry.deviceEntry->dataoffset();
+ int64_t hostDataOffset = entry.localEntry->dataoffset();
+ int64_t deviceDataLength = entry.deviceEntry->compressedsize();
+ int64_t deltaFromDeviceDataStart = hostDataOffset - currentSizeOut;
+ PatchUtils::WriteLong(deltaFromDeviceDataStart, output);
+ if (deltaFromDeviceDataStart > 0) {
+ PatchUtils::Pipe(input, output, deltaFromDeviceDataStart);
+ }
+ PatchUtils::WriteLong(deviceDataOffset, output);
+ PatchUtils::WriteLong(deviceDataLength, output);
+ adb_lseek(input, deviceDataLength, SEEK_CUR);
+ currentSizeOut += deltaFromDeviceDataStart + deviceDataLength;
+ }
+ if (currentSizeOut != newApkSize) {
+ PatchUtils::WriteLong(newApkSize - currentSizeOut, output);
+ PatchUtils::Pipe(input, output, newApkSize - currentSizeOut);
+ PatchUtils::WriteLong(0, output);
+ PatchUtils::WriteLong(0, output);
+ }
+}
+
+bool DeployPatchGenerator::CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+ borrowed_fd output) {
+ std::string content;
+ APKMetaData deviceApkMetadata;
+ if (android::base::ReadFileToString(deviceApkMetadataPath, &content)) {
+ deviceApkMetadata.ParsePartialFromString(content);
+ } else {
+ // TODO: What do we want to do if we don't find any metadata.
+ // The current fallback behavior is to build a patch with the contents of |localApkPath|.
+ }
+
+ APKMetaData localApkMetadata = PatchUtils::GetAPKMetaData(localApkPath);
+ // Log gathered metadata info.
+ APKMetaDataToLog(deviceApkMetadataPath, deviceApkMetadata);
+ APKMetaDataToLog(localApkPath, localApkMetadata);
+
+ std::vector<SimpleEntry> identicalEntries;
+ uint64_t totalSize =
+ BuildIdenticalEntries(identicalEntries, localApkMetadata, deviceApkMetadata);
+ ReportSavings(identicalEntries, totalSize);
+ GeneratePatch(identicalEntries, localApkPath, output);
+ return true;
+}
+
+uint64_t DeployPatchGenerator::BuildIdenticalEntries(std::vector<SimpleEntry>& outIdenticalEntries,
+ const APKMetaData& localApkMetadata,
+ const APKMetaData& deviceApkMetadata) {
+ uint64_t totalSize = 0;
+ for (int i = 0; i < localApkMetadata.entries_size(); i++) {
+ const APKEntry& localEntry = localApkMetadata.entries(i);
+ totalSize += localEntry.compressedsize();
+ for (int j = 0; j < deviceApkMetadata.entries_size(); j++) {
+ const APKEntry& deviceEntry = deviceApkMetadata.entries(j);
+ if (deviceEntry.crc32() == localEntry.crc32() &&
+ deviceEntry.filename().compare(localEntry.filename()) == 0) {
+ SimpleEntry simpleEntry;
+ simpleEntry.localEntry = const_cast<APKEntry*>(&localEntry);
+ simpleEntry.deviceEntry = const_cast<APKEntry*>(&deviceEntry);
+ APKEntryToLog(localEntry);
+ outIdenticalEntries.push_back(simpleEntry);
+ break;
+ }
+ }
+ }
+ std::sort(outIdenticalEntries.begin(), outIdenticalEntries.end(),
+ [](const SimpleEntry& lhs, const SimpleEntry& rhs) {
+ return lhs.localEntry->dataoffset() < rhs.localEntry->dataoffset();
+ });
+ return totalSize;
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
new file mode 100644
index 0000000..30e41a5
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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 "adb_unique_fd.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+/**
+ * This class is responsible for creating a patch that can be accepted by the deployagent. The
+ * patch format is documented in GeneratePatch.
+ */
+class DeployPatchGenerator {
+ public:
+ /**
+ * Simple struct to hold mapping between local metadata and device metadata.
+ */
+ struct SimpleEntry {
+ com::android::fastdeploy::APKEntry* localEntry;
+ com::android::fastdeploy::APKEntry* deviceEntry;
+ };
+
+ /**
+ * If |is_verbose| is true ApkEntries that are similar between device and host are written to
+ * the console.
+ */
+ explicit DeployPatchGenerator(bool is_verbose) : is_verbose_(is_verbose) {}
+ /**
+ * Given a |localApkPath|, and the |deviceApkMetadataPath| from an installed APK this function
+ * writes a patch to the given |output|.
+ */
+ bool CreatePatch(const char* localApkPath, const char* deviceApkMetadataPath,
+ android::base::borrowed_fd output);
+
+ private:
+ bool is_verbose_;
+
+ /**
+ * Log function only logs data to stdout when |is_verbose_| is true.
+ */
+ void Log(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3)));
+
+ /**
+ * Helper function to log the APKMetaData structure. If |is_verbose_| is false this function
+ * early outs. |file| is the path to the file represented by |metadata|. This function is used
+ * for debugging / information.
+ */
+ void APKMetaDataToLog(const char* file, const com::android::fastdeploy::APKMetaData& metadata);
+ /**
+ * Helper function to log APKEntry.
+ */
+ void APKEntryToLog(const com::android::fastdeploy::APKEntry& entry);
+
+ /**
+ * Helper function to report savings by fastdeploy. This function prints out savings even with
+ * |is_verbose_| set to false. |totalSize| is used to show a percentage of savings. Note:
+ * |totalSize| is the size of the ZipEntries. Not the size of the entire file. The metadata of
+ * the zip data needs to be sent across with every iteration.
+ * [Patch format]
+ * |Fixed String| Signature
+ * |long| New Size of Apk
+ * |Packets[]| Array of Packets
+ *
+ * [Packet Format]
+ * |long| Size of data to use from patch
+ * |byte[]| Patch data
+ * |long| Offset of data to use already on device
+ * |long| Length of data to read from device APK
+ * TODO(b/138306784): Move the patch format to a proto.
+ */
+ void ReportSavings(const std::vector<SimpleEntry>& identicalEntries, uint64_t totalSize);
+
+ /**
+ * This enumerates each entry in |entriesToUseOnDevice| and builds a patch file copying data
+ * from |localApkPath| where we are unable to use entries already on the device. The new patch
+ * is written to |output|. The entries are expected to be sorted by data offset from lowest to
+ * highest.
+ */
+ void GeneratePatch(const std::vector<SimpleEntry>& entriesToUseOnDevice,
+ const char* localApkPath, android::base::borrowed_fd output);
+
+ protected:
+ uint64_t BuildIdenticalEntries(
+ std::vector<SimpleEntry>& outIdenticalEntries,
+ const com::android::fastdeploy::APKMetaData& localApkMetadata,
+ const com::android::fastdeploy::APKMetaData& deviceApkMetadataPath);
+};
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
new file mode 100644
index 0000000..9cdc44e
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/deploy_patch_generator_test.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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 "deploy_patch_generator.h"
+#include "patch_utils.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+static std::string GetTestFile(const std::string& name) {
+ return "fastdeploy/testdata/" + name;
+}
+
+class TestPatchGenerator : DeployPatchGenerator {
+ public:
+ TestPatchGenerator() : DeployPatchGenerator(false) {}
+ void GatherIdenticalEntries(std::vector<DeployPatchGenerator::SimpleEntry>& outIdenticalEntries,
+ const APKMetaData& metadataA, const APKMetaData& metadataB) {
+ BuildIdenticalEntries(outIdenticalEntries, metadataA, metadataB);
+ }
+};
+
+TEST(DeployPatchGeneratorTest, IdenticalFileEntries) {
+ std::string apkPath = GetTestFile("rotating_cube-release.apk");
+ APKMetaData metadataA = PatchUtils::GetAPKMetaData(apkPath.c_str());
+ TestPatchGenerator generator;
+ std::vector<DeployPatchGenerator::SimpleEntry> entries;
+ generator.GatherIdenticalEntries(entries, metadataA, metadataA);
+ // Expect the entry count to match the number of entries in the metadata.
+ const uint32_t identicalCount = entries.size();
+ const uint32_t entriesCount = metadataA.entries_size();
+ EXPECT_EQ(identicalCount, entriesCount);
+}
+
+TEST(DeployPatchGeneratorTest, NoDeviceMetadata) {
+ std::string apkPath = GetTestFile("rotating_cube-release.apk");
+ // Get size of our test apk.
+ long apkSize = 0;
+ {
+ unique_fd apkFile(adb_open(apkPath.c_str(), O_RDWR));
+ apkSize = adb_lseek(apkFile, 0L, SEEK_END);
+ }
+
+ // Create a patch that is 100% different.
+ TemporaryFile output;
+ DeployPatchGenerator generator(true);
+ generator.CreatePatch(apkPath.c_str(), "", output.fd);
+
+ // Expect a patch file that has a size at least the size of our initial APK.
+ long patchSize = adb_lseek(output.fd, 0L, SEEK_END);
+ EXPECT_GT(patchSize, apkSize);
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/manifest.txt b/adb/fastdeploy/deploypatchgenerator/manifest.txt
deleted file mode 100644
index 5c00505..0000000
--- a/adb/fastdeploy/deploypatchgenerator/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.fastdeploy.DeployPatchGenerator
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
new file mode 100644
index 0000000..f11ddd1
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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 "patch_utils.h"
+
+#include <androidfw/ZipFileRO.h>
+#include <stdio.h>
+
+#include "adb_io.h"
+#include "android-base/endian.h"
+#include "sysdeps.h"
+
+using namespace com::android;
+using namespace com::android::fastdeploy;
+using namespace android::base;
+
+static constexpr char kSignature[] = "FASTDEPLOY";
+
+APKMetaData PatchUtils::GetAPKMetaData(const char* apkPath) {
+ APKMetaData apkMetaData;
+#undef open
+ std::unique_ptr<android::ZipFileRO> zipFile(android::ZipFileRO::open(apkPath));
+#define open ___xxx_unix_open
+ if (zipFile == nullptr) {
+ printf("Could not open %s", apkPath);
+ exit(1);
+ }
+ void* cookie;
+ if (zipFile->startIteration(&cookie)) {
+ android::ZipEntryRO entry;
+ while ((entry = zipFile->nextEntry(cookie)) != NULL) {
+ char fileName[256];
+ // Make sure we have a file name.
+ // TODO: Handle filenames longer than 256.
+ if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) {
+ continue;
+ }
+
+ uint32_t uncompressedSize, compressedSize, crc32;
+ int64_t dataOffset;
+ zipFile->getEntryInfo(entry, nullptr, &uncompressedSize, &compressedSize, &dataOffset,
+ nullptr, &crc32);
+ APKEntry* apkEntry = apkMetaData.add_entries();
+ apkEntry->set_crc32(crc32);
+ apkEntry->set_filename(fileName);
+ apkEntry->set_compressedsize(compressedSize);
+ apkEntry->set_uncompressedsize(uncompressedSize);
+ apkEntry->set_dataoffset(dataOffset);
+ }
+ }
+ return apkMetaData;
+}
+
+void PatchUtils::WriteSignature(borrowed_fd output) {
+ WriteFdExactly(output, kSignature, sizeof(kSignature) - 1);
+}
+
+void PatchUtils::WriteLong(int64_t value, borrowed_fd output) {
+ int64_t toLittleEndian = htole64(value);
+ WriteFdExactly(output, &toLittleEndian, sizeof(int64_t));
+}
+
+void PatchUtils::Pipe(borrowed_fd input, borrowed_fd output, size_t amount) {
+ constexpr static int BUFFER_SIZE = 128 * 1024;
+ char buffer[BUFFER_SIZE];
+ size_t transferAmount = 0;
+ while (transferAmount != amount) {
+ long chunkAmount =
+ amount - transferAmount > BUFFER_SIZE ? BUFFER_SIZE : amount - transferAmount;
+ long readAmount = adb_read(input, buffer, chunkAmount);
+ WriteFdExactly(output, buffer, readAmount);
+ transferAmount += readAmount;
+ }
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils.h b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
new file mode 100644
index 0000000..0ebfe8f
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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_unique_fd.h"
+#include "fastdeploy/proto/ApkEntry.pb.h"
+
+/**
+ * Helper class that mirrors the PatchUtils from deploy agent.
+ */
+class PatchUtils {
+ public:
+ /**
+ * This function takes a local APK file and builds the APKMetaData required by the patching
+ * algorithm. The if this function has an error a string is printed to the terminal and exit(1)
+ * is called.
+ */
+ static com::android::fastdeploy::APKMetaData GetAPKMetaData(const char* file);
+ /**
+ * Writes a fixed signature string to the header of the patch.
+ */
+ static void WriteSignature(android::base::borrowed_fd output);
+ /**
+ * Writes an int64 to the |output| reversing the bytes.
+ */
+ static void WriteLong(int64_t value, android::base::borrowed_fd output);
+ /**
+ * Copy |amount| of data from |input| to |output|.
+ */
+ static void Pipe(android::base::borrowed_fd input, android::base::borrowed_fd output,
+ size_t amount);
+};
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
new file mode 100644
index 0000000..a7eeebf
--- /dev/null
+++ b/adb/fastdeploy/deploypatchgenerator/patch_utils_test.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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 "patch_utils.h"
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sstream>
+#include <string>
+
+#include "adb_io.h"
+#include "sysdeps.h"
+
+using namespace com::android::fastdeploy;
+
+static std::string GetTestFile(const std::string& name) {
+ return "fastdeploy/testdata/" + name;
+}
+
+bool FileMatchesContent(android::base::borrowed_fd input, const char* contents,
+ ssize_t contentsSize) {
+ adb_lseek(input, 0, SEEK_SET);
+ // Use a temp buffer larger than any test contents.
+ constexpr int BUFFER_SIZE = 2048;
+ char buffer[BUFFER_SIZE];
+ bool result = true;
+ // Validate size of files is equal.
+ ssize_t readAmount = adb_read(input, buffer, BUFFER_SIZE);
+ EXPECT_EQ(readAmount, contentsSize);
+ result = memcmp(buffer, contents, readAmount) == 0;
+ for (int i = 0; i < readAmount; i++) {
+ printf("%x", buffer[i]);
+ }
+ printf(" == ");
+ for (int i = 0; i < contentsSize; i++) {
+ printf("%x", contents[i]);
+ }
+ printf("\n");
+
+ return result;
+}
+
+TEST(PatchUtilsTest, SwapLongWrites) {
+ TemporaryFile output;
+ PatchUtils::WriteLong(0x0011223344556677, output.fd);
+ adb_lseek(output.fd, 0, SEEK_SET);
+ const char expected[] = {0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
+ EXPECT_TRUE(FileMatchesContent(output.fd, expected, 8));
+}
+
+TEST(PatchUtilsTest, PipeWritesAmountToOutput) {
+ std::string expected("Some Data");
+ TemporaryFile input;
+ TemporaryFile output;
+ // Populate input file.
+ WriteFdExactly(input.fd, expected);
+ adb_lseek(input.fd, 0, SEEK_SET);
+ // Open input file for read, and output file for write.
+ PatchUtils::Pipe(input.fd, output.fd, expected.size());
+ // Validate pipe worked
+ EXPECT_TRUE(FileMatchesContent(output.fd, expected.c_str(), expected.size()));
+}
+
+TEST(PatchUtilsTest, SignatureConstMatches) {
+ std::string apkFile = GetTestFile("rotating_cube-release.apk");
+ TemporaryFile output;
+ PatchUtils::WriteSignature(output.fd);
+ std::string contents("FASTDEPLOY");
+ EXPECT_TRUE(FileMatchesContent(output.fd, contents.c_str(), contents.size()));
+}
+
+TEST(PatchUtilsTest, GatherMetadata) {
+ std::string apkFile = GetTestFile("rotating_cube-release.apk");
+ APKMetaData metadata = PatchUtils::GetAPKMetaData(apkFile.c_str());
+ std::string expectedMetadata;
+ android::base::ReadFileToString(GetTestFile("rotating_cube-metadata-release.data"),
+ &expectedMetadata);
+ std::string actualMetadata;
+ metadata.SerializeToString(&actualMetadata);
+ EXPECT_EQ(expectedMetadata, actualMetadata);
+}
\ No newline at end of file
diff --git a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java b/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
deleted file mode 100644
index 24b2eab..0000000
--- a/adb/fastdeploy/deploypatchgenerator/src/com/android/fastdeploy/DeployPatchGenerator.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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);
- System.err.println("Device Entries (" + deviceZipEntries.size() + ")");
- if (verbose) {
- sb = new StringBuilder();
- for (APKEntry entry : deviceZipEntries) {
- APKEntryToString(entry, sb);
- }
- System.err.println(sb.toString());
- }
-
- List<APKEntry> hostFileEntries = PatchUtils.getAPKMetaData(hostFile).getEntriesList();
- System.err.println("Host Entries (" + hostFileEntries.size() + ")");
- if (verbose) {
- sb = new StringBuilder();
- for (APKEntry entry : hostFileEntries) {
- APKEntryToString(entry, sb);
- }
- 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() &&
- deviceZipEntry.getFileName().equals(hostZipEntry.getFileName())) {
- 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/testdata/rotating_cube-metadata-release.data b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
new file mode 100644
index 0000000..0671bf3
--- /dev/null
+++ b/adb/fastdeploy/testdata/rotating_cube-metadata-release.data
@@ -0,0 +1,6 @@
+
+#ÇÏ«META-INF/MANIFEST.MFÇ Q(W
+#AndroidManifest.xml1 ä(è
+6¦µ>#lib/armeabi-v7a/libvulkan_sample.so ÀÒQ(²ì
+ ãresources.arscôàQ ´(´
+ÂÉclasses.dexÁ ÿ(ô
diff --git a/adb/fastdeploy/testdata/rotating_cube-release.apk b/adb/fastdeploy/testdata/rotating_cube-release.apk
new file mode 100644
index 0000000..d47e0ea
--- /dev/null
+++ b/adb/fastdeploy/testdata/rotating_cube-release.apk
Binary files differ
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 0ad8d9d..a4e0d76 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -150,6 +150,15 @@
return true;
}
+std::optional<DeviceMapper::Info> DeviceMapper::GetDetailedInfo(const std::string& name) const {
+ struct dm_ioctl io;
+ InitIo(&io, name);
+ if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) {
+ return std::nullopt;
+ }
+ return Info(io.flags);
+}
+
DmDeviceState DeviceMapper::GetState(const std::string& name) const {
struct dm_ioctl io;
InitIo(&io, name);
@@ -162,6 +171,24 @@
return DmDeviceState::SUSPENDED;
}
+bool DeviceMapper::ChangeState(const std::string& name, DmDeviceState state) {
+ if (state != DmDeviceState::SUSPENDED && state != DmDeviceState::ACTIVE) {
+ return false;
+ }
+
+ struct dm_ioctl io;
+ InitIo(&io, name);
+
+ if (state == DmDeviceState::SUSPENDED) io.flags = DM_SUSPEND_FLAG;
+
+ if (ioctl(fd_, DM_DEV_SUSPEND, &io) < 0) {
+ PLOG(ERROR) << "DM_DEV_SUSPEND "
+ << (state == DmDeviceState::SUSPENDED ? "suspend" : "resume") << " failed";
+ return false;
+ }
+ return true;
+}
+
bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) {
std::string ignore_path;
if (!CreateDevice(name, table, &ignore_path, 0ms)) {
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index b28a8f2..da1c4a9 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -166,6 +166,34 @@
ASSERT_TRUE(dev.Destroy());
}
+TEST(libdm, DmSuspendResume) {
+ unique_fd tmp1(CreateTempFile("file_suspend_resume", 512));
+ ASSERT_GE(tmp1, 0);
+
+ LoopDevice loop_a(tmp1, 10s);
+ ASSERT_TRUE(loop_a.valid());
+
+ DmTable table;
+ ASSERT_TRUE(table.Emplace<DmTargetLinear>(0, 1, loop_a.device(), 0));
+ ASSERT_TRUE(table.valid());
+
+ TempDevice dev("libdm-test-dm-suspend-resume", table);
+ ASSERT_TRUE(dev.valid());
+ ASSERT_FALSE(dev.path().empty());
+
+ auto& dm = DeviceMapper::Instance();
+
+ // Test Set and Get status of device.
+ vector<DeviceMapper::TargetInfo> targets;
+ ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
+
+ ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::SUSPENDED));
+ ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::SUSPENDED);
+
+ ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::ACTIVE));
+ ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE);
+}
+
TEST(libdm, DmVerityArgsAvb2) {
std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a";
std::string algorithm = "sha1";
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 9c0c2f3..c6b37cf 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -27,6 +27,7 @@
#include <chrono>
#include <memory>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
@@ -70,15 +71,37 @@
uint64_t dev_;
};
+ class Info {
+ uint32_t flags_;
+
+ public:
+ explicit Info(uint32_t flags) : flags_(flags) {}
+
+ bool IsActiveTablePresent() const { return flags_ & DM_ACTIVE_PRESENT_FLAG; }
+ bool IsBufferFull() const { return flags_ & DM_BUFFER_FULL_FLAG; }
+ bool IsInactiveTablePresent() const { return flags_ & DM_INACTIVE_PRESENT_FLAG; }
+ bool IsReadOnly() const { return flags_ & DM_READONLY_FLAG; }
+ bool IsSuspended() const { return flags_ & DM_SUSPEND_FLAG; }
+ };
+
// Removes a device mapper device with the given name.
// Returns 'true' on success, false otherwise.
bool DeleteDevice(const std::string& name);
+ // Fetches and returns the complete state of the underlying device mapper
+ // device with given name.
+ std::optional<Info> GetDetailedInfo(const std::string& name) const;
+
// Returns the current state of the underlying device mapper device
// with given name.
// One of INVALID, SUSPENDED or ACTIVE.
DmDeviceState GetState(const std::string& name) const;
+ // Puts the given device to the specified status, which must be either:
+ // - SUSPENDED: suspend the device, or
+ // - ACTIVE: resumes the device.
+ bool ChangeState(const std::string& name, DmDeviceState state);
+
// Creates a device, loads the given table, and activates it. If the device
// is not able to be activated, it is destroyed, and false is returned.
// After creation, |path| contains the result of calling
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index c12e3b2..8797ea9 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -1060,7 +1060,7 @@
if (sRetrofitDap.has_value()) {
return *sRetrofitDap;
}
- return !android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false);
+ return android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false);
}
bool MetadataBuilder::IsRetrofitMetadata() const {
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index c2a0f33..642f2c1 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -15,10 +15,13 @@
adb remount tests
---help This help
---serial Specify device (must if multiple are present)
---color Dress output with highlighting colors
---print-time Report the test duration
+--color Dress output with highlighting colors
+--help This help
+--no-wait-screen Do not wait for display screen to settle
+--print-time Report the test duration
+--serial Specify device (must if multiple are present)
+--wait-adb <duration> adb wait timeout
+--wait-fastboot <duration> fastboot wait timeout
Conditions:
- Must be a userdebug build.
@@ -53,6 +56,7 @@
ADB_WAIT=4m
FASTBOOT_WAIT=2m
+screen_wait=true
##
## Helper Functions
@@ -185,7 +189,7 @@
[ "USAGE: adb_cat <file> >stdout
Returns: content of file to stdout with carriage returns skipped,
- true of the file exists" ]
+ true if the file exists" ]
adb_cat() {
local OUTPUT="`adb_sh cat ${1} </dev/null 2>&1`"
local ret=${?}
@@ -193,6 +197,17 @@
return ${ret}
}
+[ "USAGE: adb_ls <dirfile> >stdout
+
+Returns: filename or directoru content to stdout with carriage returns skipped,
+ true if the ls had no errors" ]
+adb_ls() {
+ local OUTPUT="`adb_sh ls ${1} </dev/null 2>/dev/null`"
+ local ret=${?}
+ echo "${OUTPUT}" | tr -d '\r'
+ return ${ret}
+}
+
[ "USAGE: adb_reboot
Returns: true if the reboot command succeeded" ]
@@ -436,6 +451,10 @@
-n - echo newline at exit
TIMEOUT - default `format_duration ${wait_for_screen_timeout}`" ]
wait_for_screen() {
+ if ! ${screen_wait}; then
+ adb_wait
+ return
+ fi
exit_function=true
if [ X"-n" = X"${1}" ]; then
exit_function=echo
@@ -743,6 +762,9 @@
OPTIONS=`getopt --alternative --unquoted \
--longoptions help,serial:,colour,color,no-colour,no-color \
+ --longoptions wait-adb:,wait-fastboot: \
+ --longoptions wait-screen,wait-display \
+ --longoptions no-wait-screen,no-wait-display \
--longoptions gtest_print_time,print-time \
-- "?hs:" ${*}` ||
( echo "${USAGE}" >&2 ; false ) ||
@@ -766,9 +788,23 @@
--no-color | --no-colour)
color=false
;;
+ --no-wait-display | --no-wait-screen)
+ screen_wait=false
+ ;;
+ --wait-display | --wait-screen)
+ screen_wait=true
+ ;;
--print-time | --gtest_print_time)
print_time=true
;;
+ --wait-adb)
+ ADB_WAIT=${2}
+ shift
+ ;;
+ --wait-fastboot)
+ FASTBOOT_WAIT=${2}
+ shift
+ ;;
--)
shift
break
@@ -1145,10 +1181,14 @@
A="Hello World! $(date)"
echo "${A}" | adb_sh cat - ">/system/hello"
+echo "${A}" | adb_sh cat - ">/system/priv-app/hello"
echo "${A}" | adb_sh cat - ">/vendor/hello"
B="`adb_cat /system/hello`" ||
- die "sytem hello"
+ die "system hello"
check_eq "${A}" "${B}" /system before reboot
+B="`adb_cat /system/priv-app/hello`" ||
+ die "system priv-app hello"
+check_eq "${A}" "${B}" /system/priv-app before reboot
B="`adb_cat /vendor/hello`" ||
die "vendor hello"
check_eq "${A}" "${B}" /vendor before reboot
@@ -1230,6 +1270,13 @@
fi
B="`adb_cat /system/hello`"
check_eq "${A}" "${B}" /system after reboot
+# If overlayfs has a nested security problem, this will fail.
+B="`adb_ls /system/`" ||
+ dir "adb ls /system"
+[ X"${B}" != X"${B#*priv-app}" ] ||
+ dir "adb ls /system/priv-app"
+B="`adb_cat /system/priv-app/hello`"
+check_eq "${A}" "${B}" /system/priv-app after reboot
echo "${GREEN}[ OK ]${NORMAL} /system content remains after reboot" >&2
# Only root can read vendor if sepolicy permissions are as expected.
adb_root ||
@@ -1351,6 +1398,12 @@
fi
B="`adb_cat /system/hello`"
check_eq "${A}" "${B}" system after flash vendor
+ B="`adb_ls /system/`" ||
+ dir "adb ls /system"
+ [ X"${B}" != X"${B#*priv-app}" ] ||
+ dir "adb ls /system/priv-app"
+ B="`adb_cat /system/priv-app/hello`"
+ check_eq "${A}" "${B}" system/priv-app after flash vendor
adb_root ||
die "adb root"
B="`adb_cat /vendor/hello`"
@@ -1392,15 +1445,17 @@
echo "${H}"
[ ${err} = 0 ] &&
( adb_sh rm /vendor/hello </dev/null 2>/dev/null || true ) &&
- adb_sh rm /system/hello </dev/null ||
+ adb_sh rm /system/hello /system/priv-app/hello </dev/null ||
( [ -n "${L}" ] && echo "${L}" && false ) ||
die -t ${T} "cleanup hello"
B="`adb_cat /system/hello`"
check_eq "cat: /system/hello: No such file or directory" "${B}" after rm
+B="`adb_cat /system/priv-app/hello`"
+check_eq "cat: /system/priv-app/hello: No such file or directory" "${B}" after rm
B="`adb_cat /vendor/hello`"
check_eq "cat: /vendor/hello: No such file or directory" "${B}" after rm
-if [ -n "${scratch_partition}" ]; then
+if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
echo "${GREEN}[ RUN ]${NORMAL} test fastboot flash to ${scratch_partition} recovery" >&2
@@ -1542,7 +1597,9 @@
adb_wait ${ADB_WAIT} ||
die "adb remount -R"
if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
- "2" = "`get_property partition.system.verified`" ]; then
+ "2" = "`get_property partition.system.verified`" ] &&
+ [ -n "`get_property ro.boot.verifiedbootstate`" -o \
+ -n "`get_property partition.system.verified`" ]; then
die "remount -R command failed to disable verity"
fi
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 7e6ad5b..d6fb006 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -50,6 +50,8 @@
std::cerr << " list <devices | targets> [-v]" << std::endl;
std::cerr << " getpath <dm-name>" << std::endl;
std::cerr << " status <dm-name>" << std::endl;
+ std::cerr << " resume <dm-name>" << std::endl;
+ std::cerr << " suspend <dm-name>" << std::endl;
std::cerr << " table <dm-name>" << std::endl;
std::cerr << " help" << std::endl;
std::cerr << std::endl;
@@ -399,6 +401,34 @@
return DumpTable("status", argc, argv);
}
+static int ResumeCmdHandler(int argc, char** argv) {
+ if (argc != 1) {
+ std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+ return -EINVAL;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ if (!dm.ChangeState(argv[0], DmDeviceState::ACTIVE)) {
+ std::cerr << "Could not resume device \"" << argv[0] << "\"." << std::endl;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int SuspendCmdHandler(int argc, char** argv) {
+ if (argc != 1) {
+ std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+ return -EINVAL;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ if (!dm.ChangeState(argv[0], DmDeviceState::SUSPENDED)) {
+ std::cerr << "Could not suspend device \"" << argv[0] << "\"." << std::endl;
+ return -EINVAL;
+ }
+ return 0;
+}
+
static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
// clang-format off
{"create", DmCreateCmdHandler},
@@ -408,6 +438,8 @@
{"getpath", GetPathCmdHandler},
{"table", TableCmdHandler},
{"status", StatusCmdHandler},
+ {"resume", ResumeCmdHandler},
+ {"suspend", SuspendCmdHandler},
// clang-format on
};
diff --git a/libion/OWNERS b/libion/OWNERS
new file mode 100644
index 0000000..143ad2d
--- /dev/null
+++ b/libion/OWNERS
@@ -0,0 +1,2 @@
+sspatil@google.com
+hridya@google.com
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index c22ce8a..a9eea8c 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -33,6 +33,8 @@
#include "public_libraries.h"
#include "utils.h"
+using android::base::Error;
+
namespace android::nativeloader {
namespace {
@@ -128,11 +130,11 @@
}
}
-NativeLoaderNamespace* LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,
- jobject class_loader, bool is_shared,
- jstring dex_path, jstring java_library_path,
- jstring java_permitted_path,
- std::string* error_msg) {
+Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,
+ jobject class_loader, bool is_shared,
+ jstring dex_path,
+ jstring java_library_path,
+ jstring java_permitted_path) {
std::string library_path; // empty string by default.
if (java_library_path != nullptr) {
@@ -158,14 +160,14 @@
}
// Initialize the anonymous namespace with the first non-empty library path.
+ Result<void> ret;
if (!library_path.empty() && !initialized_ &&
- !InitPublicNamespace(library_path.c_str(), error_msg)) {
- return nullptr;
+ !(ret = InitPublicNamespace(library_path.c_str()))) {
+ return ret.error();
}
- bool found = FindNamespaceByClassLoader(env, class_loader);
-
- LOG_ALWAYS_FATAL_IF(found, "There is already a namespace associated with this classloader");
+ LOG_ALWAYS_FATAL_IF(FindNamespaceByClassLoader(env, class_loader) != nullptr,
+ "There is already a namespace associated with this classloader");
std::string system_exposed_libraries = default_public_libraries();
const char* namespace_name = kClassloaderNamespaceName;
@@ -216,58 +218,66 @@
auto app_ns =
NativeLoaderNamespace::Create(namespace_name, library_path, permitted_path, parent_ns,
is_shared, target_sdk_version < 24 /* is_greylist_enabled */);
- if (app_ns.IsNil()) {
- *error_msg = app_ns.GetError();
- return nullptr;
+ if (!app_ns) {
+ return app_ns.error();
}
// ... and link to other namespaces to allow access to some public libraries
- bool is_bridged = app_ns.IsBridged();
+ bool is_bridged = app_ns->IsBridged();
auto platform_ns = NativeLoaderNamespace::GetPlatformNamespace(is_bridged);
- if (!app_ns.Link(platform_ns, system_exposed_libraries)) {
- *error_msg = app_ns.GetError();
- return nullptr;
+ if (!platform_ns) {
+ return platform_ns.error();
+ }
+
+ auto linked = app_ns->Link(*platform_ns, system_exposed_libraries);
+ if (!linked) {
+ return linked.error();
}
auto runtime_ns = NativeLoaderNamespace::GetExportedNamespace(kRuntimeNamespaceName, is_bridged);
// Runtime apex does not exist in host, and under certain build conditions.
- if (!runtime_ns.IsNil()) {
- if (!app_ns.Link(runtime_ns, runtime_public_libraries())) {
- *error_msg = app_ns.GetError();
- return nullptr;
+ if (runtime_ns) {
+ linked = app_ns->Link(*runtime_ns, runtime_public_libraries());
+ if (!linked) {
+ return linked.error();
}
}
// Give access to NNAPI libraries (apex-updated LLNDK library).
auto nnapi_ns =
NativeLoaderNamespace::GetExportedNamespace(kNeuralNetworksNamespaceName, is_bridged);
- if (!app_ns.Link(nnapi_ns, neuralnetworks_public_libraries())) {
- *error_msg = app_ns.GetError();
- return nullptr;
+ if (nnapi_ns) {
+ linked = app_ns->Link(*nnapi_ns, neuralnetworks_public_libraries());
+ if (!linked) {
+ return linked.error();
+ }
}
// Give access to VNDK-SP libraries from the 'vndk' namespace.
if (unbundled_vendor_or_product_app && !vndksp_libraries().empty()) {
auto vndk_ns = NativeLoaderNamespace::GetExportedNamespace(kVndkNamespaceName, is_bridged);
- if (!vndk_ns.IsNil() && !app_ns.Link(vndk_ns, vndksp_libraries())) {
- *error_msg = app_ns.GetError();
- return nullptr;
+ if (vndk_ns) {
+ linked = app_ns->Link(*vndk_ns, vndksp_libraries());
+ if (!linked) {
+ return linked.error();
+ }
}
}
- // Note that when vendor_ns is not configured, vendor_ns.IsNil() will be true
- // and it will result in linking to the default namespace which is expected
- // behavior in this case.
if (!vendor_public_libraries().empty()) {
auto vendor_ns = NativeLoaderNamespace::GetExportedNamespace(kVendorNamespaceName, is_bridged);
- if (!app_ns.Link(vendor_ns, vendor_public_libraries())) {
- *error_msg = dlerror();
- return nullptr;
+ // when vendor_ns is not configured, link to the platform namespace
+ auto target_ns = vendor_ns ? vendor_ns : platform_ns;
+ if (target_ns) {
+ linked = app_ns->Link(*target_ns, vendor_public_libraries());
+ if (!linked) {
+ return linked.error();
+ }
}
}
- namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), app_ns));
+ namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), *app_ns));
return &(namespaces_.back().second);
}
@@ -285,7 +295,7 @@
return nullptr;
}
-bool LibraryNamespaces::InitPublicNamespace(const char* library_path, std::string* error_msg) {
+Result<void> LibraryNamespaces::InitPublicNamespace(const char* library_path) {
// Ask native bride if this apps library path should be handled by it
bool is_native_bridge = NativeBridgeIsPathSupported(library_path);
@@ -296,8 +306,7 @@
initialized_ = android_init_anonymous_namespace(default_public_libraries().c_str(),
is_native_bridge ? nullptr : library_path);
if (!initialized_) {
- *error_msg = dlerror();
- return false;
+ return Error() << dlerror();
}
// and now initialize native bridge namespaces if necessary.
@@ -305,11 +314,11 @@
initialized_ = NativeBridgeInitAnonymousNamespace(default_public_libraries().c_str(),
is_native_bridge ? library_path : nullptr);
if (!initialized_) {
- *error_msg = NativeBridgeGetError();
+ return Error() << NativeBridgeGetError();
}
}
- return initialized_;
+ return {};
}
NativeLoaderNamespace* LibraryNamespaces::FindParentNamespaceByClassLoader(JNIEnv* env,
diff --git a/libnativeloader/library_namespaces.h b/libnativeloader/library_namespaces.h
index 6e9a190..e54bc0a 100644
--- a/libnativeloader/library_namespaces.h
+++ b/libnativeloader/library_namespaces.h
@@ -25,10 +25,13 @@
#include <list>
#include <string>
+#include <android-base/result.h>
#include <jni.h>
namespace android::nativeloader {
+using android::base::Result;
+
// LibraryNamespaces is a singleton object that manages NativeLoaderNamespace
// objects for an app process. Its main job is to create (and configure) a new
// NativeLoaderNamespace object for a Java ClassLoader, and to find an existing
@@ -46,13 +49,13 @@
namespaces_.clear();
initialized_ = false;
}
- NativeLoaderNamespace* Create(JNIEnv* env, uint32_t target_sdk_version, jobject class_loader,
- bool is_shared, jstring dex_path, jstring java_library_path,
- jstring java_permitted_path, std::string* error_msg);
+ Result<NativeLoaderNamespace*> Create(JNIEnv* env, uint32_t target_sdk_version,
+ jobject class_loader, bool is_shared, jstring dex_path,
+ jstring java_library_path, jstring java_permitted_path);
NativeLoaderNamespace* FindNamespaceByClassLoader(JNIEnv* env, jobject class_loader);
private:
- bool InitPublicNamespace(const char* library_path, std::string* error_msg);
+ Result<void> InitPublicNamespace(const char* library_path);
NativeLoaderNamespace* FindParentNamespaceByClassLoader(JNIEnv* env, jobject class_loader);
bool initialized_;
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 0c29324..6d3c057 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -92,12 +92,10 @@
jstring permitted_path) {
#if defined(__ANDROID__)
std::lock_guard<std::mutex> guard(g_namespaces_mutex);
-
- std::string error_msg;
- bool success = g_namespaces->Create(env, target_sdk_version, class_loader, is_shared, dex_path,
- library_path, permitted_path, &error_msg) != nullptr;
- if (!success) {
- return env->NewStringUTF(error_msg.c_str());
+ auto ns = g_namespaces->Create(env, target_sdk_version, class_loader, is_shared, dex_path,
+ library_path, permitted_path);
+ if (!ns) {
+ return env->NewStringUTF(ns.error().message().c_str());
}
#else
UNUSED(env, target_sdk_version, class_loader, is_shared, dex_path, library_path, permitted_path);
@@ -139,11 +137,14 @@
if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
// This is the case where the classloader was not created by ApplicationLoaders
// In this case we create an isolated not-shared namespace for it.
- std::string create_error_msg;
- if ((ns = g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */,
- nullptr, library_path, nullptr, &create_error_msg)) == nullptr) {
- *error_msg = strdup(create_error_msg.c_str());
+ Result<NativeLoaderNamespace*> isolated_ns =
+ g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */, nullptr,
+ library_path, nullptr);
+ if (!isolated_ns) {
+ *error_msg = strdup(isolated_ns.error().message().c_str());
return nullptr;
+ } else {
+ ns = *isolated_ns;
}
}
@@ -222,12 +223,14 @@
#if defined(__ANDROID__)
void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
bool* needs_native_bridge, char** error_msg) {
- void* handle = ns->Load(path);
- if (handle == nullptr) {
- *error_msg = ns->GetError();
+ auto handle = ns->Load(path);
+ if (!handle && error_msg != nullptr) {
+ *error_msg = strdup(handle.error().message().c_str());
}
- *needs_native_bridge = ns->IsBridged();
- return handle;
+ if (needs_native_bridge != nullptr) {
+ *needs_native_bridge = ns->IsBridged();
+ }
+ return handle ? *handle : nullptr;
}
// native_bridge_namespaces are not supported for callers of this function.
diff --git a/libnativeloader/native_loader_namespace.cpp b/libnativeloader/native_loader_namespace.cpp
index 58ac686..4b02116 100644
--- a/libnativeloader/native_loader_namespace.cpp
+++ b/libnativeloader/native_loader_namespace.cpp
@@ -28,6 +28,9 @@
#include "nativeloader/dlext_namespaces.h"
+using android::base::Error;
+using android::base::Errorf;
+
namespace android {
namespace {
@@ -35,41 +38,46 @@
constexpr const char* kDefaultNamespaceName = "default";
constexpr const char* kPlatformNamespaceName = "platform";
-} // namespace
-
-NativeLoaderNamespace NativeLoaderNamespace::GetExportedNamespace(const std::string& name,
- bool is_bridged) {
- if (!is_bridged) {
- return NativeLoaderNamespace(name, android_get_exported_namespace(name.c_str()));
- } else {
- return NativeLoaderNamespace(name, NativeBridgeGetExportedNamespace(name.c_str()));
+std::string GetLinkerError(bool is_bridged) {
+ const char* msg = is_bridged ? NativeBridgeGetError() : dlerror();
+ if (msg == nullptr) {
+ return "no error";
}
+ return std::string(msg);
}
-char* NativeLoaderNamespace::GetError() const {
- if (!IsBridged()) {
- return strdup(dlerror());
+} // namespace
+
+Result<NativeLoaderNamespace> NativeLoaderNamespace::GetExportedNamespace(const std::string& name,
+ bool is_bridged) {
+ if (!is_bridged) {
+ auto raw = android_get_exported_namespace(name.c_str());
+ if (raw != nullptr) {
+ return NativeLoaderNamespace(name, raw);
+ }
} else {
- return strdup(NativeBridgeGetError());
+ auto raw = NativeBridgeGetExportedNamespace(name.c_str());
+ if (raw != nullptr) {
+ return NativeLoaderNamespace(name, raw);
+ }
}
+ return Errorf("namespace {} does not exist or exported", name);
}
// The platform namespace is called "default" for binaries in /system and
// "platform" for those in the Runtime APEX. Try "platform" first since
// "default" always exists.
-NativeLoaderNamespace NativeLoaderNamespace::GetPlatformNamespace(bool is_bridged) {
- NativeLoaderNamespace ns = GetExportedNamespace(kPlatformNamespaceName, is_bridged);
- if (ns.IsNil()) {
+Result<NativeLoaderNamespace> NativeLoaderNamespace::GetPlatformNamespace(bool is_bridged) {
+ auto ns = GetExportedNamespace(kPlatformNamespaceName, is_bridged);
+ if (!ns) {
ns = GetExportedNamespace(kDefaultNamespaceName, is_bridged);
}
return ns;
}
-NativeLoaderNamespace NativeLoaderNamespace::Create(const std::string& name,
- const std::string& search_paths,
- const std::string& permitted_paths,
- const NativeLoaderNamespace* parent,
- bool is_shared, bool is_greylist_enabled) {
+Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
+ const std::string& name, const std::string& search_paths, const std::string& permitted_paths,
+ const NativeLoaderNamespace* parent, bool is_shared, bool is_greylist_enabled) {
bool is_bridged = false;
if (parent != nullptr) {
is_bridged = parent->IsBridged();
@@ -78,8 +86,11 @@
}
// Fall back to the platform namespace if no parent is set.
- const NativeLoaderNamespace& effective_parent =
- parent != nullptr ? *parent : GetPlatformNamespace(is_bridged);
+ auto platform_ns = GetPlatformNamespace(is_bridged);
+ if (!platform_ns) {
+ return platform_ns.error();
+ }
+ const NativeLoaderNamespace& effective_parent = parent != nullptr ? *parent : *platform_ns;
uint64_t type = ANDROID_NAMESPACE_TYPE_ISOLATED;
if (is_shared) {
@@ -93,37 +104,56 @@
android_namespace_t* raw =
android_create_namespace(name.c_str(), nullptr, search_paths.c_str(), type,
permitted_paths.c_str(), effective_parent.ToRawAndroidNamespace());
- return NativeLoaderNamespace(name, raw);
+ if (raw != nullptr) {
+ return NativeLoaderNamespace(name, raw);
+ }
} else {
native_bridge_namespace_t* raw = NativeBridgeCreateNamespace(
name.c_str(), nullptr, search_paths.c_str(), type, permitted_paths.c_str(),
effective_parent.ToRawNativeBridgeNamespace());
- return NativeLoaderNamespace(name, raw);
+ if (raw != nullptr) {
+ return NativeLoaderNamespace(name, raw);
+ }
}
+ return Errorf("failed to create {} namespace name:{}, search_paths:{}, permitted_paths:{}",
+ is_bridged ? "bridged" : "native", name, search_paths, permitted_paths);
}
-bool NativeLoaderNamespace::Link(const NativeLoaderNamespace& target,
- const std::string& shared_libs) const {
+Result<void> NativeLoaderNamespace::Link(const NativeLoaderNamespace& target,
+ const std::string& shared_libs) const {
LOG_ALWAYS_FATAL_IF(shared_libs.empty(), "empty share lib when linking %s to %s",
this->name().c_str(), target.name().c_str());
if (!IsBridged()) {
- return android_link_namespaces(this->ToRawAndroidNamespace(), target.ToRawAndroidNamespace(),
- shared_libs.c_str());
+ if (android_link_namespaces(this->ToRawAndroidNamespace(), target.ToRawAndroidNamespace(),
+ shared_libs.c_str())) {
+ return {};
+ }
} else {
- return NativeBridgeLinkNamespaces(this->ToRawNativeBridgeNamespace(),
- target.ToRawNativeBridgeNamespace(), shared_libs.c_str());
+ if (NativeBridgeLinkNamespaces(this->ToRawNativeBridgeNamespace(),
+ target.ToRawNativeBridgeNamespace(), shared_libs.c_str())) {
+ return {};
+ }
}
+ return Error() << GetLinkerError(IsBridged());
}
-void* NativeLoaderNamespace::Load(const char* lib_name) const {
+Result<void*> NativeLoaderNamespace::Load(const char* lib_name) const {
if (!IsBridged()) {
android_dlextinfo extinfo;
extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
extinfo.library_namespace = this->ToRawAndroidNamespace();
- return android_dlopen_ext(lib_name, RTLD_NOW, &extinfo);
+ void* handle = android_dlopen_ext(lib_name, RTLD_NOW, &extinfo);
+ if (handle != nullptr) {
+ return handle;
+ }
} else {
- return NativeBridgeLoadLibraryExt(lib_name, RTLD_NOW, this->ToRawNativeBridgeNamespace());
+ void* handle =
+ NativeBridgeLoadLibraryExt(lib_name, RTLD_NOW, this->ToRawNativeBridgeNamespace());
+ if (handle != nullptr) {
+ return handle;
+ }
}
+ return Error() << GetLinkerError(IsBridged());
}
} // namespace android
diff --git a/libnativeloader/native_loader_namespace.h b/libnativeloader/native_loader_namespace.h
index 71e4247..29b759c 100644
--- a/libnativeloader/native_loader_namespace.h
+++ b/libnativeloader/native_loader_namespace.h
@@ -21,22 +21,25 @@
#include <vector>
#include <android-base/logging.h>
+#include <android-base/result.h>
#include <android/dlext.h>
#include <log/log.h>
#include <nativebridge/native_bridge.h>
namespace android {
+using android::base::Result;
+
// NativeLoaderNamespace abstracts a linker namespace for the native
// architecture (ex: arm on arm) or the translated architecture (ex: arm on
// x86). Instances of this class are managed by LibraryNamespaces object.
struct NativeLoaderNamespace {
public:
- // TODO(return with errors)
- static NativeLoaderNamespace Create(const std::string& name, const std::string& search_paths,
- const std::string& permitted_paths,
- const NativeLoaderNamespace* parent, bool is_shared,
- bool is_greylist_enabled);
+ static Result<NativeLoaderNamespace> Create(const std::string& name,
+ const std::string& search_paths,
+ const std::string& permitted_paths,
+ const NativeLoaderNamespace* parent, bool is_shared,
+ bool is_greylist_enabled);
NativeLoaderNamespace(NativeLoaderNamespace&&) = default;
NativeLoaderNamespace(const NativeLoaderNamespace&) = default;
@@ -47,16 +50,13 @@
std::string name() const { return name_; }
bool IsBridged() const { return raw_.index() == 1; }
- bool IsNil() const {
- return IsBridged() ? std::get<1>(raw_) == nullptr : std::get<0>(raw_) == nullptr;
- }
- bool Link(const NativeLoaderNamespace& target, const std::string& shared_libs) const;
- void* Load(const char* lib_name) const;
- char* GetError() const;
+ Result<void> Link(const NativeLoaderNamespace& target, const std::string& shared_libs) const;
+ Result<void*> Load(const char* lib_name) const;
- static NativeLoaderNamespace GetExportedNamespace(const std::string& name, bool is_bridged);
- static NativeLoaderNamespace GetPlatformNamespace(bool is_bridged);
+ static Result<NativeLoaderNamespace> GetExportedNamespace(const std::string& name,
+ bool is_bridged);
+ static Result<NativeLoaderNamespace> GetPlatformNamespace(bool is_bridged);
private:
explicit NativeLoaderNamespace(const std::string& name, android_namespace_t* ns)
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index 10e23fd..6cee668 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -26,6 +26,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/result.h>
#include <android-base/strings.h>
#include <log/log.h>
@@ -34,6 +35,9 @@
namespace android::nativeloader {
using namespace std::string_literals;
+using android::base::ErrnoError;
+using android::base::Errorf;
+using android::base::Result;
namespace {
@@ -91,22 +95,21 @@
file_name->insert(insert_pos, vndk_version_str());
}
-const std::function<bool(const std::string&, std::string*)> always_true =
- [](const std::string&, std::string*) { return true; };
+const std::function<Result<void>(const std::string&)> always_true =
+ [](const std::string&) -> Result<void> { return {}; };
-bool ReadConfig(const std::string& configFile, std::vector<std::string>* sonames,
- const std::function<bool(const std::string& /* soname */,
- std::string* /* error_msg */)>& check_soname,
- std::string* error_msg = nullptr) {
+Result<std::vector<std::string>> ReadConfig(
+ const std::string& configFile,
+ const std::function<Result<void>(const std::string& /* soname */)>& check_soname) {
// Read list of public native libraries from the config file.
std::string file_content;
if (!base::ReadFileToString(configFile, &file_content)) {
- if (error_msg) *error_msg = strerror(errno);
- return false;
+ return ErrnoError();
}
std::vector<std::string> lines = base::Split(file_content, "\n");
+ std::vector<std::string> sonames;
for (auto& line : lines) {
auto trimmed_line = base::Trim(line);
if (trimmed_line[0] == '#' || trimmed_line.empty()) {
@@ -116,8 +119,7 @@
if (space_pos != std::string::npos) {
std::string type = trimmed_line.substr(space_pos + 1);
if (type != "32" && type != "64") {
- if (error_msg) *error_msg = "Malformed line: " + line;
- return false;
+ return Errorf("Malformed line: {}", line);
}
#if defined(__LP64__)
// Skip 32 bit public library.
@@ -133,13 +135,13 @@
trimmed_line.resize(space_pos);
}
- if (check_soname(trimmed_line, error_msg)) {
- sonames->push_back(trimmed_line);
- } else {
- return false;
+ auto ret = check_soname(trimmed_line);
+ if (!ret) {
+ return ret.error();
}
+ sonames.push_back(trimmed_line);
}
- return true;
+ return sonames;
}
void ReadExtensionLibraries(const char* dirname, std::vector<std::string>* sonames) {
@@ -162,24 +164,22 @@
"Error extracting company name from public native library list file path \"%s\"",
config_file_path.c_str());
- std::string error_msg;
-
- LOG_ALWAYS_FATAL_IF(
- !ReadConfig(config_file_path, sonames,
- [&company_name](const std::string& soname, std::string* error_msg) {
- if (android::base::StartsWith(soname, "lib") &&
- android::base::EndsWith(soname, "." + company_name + ".so")) {
- return true;
- } else {
- *error_msg = "Library name \"" + soname +
- "\" does not end with the company name: " + company_name +
- ".";
- return false;
- }
- },
- &error_msg),
- "Error reading public native library list from \"%s\": %s", config_file_path.c_str(),
- error_msg.c_str());
+ auto ret = ReadConfig(
+ config_file_path, [&company_name](const std::string& soname) -> Result<void> {
+ if (android::base::StartsWith(soname, "lib") &&
+ android::base::EndsWith(soname, "." + company_name + ".so")) {
+ return {};
+ } else {
+ return Errorf("Library name \"{}\" does not end with the company name {}.", soname,
+ company_name);
+ }
+ });
+ if (ret) {
+ sonames->insert(sonames->end(), ret->begin(), ret->end());
+ } else {
+ LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
+ config_file_path.c_str(), ret.error().message().c_str());
+ }
}
}
}
@@ -187,16 +187,17 @@
static std::string InitDefaultPublicLibraries() {
std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
- std::vector<std::string> sonames;
- std::string error_msg;
- LOG_ALWAYS_FATAL_IF(!ReadConfig(config_file, &sonames, always_true, &error_msg),
- "Error reading public native library list from \"%s\": %s",
- config_file.c_str(), error_msg.c_str());
+ auto sonames = ReadConfig(config_file, always_true);
+ if (!sonames) {
+ LOG_ALWAYS_FATAL("Error reading public native library list from \"%s\": %s",
+ config_file.c_str(), sonames.error().message().c_str());
+ return "";
+ }
std::string additional_libs = additional_public_libraries();
if (!additional_libs.empty()) {
auto vec = base::Split(additional_libs, ":");
- std::copy(vec.begin(), vec.end(), std::back_inserter(sonames));
+ std::copy(vec.begin(), vec.end(), std::back_inserter(*sonames));
}
// Remove the public libs in the runtime namespace.
@@ -216,18 +217,18 @@
continue;
}
- auto it = std::find(sonames.begin(), sonames.end(), lib_name);
- if (it != sonames.end()) {
- sonames.erase(it);
+ auto it = std::find(sonames->begin(), sonames->end(), lib_name);
+ if (it != sonames->end()) {
+ sonames->erase(it);
}
}
// Remove the public libs in the nnapi namespace.
- auto it = std::find(sonames.begin(), sonames.end(), kNeuralNetworksApexPublicLibrary);
- if (it != sonames.end()) {
- sonames.erase(it);
+ auto it = std::find(sonames->begin(), sonames->end(), kNeuralNetworksApexPublicLibrary);
+ if (it != sonames->end()) {
+ sonames->erase(it);
}
- return android::base::Join(sonames, ':');
+ return android::base::Join(*sonames, ':');
}
static std::string InitRuntimePublicLibraries() {
@@ -243,9 +244,11 @@
static std::string InitVendorPublicLibraries() {
// This file is optional, quietly ignore if the file does not exist.
- std::vector<std::string> sonames;
- ReadConfig(kVendorPublicLibrariesFile, &sonames, always_true, nullptr);
- return android::base::Join(sonames, ':');
+ auto sonames = ReadConfig(kVendorPublicLibrariesFile, always_true);
+ if (!sonames) {
+ return "";
+ }
+ return android::base::Join(*sonames, ':');
}
// read /system/etc/public.libraries-<companyname>.txt and
@@ -262,17 +265,23 @@
static std::string InitLlndkLibraries() {
std::string config_file = kLlndkLibrariesFile;
InsertVndkVersionStr(&config_file);
- std::vector<std::string> sonames;
- ReadConfig(config_file, &sonames, always_true, nullptr);
- return android::base::Join(sonames, ':');
+ auto sonames = ReadConfig(config_file, always_true);
+ if (!sonames) {
+ LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
+ return "";
+ }
+ return android::base::Join(*sonames, ':');
}
static std::string InitVndkspLibraries() {
std::string config_file = kVndkLibrariesFile;
InsertVndkVersionStr(&config_file);
- std::vector<std::string> sonames;
- ReadConfig(config_file, &sonames, always_true, nullptr);
- return android::base::Join(sonames, ':');
+ auto sonames = ReadConfig(config_file, always_true);
+ if (!sonames) {
+ LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
+ return "";
+ }
+ return android::base::Join(*sonames, ':');
}
static std::string InitNeuralNetworksPublicLibraries() {