Merge "bootstat: Add Tj thermal shutdown"
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 8e8a91c..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,16 +0,0 @@
-Copyright (C) 2017 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.
-
--------------------------------------------------------------------
-
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index c8612bf..04e1e4e 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -153,14 +153,14 @@
}
struct timeval tv = {
- .tv_sec = 1 * android::base::TimeoutMultiplier(),
+ .tv_sec = 1 * android::base::HwTimeoutMultiplier(),
.tv_usec = 0,
};
if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
PLOG(ERROR) << "failed to set send timeout on activity manager socket";
return false;
}
- tv.tv_sec = 3 * android::base::TimeoutMultiplier(); // 3 seconds on handshake read
+ tv.tv_sec = 3 * android::base::HwTimeoutMultiplier(); // 3 seconds on handshake read
if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
PLOG(ERROR) << "failed to set receive timeout on activity manager socket";
return false;
@@ -391,7 +391,7 @@
// There appears to be a bug in the kernel where our death causes SIGHUP to
// be sent to our process group if we exit while it has stopped jobs (e.g.
- // because of wait_for_gdb). Use setsid to create a new process group to
+ // because of wait_for_debugger). Use setsid to create a new process group to
// avoid hitting this.
setsid();
@@ -448,7 +448,7 @@
//
// Note: processes with many threads and minidebug-info can take a bit to
// unwind, do not make this too small. b/62828735
- alarm(30 * android::base::TimeoutMultiplier());
+ alarm(30 * android::base::HwTimeoutMultiplier());
// Get the process name (aka cmdline).
std::string process_name = get_process_name(g_target_thread);
@@ -548,15 +548,17 @@
fork_exit_write.reset();
// Defer the message until later, for readability.
- bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
+ bool wait_for_debugger = android::base::GetBoolProperty(
+ "debug.debuggerd.wait_for_debugger",
+ android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false));
if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) {
- wait_for_gdb = false;
+ wait_for_debugger = false;
}
// Detach from all of our attached threads before resuming.
for (const auto& [tid, thread] : thread_info) {
int resume_signal = thread.signo == BIONIC_SIGNAL_DEBUGGER ? 0 : thread.signo;
- if (wait_for_gdb) {
+ if (wait_for_debugger) {
resume_signal = 0;
if (tgkill(target_process, tid, SIGSTOP) != 0) {
PLOG(WARNING) << "failed to send SIGSTOP to " << tid;
@@ -641,12 +643,12 @@
}
}
- if (wait_for_gdb) {
+ if (wait_for_debugger) {
// Use ALOGI to line up with output from engrave_tombstone.
ALOGI(
"***********************************************************\n"
"* Process %d has been suspended while crashing.\n"
- "* To attach gdbserver and start gdb, run this on the host:\n"
+ "* To attach the debugger, run this on the host:\n"
"*\n"
"* gdbclient.py -p %d\n"
"*\n"
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index 7975a3a..23b106e 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -13,7 +13,6 @@
"-Werror",
"-O0",
"-fstack-protector-all",
- "-Wno-free-nonheap-object",
"-Wno-date-time",
],
srcs: ["crasher.cpp"],
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index a2b13a3..db30b8f 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -134,10 +134,14 @@
return a*2;
}
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wfree-nonheap-object"
+
noinline void abuse_heap() {
char buf[16];
free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately.
}
+#pragma clang diagnostic pop
noinline void leak() {
while (true) {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index ab95768..de37a5b 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -69,7 +69,7 @@
#define ARCH_SUFFIX ""
#endif
-constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb";
+constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger";
#define TIMEOUT(seconds, expr) \
[&]() { \
@@ -157,7 +157,7 @@
class CrasherTest : public ::testing::Test {
public:
pid_t crasher_pid = -1;
- bool previous_wait_for_gdb;
+ bool previous_wait_for_debugger;
unique_fd crasher_pipe;
unique_fd intercept_fd;
@@ -178,8 +178,13 @@
};
CrasherTest::CrasherTest() {
- previous_wait_for_gdb = android::base::GetBoolProperty(kWaitForGdbKey, false);
- android::base::SetProperty(kWaitForGdbKey, "0");
+ previous_wait_for_debugger = android::base::GetBoolProperty(kWaitForDebuggerKey, false);
+ android::base::SetProperty(kWaitForDebuggerKey, "0");
+
+ // Clear the old property too, just in case someone's been using it
+ // on this device. (We only document the new name, but we still support
+ // the old name so we don't break anyone's existing setups.)
+ android::base::SetProperty("debug.debuggerd.wait_for_gdb", "0");
}
CrasherTest::~CrasherTest() {
@@ -189,7 +194,7 @@
TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, WUNTRACED));
}
- android::base::SetProperty(kWaitForGdbKey, previous_wait_for_gdb ? "1" : "0");
+ android::base::SetProperty(kWaitForDebuggerKey, previous_wait_for_debugger ? "1" : "0");
}
void CrasherTest::StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type) {
@@ -734,9 +739,9 @@
AssertDeath(SIGABRT);
}
-TEST_F(CrasherTest, wait_for_gdb) {
- if (!android::base::SetProperty(kWaitForGdbKey, "1")) {
- FAIL() << "failed to enable wait_for_gdb";
+TEST_F(CrasherTest, wait_for_debugger) {
+ if (!android::base::SetProperty(kWaitForDebuggerKey, "1")) {
+ FAIL() << "failed to enable wait_for_debugger";
}
sleep(1);
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index ca809e4..b607397 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -274,7 +274,7 @@
// There appears to be a bug in the kernel where our death causes SIGHUP to
// be sent to our process group if we exit while it has stopped jobs (e.g.
- // because of wait_for_gdb). Use setsid to create a new process group to
+ // because of wait_for_debugger). Use setsid to create a new process group to
// avoid hitting this.
setsid();
@@ -600,7 +600,7 @@
// starting to dump right before our death.
pthread_mutex_unlock(&crash_mutex);
} else {
- // Resend the signal, so that either gdb or the parent's waitpid sees it.
+ // Resend the signal, so that either the debugger or the parent's waitpid sees it.
resend_signal(info);
}
}
diff --git a/debuggerd/libdebuggerd/test/sys/system_properties.h b/debuggerd/libdebuggerd/test/sys/system_properties.h
deleted file mode 100644
index 1f4f58a..0000000
--- a/debuggerd/libdebuggerd/test/sys/system_properties.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-#define _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-
-// This is just enough to get the property code to compile on
-// the host.
-
-#define PROP_VALUE_MAX 92
-
-#endif // _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp
index 4d4646a..613e6f5 100644
--- a/debuggerd/tombstoned/intercept_manager.cpp
+++ b/debuggerd/tombstoned/intercept_manager.cpp
@@ -163,7 +163,7 @@
event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT,
intercept_close_cb, arg);
- struct timeval timeout = {.tv_sec = 10 * android::base::TimeoutMultiplier(), .tv_usec = 0};
+ struct timeval timeout = {.tv_sec = 10 * android::base::HwTimeoutMultiplier(), .tv_usec = 0};
event_add(intercept->intercept_event, &timeout);
}
@@ -179,7 +179,7 @@
intercept->intercept_manager = static_cast<InterceptManager*>(arg);
intercept->sockfd.reset(sockfd);
- struct timeval timeout = {1 * android::base::TimeoutMultiplier(), 0};
+ struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0};
event_base* base = evconnlistener_get_base(listener);
event* intercept_event =
event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept);
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index bc2d33d..0b87b7a 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -320,7 +320,7 @@
}
// TODO: Make this configurable by the interceptor?
- struct timeval timeout = {10 * android::base::TimeoutMultiplier(), 0};
+ struct timeval timeout = {10 * android::base::HwTimeoutMultiplier(), 0};
event_base* base = event_get_base(crash->crash_event);
@@ -340,7 +340,7 @@
// TODO: Make sure that only java crashes come in on the java socket
// and only native crashes on the native socket.
- struct timeval timeout = {1 * android::base::TimeoutMultiplier(), 0};
+ struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0};
event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash);
crash->crash_socket_fd.reset(sockfd);
crash->crash_event = crash_event;
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index a1f1c17..3c4269b 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -55,6 +55,7 @@
"tcp.cpp",
"udp.cpp",
"util.cpp",
+ "vendor_boot_img_utils.cpp",
"fastboot_driver.cpp",
],
@@ -75,7 +76,9 @@
],
header_libs: [
+ "avb_headers",
"bootimg_headers",
+ "libstorage_literals_headers",
],
export_header_lib_headers: [
@@ -138,6 +141,12 @@
recovery: true,
+ product_variables: {
+ debuggable: {
+ cppflags: ["-DFB_ENABLE_FETCH"],
+ },
+ },
+
srcs: [
"device/commands.cpp",
"device/fastboot_device.cpp",
@@ -183,7 +192,8 @@
header_libs: [
"avb_headers",
"libsnapshot_headers",
- ]
+ "libstorage_literals_headers",
+ ],
}
cc_defaults {
@@ -262,12 +272,17 @@
"tcp.cpp",
"udp.cpp",
"util.cpp",
+ "vendor_boot_img_utils.cpp",
"fastboot_driver.cpp",
],
// Only version the final binaries
use_version_lib: false,
static_libs: ["libbuildversion"],
+ header_libs: [
+ "avb_headers",
+ "libstorage_literals_headers",
+ ],
generated_headers: ["platform_tools_version"],
@@ -359,3 +374,33 @@
},
},
}
+
+cc_test_host {
+ name: "fastboot_vendor_boot_img_utils_test",
+ srcs: ["vendor_boot_img_utils_test.cpp"],
+ static_libs: [
+ "libbase",
+ "libc++fs",
+ "libfastboot",
+ "libgmock",
+ "liblog",
+ ],
+ header_libs: [
+ "avb_headers",
+ "bootimg_headers",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ data: [
+ ":fastboot_test_dtb",
+ ":fastboot_test_bootconfig",
+ ":fastboot_test_vendor_ramdisk_none",
+ ":fastboot_test_vendor_ramdisk_platform",
+ ":fastboot_test_vendor_ramdisk_replace",
+ ":fastboot_test_vendor_boot_v3",
+ ":fastboot_test_vendor_boot_v4_without_frag",
+ ":fastboot_test_vendor_boot_v4_with_frag"
+ ],
+}
diff --git a/fastboot/constants.h b/fastboot/constants.h
index ba43ca5..4ea68da 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -35,6 +35,7 @@
#define FB_CMD_OEM "oem"
#define FB_CMD_GSI "gsi"
#define FB_CMD_SNAPSHOT_UPDATE "snapshot-update"
+#define FB_CMD_FETCH "fetch"
#define RESPONSE_OKAY "OKAY"
#define RESPONSE_FAIL "FAIL"
@@ -77,3 +78,4 @@
#define FB_VAR_FIRST_API_LEVEL "first-api-level"
#define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level"
#define FB_VAR_TREBLE_ENABLED "treble-enabled"
+#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index b2b6a9e..b72f3fe 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -16,6 +16,7 @@
#include "commands.h"
+#include <inttypes.h>
#include <sys/socket.h>
#include <sys/un.h>
@@ -36,6 +37,7 @@
#include <liblp/builder.h>
#include <liblp/liblp.h>
#include <libsnapshot/snapshot.h>
+#include <storage_literals/storage_literals.h>
#include <uuid/uuid.h>
#include "constants.h"
@@ -43,6 +45,12 @@
#include "flashing.h"
#include "utility.h"
+#ifdef FB_ENABLE_FETCH
+static constexpr bool kEnableFetch = true;
+#else
+static constexpr bool kEnableFetch = false;
+#endif
+
using android::fs_mgr::MetadataBuilder;
using ::android::hardware::hidl_string;
using ::android::hardware::boot::V1_0::BoolResult;
@@ -54,6 +62,8 @@
using android::snapshot::SnapshotManager;
using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
+using namespace android::storage_literals;
+
struct VariableHandlers {
// Callback to retrieve the value of a single variable.
std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get;
@@ -136,7 +146,9 @@
{FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}},
{FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}},
{FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
- {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}};
+ {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
+ {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
+ };
if (args.size() < 2) {
return device->WriteFail("Missing argument");
@@ -671,3 +683,175 @@
}
return device->WriteStatus(FastbootResult::OKAY, "Success");
}
+
+namespace {
+// Helper of FetchHandler.
+class PartitionFetcher {
+ public:
+ static bool Fetch(FastbootDevice* device, const std::vector<std::string>& args) {
+ if constexpr (!kEnableFetch) {
+ return device->WriteFail("Fetch is not allowed on user build");
+ }
+
+ if (GetDeviceLockStatus()) {
+ return device->WriteFail("Fetch is not allowed on locked devices");
+ }
+
+ PartitionFetcher fetcher(device, args);
+ if (fetcher.Open()) {
+ fetcher.Fetch();
+ }
+ CHECK(fetcher.ret_.has_value());
+ return *fetcher.ret_;
+ }
+
+ private:
+ PartitionFetcher(FastbootDevice* device, const std::vector<std::string>& args)
+ : device_(device), args_(&args) {}
+ // Return whether the partition is successfully opened.
+ // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value
+ // that FetchHandler should return.
+ bool Open() {
+ if (args_->size() < 2) {
+ ret_ = device_->WriteFail("Missing partition arg");
+ return false;
+ }
+
+ partition_name_ = args_->at(1);
+ if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) ==
+ kAllowedPartitions.end()) {
+ ret_ = device_->WriteFail("Fetch is only allowed on [" +
+ android::base::Join(kAllowedPartitions, ", ") + "]");
+ return false;
+ }
+
+ if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
+ ret_ = device_->WriteFail(
+ android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
+ return false;
+ }
+
+ partition_size_ = get_block_device_size(handle_.fd());
+ if (partition_size_ == 0) {
+ ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0",
+ partition_name_.c_str()));
+ return false;
+ }
+
+ start_offset_ = 0;
+ if (args_->size() >= 3) {
+ if (!android::base::ParseUint(args_->at(2), &start_offset_)) {
+ ret_ = device_->WriteFail("Invalid offset, must be integer");
+ return false;
+ }
+ if (start_offset_ > std::numeric_limits<off64_t>::max()) {
+ ret_ = device_->WriteFail(
+ android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_));
+ return false;
+ }
+ }
+ if (start_offset_ > partition_size_) {
+ ret_ = device_->WriteFail(android::base::StringPrintf(
+ "Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_,
+ partition_name_.c_str(), partition_size_));
+ return false;
+ }
+ uint64_t maximum_total_size_to_read = partition_size_ - start_offset_;
+ total_size_to_read_ = maximum_total_size_to_read;
+ if (args_->size() >= 4) {
+ if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) {
+ ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer");
+ return false;
+ }
+ }
+ if (total_size_to_read_ == 0) {
+ ret_ = device_->WriteOkay("Read 0 bytes");
+ return false;
+ }
+ if (total_size_to_read_ > maximum_total_size_to_read) {
+ ret_ = device_->WriteFail(android::base::StringPrintf(
+ "Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64
+ " and fetching from offset 0x%" PRIx64,
+ total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_));
+ return false;
+ }
+
+ if (total_size_to_read_ > kMaxFetchSizeDefault) {
+ ret_ = device_->WriteFail(android::base::StringPrintf(
+ "Cannot fetch 0x%" PRIx64
+ " bytes because it exceeds maximum transport size 0x%x",
+ partition_size_, kMaxDownloadSizeDefault));
+ return false;
+ }
+
+ return true;
+ }
+
+ // Assume Open() returns true.
+ // After execution, ret_ is set to the value that FetchHandler should return.
+ void Fetch() {
+ CHECK(start_offset_ <= std::numeric_limits<off64_t>::max());
+ if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast<off64_t>(start_offset_)) {
+ ret_ = device_->WriteFail(android::base::StringPrintf(
+ "On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(),
+ start_offset_, strerror(errno)));
+ return;
+ }
+
+ if (!device_->WriteStatus(FastbootResult::DATA,
+ android::base::StringPrintf(
+ "%08x", static_cast<uint32_t>(total_size_to_read_)))) {
+ ret_ = false;
+ return;
+ }
+ uint64_t end_offset = start_offset_ + total_size_to_read_;
+ std::vector<char> buf(1_MiB);
+ uint64_t current_offset = start_offset_;
+ while (current_offset < end_offset) {
+ // On any error, exit. We can't return a status message to the driver because
+ // we are in the middle of writing data, so just let the driver guess what's wrong
+ // by ending the data stream prematurely.
+ uint64_t remaining = end_offset - current_offset;
+ uint64_t chunk_size = std::min<uint64_t>(buf.size(), remaining);
+ if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) {
+ PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from "
+ << partition_name_ << " @ offset 0x" << current_offset;
+ ret_ = false;
+ return;
+ }
+ if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) {
+ PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of "
+ << partition_name_ << " @ offset 0x" << current_offset;
+ ret_ = false;
+ return;
+ }
+ current_offset += chunk_size;
+ }
+
+ ret_ = device_->WriteOkay(android::base::StringPrintf(
+ "Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(),
+ start_offset_, total_size_to_read_));
+ }
+
+ static constexpr std::array<const char*, 3> kAllowedPartitions{
+ "vendor_boot",
+ "vendor_boot_a",
+ "vendor_boot_b",
+ };
+
+ FastbootDevice* device_;
+ const std::vector<std::string>* args_ = nullptr;
+ std::string partition_name_;
+ PartitionHandle handle_;
+ uint64_t partition_size_ = 0;
+ uint64_t start_offset_ = 0;
+ uint64_t total_size_to_read_ = 0;
+
+ // What FetchHandler should return.
+ std::optional<bool> ret_ = std::nullopt;
+};
+} // namespace
+
+bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+ return PartitionFetcher::Fetch(device, args);
+}
diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h
index c1324bc..345ae1a 100644
--- a/fastboot/device/commands.h
+++ b/fastboot/device/commands.h
@@ -20,6 +20,7 @@
#include <vector>
constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000;
+constexpr unsigned int kMaxFetchSizeDefault = 0x10000000;
class FastbootDevice;
@@ -50,3 +51,4 @@
bool OemCmdHandler(FastbootDevice* device, const std::vector<std::string>& args);
bool GsiHandler(FastbootDevice* device, const std::vector<std::string>& args);
bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args);
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 35f3de0..64a934d 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -61,6 +61,7 @@
{FB_CMD_OEM, OemCmdHandler},
{FB_CMD_GSI, GsiHandler},
{FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler},
+ {FB_CMD_FETCH, FetchHandler},
}),
boot_control_hal_(IBootControl::getService()),
health_hal_(get_health_service()),
@@ -137,14 +138,18 @@
}
bool FastbootDevice::HandleData(bool read, std::vector<char>* data) {
- auto read_write_data_size = read ? this->get_transport()->Read(data->data(), data->size())
- : this->get_transport()->Write(data->data(), data->size());
+ return HandleData(read, data->data(), data->size());
+}
+
+bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) {
+ auto read_write_data_size = read ? this->get_transport()->Read(data, size)
+ : this->get_transport()->Write(data, size);
if (read_write_data_size == -1) {
LOG(ERROR) << (read ? "read from" : "write to") << " transport failed";
return false;
}
- if (static_cast<size_t>(read_write_data_size) != data->size()) {
- LOG(ERROR) << (read ? "read" : "write") << " expected " << data->size() << " bytes, got "
+ if (static_cast<size_t>(read_write_data_size) != size) {
+ LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got "
<< read_write_data_size;
return false;
}
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 23be721..3536136 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -40,6 +40,7 @@
void ExecuteCommands();
bool WriteStatus(FastbootResult result, const std::string& message);
bool HandleData(bool read, std::vector<char>* data);
+ bool HandleData(bool read, char* data, uint64_t size);
std::string GetCurrentSlot();
// Shortcuts for writing status results.
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 7c6ac89..f9267e0 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -77,7 +77,8 @@
} // namespace
-bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle) {
+bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
+ bool read) {
// We prioritize logical partitions over physical ones, and do this
// consistently for other partition operations (like getvar:partition-size).
if (LogicalPartitionExists(device, name)) {
@@ -89,7 +90,9 @@
return false;
}
- unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), O_WRONLY | O_EXCL)));
+ int flags = (read ? O_RDONLY : O_WRONLY);
+ flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
+ unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
if (fd < 0) {
PLOG(ERROR) << "Failed to open block device: " << handle->path();
return false;
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 3b71ef0..c2646d7 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -75,7 +75,11 @@
std::optional<std::string> FindPhysicalPartition(const std::string& name);
bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
bool* is_zero_length = nullptr);
-bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle);
+
+// If read, partition is readonly. Else it is write only.
+bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
+ bool read = false);
+
bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
std::vector<std::string> ListPartitions(FastbootDevice* device);
bool GetDeviceLockStatus();
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index e7d8bc3..ee1eed8 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -33,6 +33,12 @@
#include "flashing.h"
#include "utility.h"
+#ifdef FB_ENABLE_FETCH
+static constexpr bool kEnableFetch = true;
+#else
+static constexpr bool kEnableFetch = false;
+#endif
+
using ::android::hardware::boot::V1_0::BoolResult;
using ::android::hardware::boot::V1_0::Slot;
using ::android::hardware::boot::V1_1::MergeStatus;
@@ -509,3 +515,13 @@
*message = android::base::GetProperty("ro.treble.enabled", "");
return true;
}
+
+bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+ std::string* message) {
+ if (!kEnableFetch) {
+ *message = "fetch not supported on user builds";
+ return false;
+ }
+ *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault);
+ return true;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index c11e472..f40a025 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -80,6 +80,8 @@
std::string* message);
bool GetTrebleEnabled(FastbootDevice* device, const std::vector<std::string>& args,
std::string* message);
+bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+ std::string* message);
// Helpers for getvar all.
std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 38be934..94efeea 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -76,12 +76,15 @@
#include "udp.h"
#include "usb.h"
#include "util.h"
+#include "vendor_boot_img_utils.h"
+using android::base::borrowed_fd;
using android::base::ReadFully;
using android::base::Split;
using android::base::Trim;
using android::base::unique_fd;
using namespace std::string_literals;
+using namespace std::placeholders;
static const char* serial = nullptr;
@@ -114,7 +117,7 @@
enum fb_buffer_type type;
void* data;
int64_t sz;
- int fd;
+ unique_fd fd;
int64_t image_size;
};
@@ -414,6 +417,7 @@
" snapshot-update merge On devices that support snapshot-based updates, finish\n"
" an in-progress update if it is in the \"merging\"\n"
" phase.\n"
+ " fetch PARTITION Fetch a partition image from the device."
"\n"
"boot image:\n"
" boot KERNEL [RAMDISK [SECOND]]\n"
@@ -466,7 +470,7 @@
" --version Display version.\n"
" --help, -h Show this message.\n"
);
- // clang-format off
+ // clang-format on
return 0;
}
@@ -640,14 +644,14 @@
}
}
-static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
+static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
unique_fd fd(make_temporary_fd(entry_name));
ZipEntry64 zip_entry;
if (FindEntry(zip, entry_name, &zip_entry) != 0) {
fprintf(stderr, "archive does not contain '%s'\n", entry_name);
errno = ENOENT;
- return -1;
+ return unique_fd();
}
fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name,
@@ -664,7 +668,7 @@
fprintf(stderr, " took %.3fs\n", now() - start);
- return fd.release();
+ return fd;
}
static void CheckRequirement(const std::string& cur_product, const std::string& var,
@@ -851,24 +855,23 @@
return out_s;
}
-static int64_t get_target_sparse_limit() {
- std::string max_download_size;
- if (fb->GetVar("max-download-size", &max_download_size) != fastboot::SUCCESS ||
- max_download_size.empty()) {
- verbose("target didn't report max-download-size");
+static uint64_t get_uint_var(const char* var_name) {
+ std::string value_str;
+ if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
+ verbose("target didn't report %s", var_name);
return 0;
}
// Some bootloaders (angler, for example) send spurious whitespace too.
- max_download_size = android::base::Trim(max_download_size);
+ value_str = android::base::Trim(value_str);
- uint64_t limit;
- if (!android::base::ParseUint(max_download_size, &limit)) {
- fprintf(stderr, "couldn't parse max-download-size '%s'\n", max_download_size.c_str());
+ uint64_t value;
+ if (!android::base::ParseUint(value_str, &value)) {
+ fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str());
return 0;
}
- if (limit > 0) verbose("target reported max download size of %" PRId64 " bytes", limit);
- return limit;
+ if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value);
+ return value;
}
static int64_t get_sparse_limit(int64_t size) {
@@ -877,7 +880,7 @@
// Unlimited, so see what the target device's limit is.
// TODO: shouldn't we apply this limit even if you've used -S?
if (target_sparse_limit == -1) {
- target_sparse_limit = get_target_sparse_limit();
+ target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size"));
}
if (target_sparse_limit > 0) {
limit = target_sparse_limit;
@@ -893,7 +896,7 @@
return 0;
}
-static bool load_buf_fd(int fd, struct fastboot_buffer* buf) {
+static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) {
int64_t sz = get_file_size(fd);
if (sz == -1) {
return false;
@@ -918,7 +921,7 @@
} else {
buf->type = FB_BUFFER_FD;
buf->data = nullptr;
- buf->fd = fd;
+ buf->fd = std::move(fd);
buf->sz = sz;
}
@@ -941,7 +944,7 @@
return false;
}
- return load_buf_fd(fd.release(), buf);
+ return load_buf_fd(std::move(fd), buf);
}
static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) {
@@ -987,12 +990,11 @@
data[flags_offset] |= 0x02;
}
- int fd = make_temporary_fd("vbmeta rewriting");
+ unique_fd fd(make_temporary_fd("vbmeta rewriting"));
if (!android::base::WriteStringToFd(data, fd)) {
die("Failed writing to modified vbmeta");
}
- close(buf->fd);
- buf->fd = fd;
+ buf->fd = std::move(fd);
lseek(fd, 0, SEEK_SET);
}
@@ -1012,21 +1014,28 @@
return var;
}
+static uint64_t get_partition_size(const std::string& partition) {
+ std::string partition_size_str;
+ if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
+ die("cannot get partition size for %s", partition.c_str());
+ }
+
+ partition_size_str = fb_fix_numeric_var(partition_size_str);
+ uint64_t partition_size;
+ if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+ die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
+ }
+ return partition_size;
+}
+
static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
if (buf->sz < AVB_FOOTER_SIZE) {
return;
}
- std::string partition_size_str;
- if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
- die("cannot get boot partition size");
- }
+ // If overflows and negative, it should be < buf->sz.
+ int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
- partition_size_str = fb_fix_numeric_var(partition_size_str);
- int64_t partition_size;
- if (!android::base::ParseInt(partition_size_str, &partition_size)) {
- die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
- }
if (partition_size == buf->sz) {
return;
}
@@ -1044,7 +1053,7 @@
return;
}
- int fd = make_temporary_fd("boot rewriting");
+ unique_fd fd(make_temporary_fd("boot rewriting"));
if (!android::base::WriteStringToFd(data, fd)) {
die("Failed writing to modified boot");
}
@@ -1052,8 +1061,7 @@
if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) {
die("Failed copying AVB footer in boot");
}
- close(buf->fd);
- buf->fd = fd;
+ buf->fd = std::move(fd);
buf->sz = partition_size;
lseek(fd, 0, SEEK_SET);
}
@@ -1185,8 +1193,10 @@
const std::function<void(const std::string&)>& func, bool force_slot) {
std::string has_slot;
std::string current_slot;
+ // |part| can be vendor_boot:default. Append slot to the first token.
+ auto part_tokens = android::base::Split(part, ":");
- if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
+ if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
/* If has-slot is not supported, the answer is no. */
has_slot = "no";
}
@@ -1196,14 +1206,15 @@
if (current_slot == "") {
die("Failed to identify current slot");
}
- func(part + "_" + current_slot);
+ part_tokens[0] += "_" + current_slot;
} else {
- func(part + '_' + slot);
+ part_tokens[0] += "_" + slot;
}
+ func(android::base::Join(part_tokens, ":"));
} else {
if (force_slot && slot != "") {
- fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
- part.c_str(), slot.c_str());
+ fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
+ part_tokens[0].c_str(), slot.c_str());
}
func(part);
}
@@ -1217,10 +1228,13 @@
static void do_for_partitions(const std::string& part, const std::string& slot,
const std::function<void(const std::string&)>& func, bool force_slot) {
std::string has_slot;
+ // |part| can be vendor_boot:default. Query has-slot on the first token only.
+ auto part_tokens = android::base::Split(part, ":");
if (slot == "all") {
- if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
- die("Could not check if partition %s has slot %s", part.c_str(), slot.c_str());
+ if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
+ die("Could not check if partition %s has slot %s", part_tokens[0].c_str(),
+ slot.c_str());
}
if (has_slot == "yes") {
for (int i=0; i < get_slot_count(); i++) {
@@ -1247,7 +1261,74 @@
return android::base::StartsWith(value, "system_");
}
+// Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch
+// the full image.
+static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) {
+ uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE);
+ if (fetch_size == 0) {
+ die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE);
+ }
+ uint64_t partition_size = get_partition_size(partition);
+ if (partition_size <= 0) {
+ die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size);
+ }
+
+ uint64_t offset = 0;
+ while (offset < partition_size) {
+ uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
+ if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) {
+ die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(),
+ offset, chunk_size);
+ }
+ offset += chunk_size;
+ }
+ return partition_size;
+}
+
+static void do_fetch(const std::string& partition, const std::string& slot_override,
+ const std::string& outfile) {
+ unique_fd fd(TEMP_FAILURE_RETRY(
+ open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644)));
+ auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd));
+ do_for_partitions(partition, slot_override, fetch, false /* force slot */);
+}
+
+// Return immediately if not flashing a vendor boot image. If flashing a vendor boot image,
+// repack vendor_boot image with an updated ramdisk. After execution, buf is set
+// to the new image to flash, and return value is the real partition name to flash.
+static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) {
+ std::string_view pname_sv{pname};
+
+ if (!android::base::StartsWith(pname_sv, "vendor_boot:") &&
+ !android::base::StartsWith(pname_sv, "vendor_boot_a:") &&
+ !android::base::StartsWith(pname_sv, "vendor_boot_b:")) {
+ return std::string(pname_sv);
+ }
+ if (buf->type != FB_BUFFER_FD) {
+ die("Flashing sparse vendor ramdisk image is not supported.");
+ }
+ if (buf->sz <= 0) {
+ die("repack_ramdisk() sees negative size: %" PRId64, buf->sz);
+ }
+ std::string partition(pname_sv.substr(0, pname_sv.find(':')));
+ std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1));
+
+ unique_fd vendor_boot(make_temporary_fd("vendor boot repack"));
+ uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot);
+ auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd,
+ static_cast<uint64_t>(buf->sz));
+ if (!repack_res.ok()) {
+ die("%s", repack_res.error().message().c_str());
+ }
+
+ buf->fd = std::move(vendor_boot);
+ buf->sz = vendor_boot_size;
+ buf->image_size = vendor_boot_size;
+ return partition;
+}
+
static void do_flash(const char* pname, const char* fname) {
+ verbose("Do flash %s %s", pname, fname);
struct fastboot_buffer buf;
if (!load_buf(fname, &buf)) {
@@ -1256,7 +1337,8 @@
if (is_logical(pname)) {
fb->ResizePartition(pname, std::to_string(buf.image_size));
}
- flash_buf(pname, &buf);
+ std::string flash_pname = repack_ramdisk(pname, &buf);
+ flash_buf(flash_pname, &buf);
}
// Sets slot_override as the active slot. If slot_override is blank,
@@ -1310,7 +1392,7 @@
class ImageSource {
public:
virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
- virtual int OpenFile(const std::string& name) const = 0;
+ virtual unique_fd OpenFile(const std::string& name) const = 0;
};
class FlashAllTool {
@@ -1428,8 +1510,8 @@
void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
for (const auto& [image, slot] : images) {
fastboot_buffer buf;
- int fd = source_.OpenFile(image->img_name);
- if (fd < 0 || !load_buf_fd(fd, &buf)) {
+ unique_fd fd = source_.OpenFile(image->img_name);
+ if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
if (image->optional_if_no_image) {
continue;
}
@@ -1494,7 +1576,7 @@
public:
explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
bool ReadFile(const std::string& name, std::vector<char>* out) const override;
- int OpenFile(const std::string& name) const override;
+ unique_fd OpenFile(const std::string& name) const override;
private:
ZipArchiveHandle zip_;
@@ -1504,7 +1586,7 @@
return UnzipToMemory(zip_, name, out);
}
-int ZipImageSource::OpenFile(const std::string& name) const {
+unique_fd ZipImageSource::OpenFile(const std::string& name) const {
return unzip_to_file(zip_, name.c_str());
}
@@ -1524,7 +1606,7 @@
class LocalImageSource final : public ImageSource {
public:
bool ReadFile(const std::string& name, std::vector<char>* out) const override;
- int OpenFile(const std::string& name) const override;
+ unique_fd OpenFile(const std::string& name) const override;
};
bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) const {
@@ -1535,9 +1617,9 @@
return ReadFileToVector(path, out);
}
-int LocalImageSource::OpenFile(const std::string& name) const {
+unique_fd LocalImageSource::OpenFile(const std::string& name) const {
auto path = find_item_given_name(name);
- return open(path.c_str(), O_RDONLY | O_BINARY);
+ return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
}
static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
@@ -1656,7 +1738,7 @@
if (fd == -1) {
die("Cannot open generated image: %s", strerror(errno));
}
- if (!load_buf_fd(fd.release(), &buf)) {
+ if (!load_buf_fd(std::move(fd), &buf)) {
die("Cannot read image: %s", strerror(errno));
}
flash_buf(partition, &buf);
@@ -2169,6 +2251,10 @@
syntax_error("expected: snapshot-update [cancel|merge]");
}
fb->SnapshotUpdateCommand(arg);
+ } else if (command == FB_CMD_FETCH) {
+ std::string partition = next_arg(&args);
+ std::string outfile = next_arg(&args);
+ do_fetch(partition, slot_override, outfile);
} else {
syntax_error("unknown command %s", command.c_str());
}
diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp
index 8d534ea..14ee785 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -30,6 +30,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -42,14 +43,17 @@
#include <android-base/file.h>
#include <android-base/mapped_file.h>
+#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <storage_literals/storage_literals.h>
#include "constants.h"
#include "transport.h"
using android::base::StringPrintf;
+using namespace android::storage_literals;
namespace fastboot {
@@ -297,41 +301,85 @@
return result;
}
-RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
- std::vector<std::string>* info) {
+// This function executes cmd, then expect a "DATA" response with a number N, followed
+// by N bytes, and another response.
+// This is the common way for the device to send data to the driver used by upload and fetch.
+RetCode FastBootDriver::RunAndReadBuffer(
+ const std::string& cmd, std::string* response, std::vector<std::string>* info,
+ const std::function<RetCode(const char* data, uint64_t size)>& write_fn) {
RetCode ret;
int dsize = 0;
- if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) {
- error_ = "Upload request failed: " + error_;
+ if ((ret = RawCommand(cmd, response, info, &dsize))) {
+ error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str());
return ret;
}
- if (!dsize) {
- error_ = "Upload request failed, device reports 0 bytes available";
+ if (dsize <= 0) {
+ error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available",
+ cmd.c_str(), dsize);
return BAD_DEV_RESP;
}
- std::vector<char> data;
- data.resize(dsize);
-
- if ((ret = ReadBuffer(data))) {
- return ret;
+ const uint64_t total_size = dsize;
+ const uint64_t buf_size = std::min<uint64_t>(total_size, 1_MiB);
+ std::vector<char> data(buf_size);
+ uint64_t current_offset = 0;
+ while (current_offset < total_size) {
+ uint64_t remaining = total_size - current_offset;
+ uint64_t chunk_size = std::min(buf_size, remaining);
+ if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) {
+ return ret;
+ }
+ if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) {
+ return ret;
+ }
+ current_offset += chunk_size;
}
+ return HandleResponse(response, info);
+}
+RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
+ std::vector<std::string>* info) {
std::ofstream ofs;
ofs.open(outfile, std::ofstream::out | std::ofstream::binary);
if (ofs.fail()) {
error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str());
return IO_ERROR;
}
- ofs.write(data.data(), data.size());
- if (ofs.fail() || ofs.bad()) {
- error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
- return IO_ERROR;
- }
+ auto write_fn = [&](const char* data, uint64_t size) {
+ ofs.write(data, size);
+ if (ofs.fail() || ofs.bad()) {
+ error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
+ return IO_ERROR;
+ }
+ return SUCCESS;
+ };
+ RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn);
ofs.close();
+ return ret;
+}
- return HandleResponse(response, info);
+RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+ int64_t offset, int64_t size, std::string* response,
+ std::vector<std::string>* info) {
+ prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")",
+ partition.c_str(), offset, size));
+ std::string cmd = FB_CMD_FETCH ":" + partition;
+ if (offset >= 0) {
+ cmd += android::base::StringPrintf(":0x%08" PRIx64, offset);
+ if (size >= 0) {
+ cmd += android::base::StringPrintf(":0x%08" PRIx64, size);
+ }
+ }
+ RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) {
+ if (!android::base::WriteFully(fd, data, size)) {
+ error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno));
+ return IO_ERROR;
+ }
+ return SUCCESS;
+ });
+ epilog_(ret);
+ return ret;
}
// Helpers
@@ -524,11 +572,6 @@
return SUCCESS;
}
-RetCode FastBootDriver::ReadBuffer(std::vector<char>& buf) {
- // Read the buffer
- return ReadBuffer(buf.data(), buf.size());
-}
-
RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) {
// Read the buffer
ssize_t tmp = transport_->Read(buf, size);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index 7265632..f1c094f 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -34,6 +34,7 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
#include <bootimg.h>
#include <inttypes.h>
#include <sparse/sparse.h>
@@ -106,6 +107,9 @@
std::vector<std::string>* info = nullptr);
RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr,
std::vector<std::string>* info = nullptr);
+ RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+ int64_t offset = -1, int64_t size = -1, std::string* response = nullptr,
+ std::vector<std::string>* info = nullptr);
/* HIGHER LEVEL COMMANDS -- Composed of the commands above */
RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
@@ -149,11 +153,13 @@
RetCode SendBuffer(const std::vector<char>& buf);
RetCode SendBuffer(const void* buf, size_t size);
- RetCode ReadBuffer(std::vector<char>& buf);
RetCode ReadBuffer(void* buf, size_t size);
RetCode UploadInner(const std::string& outfile, std::string* response = nullptr,
std::vector<std::string>* info = nullptr);
+ RetCode RunAndReadBuffer(const std::string& cmd, std::string* response,
+ std::vector<std::string>* info,
+ const std::function<RetCode(const char*, uint64_t)>& write_fn);
int SparseWriteCallback(std::vector<char>& tpbuf, const char* data, size_t len);
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
index 34ab32c..b6beaf9 100644
--- a/fastboot/fuzzy_fastboot/main.cpp
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -43,8 +43,10 @@
#include <thread>
#include <vector>
+#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <gtest/gtest.h>
#include <sparse/sparse.h>
@@ -349,22 +351,35 @@
EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'";
}
+void AssertHexUint32(const std::string& name, const std::string& var) {
+ ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string";
+ // This must start with 0x
+ ASSERT_FALSE(isspace(var.front()))
+ << "getvar:" << name << " responded with a string with leading whitespace";
+ ASSERT_FALSE(var.compare(0, 2, "0x"))
+ << "getvar:" << name << " responded with a string that does not start with 0x...";
+ int64_t size = strtoll(var.c_str(), nullptr, 16);
+ ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name;
+ // At most 32-bits
+ ASSERT_LE(size, std::numeric_limits<uint32_t>::max())
+ << "getvar:" << name << " must fit in a uint32_t";
+ ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4)
+ << "getvar:" << name << " responded with too large of string: " + var;
+}
+
TEST_F(Conformance, GetVarDownloadSize) {
std::string var;
EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
- EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string";
- // This must start with 0x
- EXPECT_FALSE(isspace(var.front()))
- << "getvar:max-download-size responded with a string with leading whitespace";
- EXPECT_FALSE(var.compare(0, 2, "0x"))
- << "getvar:max-download-size responded with a string that does not start with 0x...";
- int64_t size = strtoll(var.c_str(), nullptr, 16);
- EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size";
- // At most 32-bits
- EXPECT_LE(size, std::numeric_limits<uint32_t>::max())
- << "getvar:max-download-size must fit in a uint32_t";
- EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4)
- << "getvar:max-download-size responded with too large of string: " + var;
+ AssertHexUint32("max-download-size", var);
+}
+
+// If fetch is supported, getvar:max-fetch-size must return a hex string.
+TEST_F(Conformance, GetVarFetchSize) {
+ std::string var;
+ if (SUCCESS != fb->GetVar("max-fetch-size", &var)) {
+ GTEST_SKIP() << "getvar:max-fetch-size failed";
+ }
+ AssertHexUint32("max-fetch-size", var);
}
TEST_F(Conformance, GetVarAll) {
@@ -656,6 +671,33 @@
EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode";
}
+// If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*.
+TEST_F(UnlockPermissions, FetchVendorBoot) {
+ std::string var;
+ uint64_t fetch_size;
+ if (fb->GetVar("max-fetch-size", &var) != SUCCESS) {
+ GTEST_SKIP() << "This test is skipped because fetch is not supported.";
+ }
+ ASSERT_FALSE(var.empty());
+ ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer";
+ std::vector<std::tuple<std::string, uint64_t>> parts;
+ EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+ for (const auto& [partition, partition_size] : parts) {
+ if (!android::base::StartsWith(partition, "vendor_boot")) continue;
+ TemporaryFile fetched;
+
+ uint64_t offset = 0;
+ while (offset < partition_size) {
+ uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
+ auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size);
+ ASSERT_EQ(fastboot::RetCode::SUCCESS, ret)
+ << "Unable to fetch " << partition << " (offset=" << offset
+ << ", size=" << chunk_size << ")";
+ offset += chunk_size;
+ }
+ }
+}
+
TEST_F(LockPermissions, DownloadFlash) {
std::vector<char> buf{'a', 'o', 's', 'p'};
EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode";
@@ -717,6 +759,16 @@
EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL";
}
+TEST_F(LockPermissions, FetchVendorBoot) {
+ std::vector<std::tuple<std::string, uint64_t>> parts;
+ EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+ for (const auto& [partition, _] : parts) {
+ TemporaryFile fetched;
+ ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL)
+ << "fetch:" << partition << ":0:0 did not fail in locked mode";
+ }
+}
+
TEST_F(Fuzz, DownloadSize) {
std::string var;
EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
diff --git a/fastboot/testdata/Android.bp b/fastboot/testdata/Android.bp
new file mode 100644
index 0000000..5debf5e
--- /dev/null
+++ b/fastboot/testdata/Android.bp
@@ -0,0 +1,136 @@
+// Copyright (C) 2021 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.
+
+python_binary_host {
+ name: "fastboot_gen_rand",
+ visibility: [":__subpackages__"],
+ srcs: ["fastboot_gen_rand.py"],
+}
+
+genrule_defaults {
+ name: "fastboot_test_data_gen_defaults",
+ visibility: ["//system/core/fastboot"],
+ tools: [
+ "fastboot_gen_rand",
+ ],
+}
+
+// Genrules for components of test vendor boot image.
+
+// Fake dtb image.
+genrule {
+ name: "fastboot_test_dtb",
+ defaults: ["fastboot_test_data_gen_defaults"],
+ out: ["test_dtb.img"],
+ cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)",
+}
+
+// Fake bootconfig image.
+genrule {
+ name: "fastboot_test_bootconfig",
+ defaults: ["fastboot_test_data_gen_defaults"],
+ out: ["test_bootconfig.img"],
+ cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)",
+}
+
+// Fake vendor ramdisk with type "none".
+genrule {
+ name: "fastboot_test_vendor_ramdisk_none",
+ defaults: ["fastboot_test_data_gen_defaults"],
+ out: ["test_vendor_ramdisk_none.img"],
+ cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)",
+}
+
+// Fake vendor ramdisk with type "platform".
+genrule {
+ name: "fastboot_test_vendor_ramdisk_platform",
+ defaults: ["fastboot_test_data_gen_defaults"],
+ out: ["test_vendor_ramdisk_platform.img"],
+ cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)",
+}
+
+// Fake replacement ramdisk.
+genrule {
+ name: "fastboot_test_vendor_ramdisk_replace",
+ defaults: ["fastboot_test_data_gen_defaults"],
+ out: ["test_vendor_ramdisk_replace.img"],
+ cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)",
+}
+
+// Genrules for test vendor boot images.
+
+fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " +
+ "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))"
+
+genrule_defaults {
+ name: "fastboot_test_vendor_boot_gen_defaults",
+ defaults: ["fastboot_test_data_gen_defaults"],
+ tools: [
+ "avbtool",
+ "mkbootimg",
+ ],
+}
+
+genrule {
+ name: "fastboot_test_vendor_boot_v3",
+ defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+ out: ["vendor_boot_v3.img"],
+ srcs: [
+ ":fastboot_test_dtb",
+ ":fastboot_test_vendor_ramdisk_none",
+ ],
+ cmd: "$(location mkbootimg) --header_version 3 " +
+ "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
+ "--dtb $(location :fastboot_test_dtb) " +
+ "--vendor_boot $(out) && " +
+ fastboot_sign_test_image,
+}
+
+genrule {
+ name: "fastboot_test_vendor_boot_v4_without_frag",
+ defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+ out: ["vendor_boot_v4_without_frag.img"],
+ srcs: [
+ ":fastboot_test_dtb",
+ ":fastboot_test_vendor_ramdisk_none",
+ ":fastboot_test_bootconfig",
+ ],
+ cmd: "$(location mkbootimg) --header_version 4 " +
+ "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
+ "--dtb $(location :fastboot_test_dtb) " +
+ "--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
+ "--vendor_boot $(out) && " +
+ fastboot_sign_test_image,
+}
+
+genrule {
+ name: "fastboot_test_vendor_boot_v4_with_frag",
+ defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+ out: ["vendor_boot_v4_with_frag.img"],
+ srcs: [
+ ":fastboot_test_dtb",
+ ":fastboot_test_vendor_ramdisk_none",
+ ":fastboot_test_vendor_ramdisk_platform",
+ ":fastboot_test_bootconfig",
+ ],
+ cmd: "$(location mkbootimg) --header_version 4 " +
+ "--dtb $(location :fastboot_test_dtb) " +
+ "--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
+ "--ramdisk_type none --ramdisk_name none_ramdisk " +
+ "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " +
+ "--ramdisk_type platform --ramdisk_name platform_ramdisk " +
+ "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " +
+ "--vendor_boot $(out) && " +
+ fastboot_sign_test_image,
+}
diff --git a/fastboot/testdata/fastboot_gen_rand.py b/fastboot/testdata/fastboot_gen_rand.py
new file mode 100644
index 0000000..a87467b
--- /dev/null
+++ b/fastboot/testdata/fastboot_gen_rand.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2021 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.
+
+"""
+Write given number of random bytes, generated with optional seed.
+"""
+
+import random, argparse
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--seed', help='Seed to random generator')
+ parser.add_argument('--length', type=int, required=True, help='Length of output')
+ args = parser.parse_args()
+
+ if args.seed:
+ random.seed(args.seed)
+
+ print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length)))
diff --git a/fastboot/vendor_boot_img_utils.cpp b/fastboot/vendor_boot_img_utils.cpp
new file mode 100644
index 0000000..2db20cd
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils.cpp
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2021 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 "vendor_boot_img_utils.h"
+
+#include <string.h>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <bootimg.h>
+#include <libavb/libavb.h>
+
+namespace {
+
+using android::base::Result;
+
+// Updates a given buffer by creating a new one.
+class DataUpdater {
+ public:
+ DataUpdater(const std::string& old_data) : old_data_(&old_data) {
+ old_data_ptr_ = old_data_->data();
+ new_data_.resize(old_data_->size(), '\0');
+ new_data_ptr_ = new_data_.data();
+ }
+ // Copy |num_bytes| from src to dst.
+ [[nodiscard]] Result<void> Copy(uint32_t num_bytes) {
+ if (num_bytes == 0) return {};
+ if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok())
+ return res;
+ if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok())
+ return res;
+ memcpy(new_data_ptr_, old_data_ptr_, num_bytes);
+ old_data_ptr_ += num_bytes;
+ new_data_ptr_ += num_bytes;
+ return {};
+ }
+ // Replace |old_num_bytes| from src with new data.
+ [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const std::string& new_data) {
+ return Replace(old_num_bytes, new_data.data(), new_data.size());
+ }
+ [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const void* new_data,
+ uint32_t new_data_size) {
+ if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__);
+ !res.ok())
+ return res;
+ old_data_ptr_ += old_num_bytes;
+
+ if (new_data_size == 0) return {};
+ if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__);
+ !res.ok())
+ return res;
+ memcpy(new_data_ptr_, new_data, new_data_size);
+ new_data_ptr_ += new_data_size;
+ return {};
+ }
+ // Skip |old_skip| from src and |new_skip| from dst, respectively.
+ [[nodiscard]] Result<void> Skip(uint32_t old_skip, uint32_t new_skip) {
+ if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok())
+ return res;
+ old_data_ptr_ += old_skip;
+ if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok())
+ return res;
+ new_data_ptr_ += new_skip;
+ return {};
+ }
+
+ [[nodiscard]] Result<void> Seek(uint32_t offset) {
+ if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size());
+ old_data_ptr_ = old_begin() + offset;
+ new_data_ptr_ = new_begin() + offset;
+ return {};
+ }
+
+ std::string Finish() {
+ new_data_ptr_ = nullptr;
+ return std::move(new_data_);
+ }
+
+ [[nodiscard]] Result<void> CheckOffset(uint32_t old_offset, uint32_t new_offset) {
+ if (old_begin() + old_offset != old_cur())
+ return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset,
+ old_cur() - old_begin());
+ if (new_begin() + new_offset != new_cur())
+ return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset,
+ new_cur() - new_begin());
+ return {};
+ }
+
+ uint64_t size() const { return old_data_->size(); }
+ const char* old_begin() const { return old_data_->data(); }
+ const char* old_cur() { return old_data_ptr_; }
+ const char* old_end() const { return old_data_->data() + old_data_->size(); }
+ char* new_begin() { return new_data_.data(); }
+ char* new_cur() { return new_data_ptr_; }
+ char* new_end() { return new_data_.data() + new_data_.size(); }
+
+ private:
+ // Check if it is okay to advance |num_bytes| from |current|.
+ [[nodiscard]] Result<void> CheckAdvance(const char* current, const char* end,
+ uint32_t num_bytes, const char* op) {
+ auto new_end = current + num_bytes;
+ if (new_end < current /* add overflow */)
+ return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current),
+ num_bytes, fmt::ptr(current));
+ if (new_end > end)
+ return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current),
+ num_bytes, fmt::ptr(end));
+ return {};
+ }
+ const std::string* old_data_;
+ std::string new_data_;
+ const char* old_data_ptr_;
+ char* new_data_ptr_;
+};
+
+// Get the size of vendor boot header.
+[[nodiscard]] Result<uint32_t> get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) {
+ if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3);
+ if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4);
+ return Errorf("Unrecognized vendor boot header version {}", hdr->header_version);
+}
+
+// Check that content contains a valid vendor boot image header with a version at least |version|.
+[[nodiscard]] Result<void> check_vendor_boot_hdr(const std::string& content, uint32_t version) {
+ // get_vendor_boot_header_size reads header_version, so make sure reading it does not
+ // go out of bounds by ensuring that the content has at least the size of V3 header.
+ if (content.size() < sizeof(vendor_boot_img_hdr_v3)) {
+ return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}",
+ content.size(), sizeof(vendor_boot_img_hdr_v3));
+ }
+ // Now read hdr->header_version and assert the size.
+ auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(content.data());
+ auto expect_header_size = get_vendor_boot_header_size(hdr);
+ if (!expect_header_size.ok()) return expect_header_size.error();
+ if (content.size() < *expect_header_size) {
+ return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}",
+ content.size(), version, *expect_header_size);
+ }
+ if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) {
+ return Errorf("Vendor boot image magic mismatch");
+ }
+ if (hdr->header_version < version) {
+ return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version);
+ }
+ return {};
+}
+
+// Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string.
+[[nodiscard]] Result<std::string> load_file(android::base::borrowed_fd fd, uint64_t expected_size,
+ const char* what) {
+ if (lseek(fd.get(), 0, SEEK_SET) != 0) {
+ return ErrnoErrorf("Can't seek to the beginning of {} image", what);
+ }
+ std::string content;
+ if (!android::base::ReadFdToString(fd, &content)) {
+ return ErrnoErrorf("Cannot read {} to string", what);
+ }
+ if (content.size() != expected_size) {
+ return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what,
+ expected_size, content.size());
+ }
+ return content;
+}
+
+// Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string.
+[[nodiscard]] Result<void> store_file(android::base::borrowed_fd fd, const std::string& data,
+ const char* what) {
+ if (lseek(fd.get(), 0, SEEK_SET) != 0) {
+ return ErrnoErrorf("Cannot seek to beginning of {} before writing", what);
+ }
+ if (!android::base::WriteStringToFd(data, fd)) {
+ return ErrnoErrorf("Cannot write new content to {}", what);
+ }
+ if (TEMP_FAILURE_RETRY(ftruncate64(fd.get(), data.size())) == -1) {
+ return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size());
+ }
+ return {};
+}
+
+// Copy AVB footer if it exists in the old buffer.
+[[nodiscard]] Result<void> copy_avb_footer(DataUpdater* updater) {
+ if (updater->size() < AVB_FOOTER_SIZE) return {};
+ if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res;
+ if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {};
+ return updater->Copy(AVB_FOOTER_SIZE);
+}
+
+// round |value| up to a multiple of |page_size|.
+inline uint32_t round_up(uint32_t value, uint32_t page_size) {
+ return (value + page_size - 1) / page_size * page_size;
+}
+
+// Replace the vendor ramdisk as a whole.
+[[nodiscard]] Result<std::string> replace_default_vendor_ramdisk(const std::string& vendor_boot,
+ const std::string& new_ramdisk) {
+ if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error();
+ auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(vendor_boot.data());
+ auto hdr_size = get_vendor_boot_header_size(hdr);
+ if (!hdr_size.ok()) return hdr_size.error();
+ // Refer to bootimg.h for details. Numbers are in bytes.
+ const uint32_t o = round_up(*hdr_size, hdr->page_size);
+ const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+ const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
+
+ DataUpdater updater(vendor_boot);
+
+ // Copy header (O bytes), then update fields in header.
+ if (auto res = updater.Copy(o); !res.ok()) return res.error();
+ auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v3*>(updater.new_begin());
+ new_hdr->vendor_ramdisk_size = new_ramdisk.size();
+ // Because it is unknown how the new ramdisk is fragmented, the whole table is replaced
+ // with a single entry representing the full ramdisk.
+ if (new_hdr->header_version >= 4) {
+ auto new_hdr_v4 = static_cast<vendor_boot_img_hdr_v4*>(new_hdr);
+ new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4);
+ new_hdr_v4->vendor_ramdisk_table_entry_num = 1;
+ new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num *
+ new_hdr_v4->vendor_ramdisk_table_entry_size;
+ }
+
+ // Copy the new ramdisk.
+ if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok())
+ return res.error();
+ const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
+ if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
+ !res.ok())
+ return res.error();
+ if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
+
+ // Copy DTB (Q bytes).
+ if (auto res = updater.Copy(q); !res.ok()) return res.error();
+
+ if (new_hdr->header_version >= 4) {
+ auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
+ const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
+ const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
+
+ auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
+ auto new_hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(new_hdr);
+ auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size);
+ if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error();
+ if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + new_r); !res.ok())
+ return res.error();
+
+ // Replace table with single entry representing the full ramdisk.
+ new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size;
+ new_entry->ramdisk_offset = 0;
+ new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE;
+ memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE);
+ memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE);
+
+ // Copy bootconfig (S bytes).
+ if (auto res = updater.Copy(s); !res.ok()) return res.error();
+ }
+
+ if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
+ return updater.Finish();
+}
+
+// Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found.
+[[nodiscard]] Result<const vendor_ramdisk_table_entry_v4*> find_unique_ramdisk(
+ const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table,
+ uint32_t size) {
+ const vendor_ramdisk_table_entry_v4* ret = nullptr;
+ uint32_t idx = 0;
+ const vendor_ramdisk_table_entry_v4* entry = table;
+ for (; idx < size; idx++, entry++) {
+ auto entry_name_c_str = reinterpret_cast<const char*>(entry->ramdisk_name);
+ auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE);
+ std::string_view entry_name(entry_name_c_str, entry_name_len);
+ if (entry_name == ramdisk_name) {
+ if (ret != nullptr) {
+ return Errorf("Multiple vendor ramdisk '{}' found, name should be unique",
+ ramdisk_name.c_str());
+ }
+ ret = entry;
+ }
+ }
+ if (ret == nullptr) {
+ return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str());
+ }
+ return ret;
+}
+
+// Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and
+// replace it with the content of |new_ramdisk|.
+[[nodiscard]] Result<std::string> replace_vendor_ramdisk_fragment(const std::string& ramdisk_name,
+ const std::string& vendor_boot,
+ const std::string& new_ramdisk) {
+ if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error();
+ auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(vendor_boot.data());
+ auto hdr_size = get_vendor_boot_header_size(hdr);
+ if (!hdr_size.ok()) return hdr_size.error();
+ // Refer to bootimg.h for details. Numbers are in bytes.
+ const uint32_t o = round_up(*hdr_size, hdr->page_size);
+ const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+ const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
+ const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
+ const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size);
+
+ if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits<uint32_t>::max()) {
+ return Errorf("Too many vendor ramdisk entries in table, overflow");
+ }
+
+ // Find entry with name |ramdisk_name|.
+ auto old_table_start =
+ reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(vendor_boot.data() + o + p + q);
+ auto find_res =
+ find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num);
+ if (!find_res.ok()) return find_res.error();
+ const vendor_ramdisk_table_entry_v4* replace_entry = *find_res;
+ uint32_t replace_idx = replace_entry - old_table_start;
+
+ // Now reconstruct.
+ DataUpdater updater(vendor_boot);
+
+ // Copy header (O bytes), then update fields in header.
+ if (auto res = updater.Copy(o); !res.ok()) return res.error();
+ auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v4*>(updater.new_begin());
+
+ // Copy ramdisk fragments, replace for the matching index.
+ {
+ auto old_ramdisk_entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
+ vendor_boot.data() + o + p + q);
+ uint32_t new_total_ramdisk_size = 0;
+ for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num;
+ new_ramdisk_idx++, old_ramdisk_entry++) {
+ if (new_ramdisk_idx == replace_idx) {
+ if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok())
+ return res.error();
+ new_total_ramdisk_size += new_ramdisk.size();
+ } else {
+ if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok())
+ return res.error();
+ new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size;
+ }
+ }
+ new_hdr->vendor_ramdisk_size = new_total_ramdisk_size;
+ }
+
+ // Pad ramdisk to page boundary.
+ const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
+ if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
+ !res.ok())
+ return res.error();
+ if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
+
+ // Copy DTB (Q bytes).
+ if (auto res = updater.Copy(q); !res.ok()) return res.error();
+
+ // Copy table, but with corresponding entries modified, including:
+ // - ramdisk_size of the entry replaced
+ // - ramdisk_offset of subsequent entries.
+ for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0;
+ new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) {
+ auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
+ if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok())
+ return res.error();
+ new_entry->ramdisk_offset = new_total_ramdisk_size;
+
+ if (new_entry_idx == replace_idx) {
+ new_entry->ramdisk_size = new_ramdisk.size();
+ }
+ new_total_ramdisk_size += new_entry->ramdisk_size;
+ }
+
+ // Copy padding of R pages; this is okay because table size is not changed.
+ if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num *
+ hdr->vendor_ramdisk_table_entry_size);
+ !res.ok())
+ return res.error();
+ if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + r); !res.ok())
+ return res.error();
+
+ // Copy bootconfig (S bytes).
+ if (auto res = updater.Copy(s); !res.ok()) return res.error();
+
+ if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
+ return updater.Finish();
+}
+
+} // namespace
+
+[[nodiscard]] Result<void> replace_vendor_ramdisk(android::base::borrowed_fd vendor_boot_fd,
+ uint64_t vendor_boot_size,
+ const std::string& ramdisk_name,
+ android::base::borrowed_fd new_ramdisk_fd,
+ uint64_t new_ramdisk_size) {
+ if (new_ramdisk_size > std::numeric_limits<uint32_t>::max()) {
+ return Errorf("New vendor ramdisk is too big");
+ }
+
+ auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot");
+ if (!vendor_boot.ok()) return vendor_boot.error();
+ auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk");
+ if (!new_ramdisk.ok()) return new_ramdisk.error();
+
+ Result<std::string> new_vendor_boot;
+ if (ramdisk_name == "default") {
+ new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk);
+ } else {
+ new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk);
+ }
+ if (!new_vendor_boot.ok()) return new_vendor_boot.error();
+ if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok())
+ return res.error();
+
+ return {};
+}
diff --git a/fastboot/vendor_boot_img_utils.h b/fastboot/vendor_boot_img_utils.h
new file mode 100644
index 0000000..0b702bc
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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 <inttypes.h>
+
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+// Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image,
+// specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks
+// that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively.
+// If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace
+// a vendor ramdisk fragment with the given unique name.
+[[nodiscard]] android::base::Result<void> replace_vendor_ramdisk(
+ android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size,
+ const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd,
+ uint64_t new_ramdisk_size);
diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp
new file mode 100644
index 0000000..1563b89
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils_test.cpp
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2021 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 <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <optional>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/strings.h>
+#include <bootimg.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libavb/libavb.h>
+
+#include "vendor_boot_img_utils.h"
+
+using android::base::borrowed_fd;
+using android::base::ErrnoError;
+using android::base::GetExecutableDirectory;
+using android::base::ReadFdToString;
+using android::base::Result;
+using testing::AllOf;
+using testing::Each;
+using testing::Eq;
+using testing::HasSubstr;
+using testing::Not;
+using testing::Property;
+using std::string_literals::operator""s;
+
+// Expect that the Result<T> returned by |expr| is successful, and value matches |result_matcher|.
+#define EXPECT_RESULT(expr, result_matcher) \
+ EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \
+ Property(&decltype(expr)::value, result_matcher)))
+
+// Expect that the Result<T> returned by |expr| fails, and error message matches |error_matcher|.
+#define EXPECT_ERROR(expr, error_matcher) \
+ do { \
+ EXPECT_THAT( \
+ expr, \
+ AllOf(Property(&decltype(expr)::ok, Eq(false)), \
+ Property(&decltype(expr)::error, \
+ Property(&decltype(expr)::error_type::message, error_matcher)))); \
+ } while (0)
+
+namespace {
+
+// Wrapper of fstat.
+Result<uint64_t> FileSize(borrowed_fd fd, std::filesystem::path path) {
+ struct stat sb;
+ if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")";
+ return sb.st_size;
+}
+
+// Seek to beginning then read the whole file.
+Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
+ if (lseek64(fd.get(), 0, SEEK_SET) != 0)
+ return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
+ std::string content;
+ if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
+ return content;
+}
+
+// Round |value| up to page boundary.
+inline uint32_t round_up(uint32_t value, uint32_t page_size) {
+ return (value + page_size - 1) / page_size * page_size;
+}
+
+// Match is successful if |arg| is a zero-padded version of |expected|.
+MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) {
+ if (arg.size() < expected.size()) return false;
+ if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false;
+ auto remainder = std::string_view(arg).substr(expected.size());
+ for (char e : remainder)
+ if (e != '\0') return false;
+ return true;
+}
+
+// Same as Eq, but don't print the content to avoid spam.
+MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) {
+ if (arg.size() != expected.size()) return false;
+ return 0 == memcmp(arg.data(), expected.data(), expected.size());
+}
+
+// Expect that |arg| and |expected| has the same AVB footer.
+MATCHER_P(HasSameAvbFooter, expected,
+ (negation ? "has" : "does not have") + "expected AVB footer"s) {
+ if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false;
+ return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) ==
+ std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE);
+}
+
+// A lazy handle of a file.
+struct TestFileHandle {
+ virtual ~TestFileHandle() = default;
+ // Lazily call OpenImpl(), cache result in open_result_.
+ android::base::Result<void> Open() {
+ if (!open_result_.has_value()) open_result_ = OpenImpl();
+ return open_result_.value();
+ }
+ // The original size at the time when the file is opened. If the file has been modified,
+ // this field is NOT updated.
+ uint64_t size() {
+ CHECK(open_result_.has_value());
+ return size_;
+ }
+ // The current size of the file. If the file has been modified since opened,
+ // this is updated.
+ Result<uint64_t> fsize() {
+ CHECK(open_result_.has_value());
+ return FileSize(fd_, abs_path_);
+ }
+ borrowed_fd fd() {
+ CHECK(open_result_.has_value());
+ return fd_;
+ }
+ Result<std::string> Read() {
+ CHECK(open_result_.has_value());
+ return ReadStartOfFdToString(fd_, abs_path_);
+ }
+
+ private:
+ std::filesystem::path abs_path_;
+ uint64_t size_;
+ std::optional<android::base::Result<void>> open_result_;
+ borrowed_fd fd_{-1};
+ // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to
+ // |borrowed_fd_|.
+ android::base::Result<void> OpenImpl() {
+ android::base::unique_fd read_fd(TEMP_FAILURE_RETRY(
+ open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY)));
+ if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")";
+ auto size = FileSize(read_fd, abs_path_);
+ if (!size.ok()) return size.error();
+ size_ = *size;
+
+ auto borrowed_fd = Transform(abs_path_, std::move(read_fd));
+ if (!borrowed_fd.ok()) return borrowed_fd.error();
+ fd_ = borrowed_fd.value();
+
+ return {};
+ }
+
+ protected:
+ // |rel_path| is the relative path under test data directory.
+ TestFileHandle(const std::filesystem::path& rel_path)
+ : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {}
+ // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client
+ // to use. Implementation is responsible for managing the lifetime of the returned fd.
+ virtual android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
+ android::base::unique_fd read_fd) = 0;
+};
+
+// A TestFileHandle where the file is readonly.
+struct ReadOnlyTestFileHandle : TestFileHandle {
+ ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
+
+ private:
+ android::base::unique_fd owned_fd_;
+ android::base::Result<borrowed_fd> Transform(const std::filesystem::path&,
+ android::base::unique_fd read_fd) override {
+ owned_fd_ = std::move(read_fd);
+ return owned_fd_;
+ }
+};
+
+// A TestFileHandle where the test file is copies, hence read-writable.
+struct ReadWriteTestFileHandle : TestFileHandle {
+ ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
+
+ private:
+ std::unique_ptr<TemporaryFile> temp_file_;
+
+ android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
+ android::base::unique_fd read_fd) override {
+ // Make a copy to avoid writing to test data. Test files are small, so it is okay
+ // to read the whole file.
+ auto content = ReadStartOfFdToString(read_fd, abs_path);
+ if (!content.ok()) return content.error();
+ temp_file_ = std::make_unique<TemporaryFile>();
+ if (temp_file_->fd == -1)
+ return ErrnoError() << "copy " << abs_path << ": open temp file failed";
+ if (!android::base::WriteStringToFd(*content, temp_file_->fd))
+ return ErrnoError() << "copy " << abs_path << ": write temp file failed";
+
+ return temp_file_->fd;
+ }
+};
+
+class RepackVendorBootImgTestEnv : public ::testing::Environment {
+ public:
+ virtual void SetUp() {
+ OpenTestFile("test_dtb.img", &dtb, &dtb_content);
+ OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content);
+ OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content);
+ OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content);
+ OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content);
+ }
+
+ std::unique_ptr<TestFileHandle> dtb;
+ std::string dtb_content;
+ std::unique_ptr<TestFileHandle> bootconfig;
+ std::string bootconfig_content;
+ std::unique_ptr<TestFileHandle> none;
+ std::string none_content;
+ std::unique_ptr<TestFileHandle> platform;
+ std::string platform_content;
+ std::unique_ptr<TestFileHandle> replace;
+ std::string replace_content;
+
+ private:
+ void OpenTestFile(const char* rel_path, std::unique_ptr<TestFileHandle>* handle,
+ std::string* content) {
+ *handle = std::make_unique<ReadOnlyTestFileHandle>(rel_path);
+ ASSERT_RESULT_OK((*handle)->Open());
+ auto content_res = (*handle)->Read();
+ ASSERT_RESULT_OK(content_res);
+ *content = *content_res;
+ }
+};
+RepackVendorBootImgTestEnv* env = nullptr;
+
+struct RepackVendorBootImgTestParam {
+ std::string vendor_boot_file_name;
+ uint32_t expected_header_version;
+ friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) {
+ return os << param.vendor_boot_file_name;
+ }
+};
+
+class RepackVendorBootImgTest : public ::testing::TestWithParam<RepackVendorBootImgTestParam> {
+ public:
+ virtual void SetUp() {
+ vboot = std::make_unique<ReadWriteTestFileHandle>(GetParam().vendor_boot_file_name);
+ ASSERT_RESULT_OK(vboot->Open());
+ }
+ std::unique_ptr<TestFileHandle> vboot;
+};
+
+TEST_P(RepackVendorBootImgTest, InvalidSize) {
+ EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default",
+ env->replace->fd(), env->replace->size()),
+ HasSubstr("Size of vendor boot does not match"));
+ EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(),
+ env->replace->size() + 1),
+ HasSubstr("Size of new vendor ramdisk does not match"));
+}
+
+TEST_P(RepackVendorBootImgTest, ReplaceUnknown) {
+ auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(),
+ env->replace->size());
+ if (GetParam().expected_header_version == 3) {
+ EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3"));
+ } else if (GetParam().expected_header_version == 4) {
+ EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found"));
+ }
+}
+
+TEST_P(RepackVendorBootImgTest, ReplaceDefault) {
+ auto old_content = vboot->Read();
+ ASSERT_RESULT_OK(old_content);
+
+ ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default",
+ env->replace->fd(), env->replace->size()));
+ EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
+
+ auto new_content_res = vboot->Read();
+ ASSERT_RESULT_OK(new_content_res);
+ std::string_view new_content(*new_content_res);
+
+ auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(new_content.data());
+ ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
+ ASSERT_EQ(GetParam().expected_header_version, hdr->header_version);
+ EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size());
+ EXPECT_EQ(hdr->dtb_size, env->dtb->size());
+
+ auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
+ auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+ auto q = round_up(hdr->dtb_size, hdr->page_size);
+
+ EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content));
+ EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
+
+ if (hdr->header_version < 4) return;
+
+ auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
+ EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1);
+ EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size);
+ EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
+ auto entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
+ EXPECT_EQ(entry->ramdisk_offset, 0);
+ EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size);
+ EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
+
+ EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size());
+ auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
+ auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
+ EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
+
+ EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ RepackVendorBootImgTest, RepackVendorBootImgTest,
+ ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3},
+ RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4},
+ RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}),
+ [](const auto& info) {
+ return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false);
+ });
+
+std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) {
+ auto ramdisk_name = reinterpret_cast<const char*>(entry->ramdisk_name);
+ return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE));
+}
+
+class RepackVendorBootImgTestV4 : public ::testing::TestWithParam<uint32_t /* ramdisk type */> {
+ public:
+ virtual void SetUp() {
+ vboot = std::make_unique<ReadWriteTestFileHandle>("vendor_boot_v4_with_frag.img");
+ ASSERT_RESULT_OK(vboot->Open());
+ }
+ std::unique_ptr<TestFileHandle> vboot;
+};
+
+TEST_P(RepackVendorBootImgTestV4, Replace) {
+ uint32_t replace_ramdisk_type = GetParam();
+ std::string replace_ramdisk_name;
+ std::string expect_new_ramdisk_content;
+ uint32_t expect_none_size = env->none->size();
+ uint32_t expect_platform_size = env->platform->size();
+ switch (replace_ramdisk_type) {
+ case VENDOR_RAMDISK_TYPE_NONE:
+ replace_ramdisk_name = "none_ramdisk";
+ expect_new_ramdisk_content = env->replace_content + env->platform_content;
+ expect_none_size = env->replace->size();
+ break;
+ case VENDOR_RAMDISK_TYPE_PLATFORM:
+ replace_ramdisk_name = "platform_ramdisk";
+ expect_new_ramdisk_content = env->none_content + env->replace_content;
+ expect_platform_size = env->replace->size();
+ break;
+ default:
+ LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type
+ << " is not supported by this test.";
+ }
+
+ auto old_content = vboot->Read();
+ ASSERT_RESULT_OK(old_content);
+
+ ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name,
+ env->replace->fd(), env->replace->size()));
+ EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
+
+ auto new_content_res = vboot->Read();
+ ASSERT_RESULT_OK(new_content_res);
+ std::string_view new_content(*new_content_res);
+
+ auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(new_content.data());
+ ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
+ ASSERT_EQ(4, hdr->header_version);
+ EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size);
+ EXPECT_EQ(hdr->dtb_size, env->dtb->size());
+ EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size());
+
+ auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
+ auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+ auto q = round_up(hdr->dtb_size, hdr->page_size);
+ auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
+ auto s = round_up(hdr->bootconfig_size, hdr->page_size);
+
+ EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content));
+ EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
+
+ // Check changes in table.
+ EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2);
+ EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size);
+ EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
+ auto entry_none =
+ reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
+ EXPECT_EQ(entry_none->ramdisk_offset, 0);
+ EXPECT_EQ(entry_none->ramdisk_size, expect_none_size);
+ EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
+ EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk");
+
+ auto entry_platform = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
+ &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]);
+ EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size);
+ EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size);
+ EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM);
+ EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk");
+
+ EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
+
+ EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
+}
+INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4,
+ ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM),
+ [](const auto& info) {
+ return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform";
+ });
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ env = static_cast<RepackVendorBootImgTestEnv*>(
+ testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv));
+ return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp
index 635ca49..5b07168 100644
--- a/fs_mgr/libstorage_literals/Android.bp
+++ b/fs_mgr/libstorage_literals/Android.bp
@@ -8,4 +8,9 @@
host_supported: true,
recovery_available: true,
export_include_dirs: ["."],
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
}
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index b2ab550..5cde167 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -337,12 +337,21 @@
SwitchRoot("/first_stage_ramdisk");
}
+ std::string force_debuggable("/force_debuggable");
+ std::string adb_debug_prop("/adb_debug.prop");
+ std::string userdebug_sepolicy("/userdebug_plat_sepolicy.cil");
+ if (IsRecoveryMode()) {
+ // Update these file paths since we didn't switch root
+ force_debuggable.insert(0, "/first_stage_ramdisk");
+ adb_debug_prop.insert(0, "/first_stage_ramdisk");
+ userdebug_sepolicy.insert(0, "/first_stage_ramdisk");
+ }
// If this file is present, the second-stage init will use a userdebug sepolicy
// and load adb_debug.prop to allow adb root, if the device is unlocked.
- if (access("/force_debuggable", F_OK) == 0) {
+ if (access(force_debuggable.c_str(), F_OK) == 0) {
std::error_code ec; // to invoke the overloaded copy_file() that won't throw.
- if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
- !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) {
+ if (!fs::copy_file(adb_debug_prop, kDebugRamdiskProp, ec) ||
+ !fs::copy_file(userdebug_sepolicy, kDebugRamdiskSEPolicy, ec)) {
LOG(ERROR) << "Failed to setup debug ramdisk";
} else {
// setenv for second-stage init to read above kDebugRamdisk* files.
diff --git a/libkeyutils/Android.bp b/libkeyutils/Android.bp
index 9848cd8..86f68fb 100644
--- a/libkeyutils/Android.bp
+++ b/libkeyutils/Android.bp
@@ -2,25 +2,10 @@
default_applicable_licenses: ["system_core_libkeyutils_license"],
}
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
license {
name: "system_core_libkeyutils_license",
visibility: [":__subpackages__"],
license_kinds: [
- "SPDX-license-identifier-Apache-2.0",
"SPDX-license-identifier-BSD",
],
// large-scale-change unable to identify any license_text files
diff --git a/libstats/OWNERS b/libstats/OWNERS
index 7855774..d391679 100644
--- a/libstats/OWNERS
+++ b/libstats/OWNERS
@@ -1,6 +1,7 @@
-joeo@google.com
+jeffreyhuang@google.com
+jtnguyen@google.com
muhammadq@google.com
-ruchirr@google.com
+sharaienko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
diff --git a/libstats/pull_lazy/Android.bp b/libstats/pull_lazy/Android.bp
new file mode 100644
index 0000000..65dce26
--- /dev/null
+++ b/libstats/pull_lazy/Android.bp
@@ -0,0 +1,48 @@
+// Lazy loading version of libstatspull that can be used by code
+// that is running before the statsd APEX is mounted and
+// libstatspull.so is available.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libstatspull_lazy",
+ header_libs: [
+ "libstatspull_headers",
+ "libstatssocket_headers",
+ ],
+ export_header_lib_headers: [
+ "libstatspull_headers",
+ ],
+ apex_available: ["//apex_available:platform"],
+ srcs: ["libstatspull_lazy.cpp"],
+}
+
+cc_test {
+ name: "libstatspull_lazy_test",
+ srcs: [
+ "tests/libstatspull_lazy_test.cpp",
+ ],
+ static_libs: [
+ "libstatspull_lazy",
+ "libstatssocket_lazy",
+ ],
+ shared_libs: ["liblog"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ test_suites: ["device-tests", "mts-statsd"],
+ test_config: "libstatspull_lazy_test.xml",
+ // TODO(b/153588990): Remove when the build system properly separates.
+ // 32bit and 64bit architectures.
+ compile_multilib: "both",
+ multilib: {
+ lib64: {
+ suffix: "64",
+ },
+ lib32: {
+ suffix: "32",
+ },
+ },
+}
diff --git a/libstats/pull_lazy/TEST_MAPPING b/libstats/pull_lazy/TEST_MAPPING
new file mode 100644
index 0000000..89b8c2a
--- /dev/null
+++ b/libstats/pull_lazy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit" : [
+ {
+ "name" : "libstatspull_lazy_test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/libstats/pull_lazy/libstatspull_lazy.cpp b/libstats/pull_lazy/libstatspull_lazy.cpp
new file mode 100644
index 0000000..b11fcee
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2021 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 "libstatspull_lazy.h"
+
+#include <mutex>
+
+#include <dlfcn.h>
+#include <stdatomic.h>
+
+#include "log/log.h"
+
+#include "stats_pull_atom_callback.h"
+
+// This file provides a lazy interface to libstatspull.so to address early boot dependencies.
+// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
+// libstatspull.so is in the statsd APEX.
+
+// Method pointers to libstatspull methods are held in an array which simplifies checking
+// all pointers are initialized.
+enum MethodIndex {
+ // PullAtomMetadata APIs in stats_pull_atom_callback.h.
+ k_AStatsManager_PullAtomMetadata_obtain,
+ k_AStatsManager_PullAtomMetadata_release,
+ k_AStatsManager_PullAtomMetadata_setCoolDownMillis,
+ k_AStatsManager_PullAtomMetadata_getCoolDownMillis,
+ k_AStatsManager_PullAtomMetadata_setTimeoutMillis,
+ k_AStatsManager_PullAtomMetadata_getTimeoutMillis,
+ k_AStatsManager_PullAtomMetadata_setAdditiveFields,
+ k_AStatsManager_PullAtomMetadata_getNumAdditiveFields,
+ k_AStatsManager_PullAtomMetadata_getAdditiveFields,
+
+ // AStatsEventList APIs in stats_pull_atom_callback.h
+ k_AStatsEventList_addStatsEvent,
+
+ // PullAtomCallback APIs in stats_pull_atom_callback.h
+ k_AStatsManager_setPullAtomCallback,
+ k_AStatsManager_clearPullAtomCallback,
+
+ // Marker for count of methods
+ k_MethodCount
+};
+
+// Table of methods pointers in libstatspull APIs.
+static void* g_Methods[k_MethodCount];
+
+//
+// Libstatspull lazy loading.
+//
+
+static atomic_bool gPreventLibstatspullLoading = false; // Allows tests to block loading.
+
+void PreventLibstatspullLazyLoadingForTests() {
+ gPreventLibstatspullLoading.store(true);
+}
+
+static void* LoadLibstatspull(int dlopen_flags) {
+ if (gPreventLibstatspullLoading.load()) {
+ return nullptr;
+ }
+ return dlopen("libstatspull.so", dlopen_flags);
+}
+
+//
+// Initialization and symbol binding.
+
+static void BindSymbol(void* handle, const char* name, enum MethodIndex index) {
+ void* symbol = dlsym(handle, name);
+ LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatspull.so: %s",
+ name, dlerror());
+ g_Methods[index] = symbol;
+}
+
+static void InitializeOnce() {
+ void* handle = LoadLibstatspull(RTLD_NOW);
+ LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatspull.so: %s", dlerror());
+
+#undef BIND_SYMBOL
+#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name);
+ // PullAtomMetadata APIs in stats_pull_atom_callback.h.
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_obtain);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_release);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_setCoolDownMillis);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_getCoolDownMillis);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_setTimeoutMillis);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_getTimeoutMillis);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_setAdditiveFields);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_getNumAdditiveFields);
+ BIND_SYMBOL(AStatsManager_PullAtomMetadata_getAdditiveFields);
+
+ // AStatsEventList APIs in stats_pull_atom_callback.h
+ BIND_SYMBOL(AStatsEventList_addStatsEvent);
+
+ // PullAtomCallback APIs in stats_pull_atom_callback.h
+ BIND_SYMBOL(AStatsManager_setPullAtomCallback);
+ BIND_SYMBOL(AStatsManager_clearPullAtomCallback);
+
+#undef BIND_SYMBOL
+
+ // Check every symbol is bound.
+ for (int i = 0; i < k_MethodCount; ++i) {
+ LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr,
+ "Uninitialized method in libstatspull_lazy at index: %d", i);
+ }
+}
+
+static void EnsureInitialized() {
+ static std::once_flag initialize_flag;
+ std::call_once(initialize_flag, InitializeOnce);
+}
+
+#define INVOKE_METHOD(name, args...) \
+ do { \
+ EnsureInitialized(); \
+ void* method = g_Methods[k_##name]; \
+ return reinterpret_cast<decltype(&name)>(method)(args); \
+ } while (0)
+
+//
+// Forwarding for methods in stats_pull_atom_callback.h.
+//
+
+AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain() {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_obtain);
+}
+
+void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_release, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata,
+ int64_t cool_down_millis) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_setCoolDownMillis, metadata, cool_down_millis);
+}
+
+int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_getCoolDownMillis, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata,
+ int64_t timeout_millis) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_setTimeoutMillis, metadata, timeout_millis);
+}
+
+int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_getTimeoutMillis, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
+ int32_t* additive_fields,
+ int32_t num_fields) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_setAdditiveFields, metadata, additive_fields,
+ num_fields);
+}
+
+int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields(
+ AStatsManager_PullAtomMetadata* metadata) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_getNumAdditiveFields, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
+ int32_t* fields) {
+ INVOKE_METHOD(AStatsManager_PullAtomMetadata_getAdditiveFields, metadata, fields);
+}
+
+AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data) {
+ INVOKE_METHOD(AStatsEventList_addStatsEvent, pull_data);
+}
+
+void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata,
+ AStatsManager_PullAtomCallback callback, void* cookie) {
+ INVOKE_METHOD(AStatsManager_setPullAtomCallback, atom_tag, metadata, callback, cookie);
+}
+
+void AStatsManager_clearPullAtomCallback(int32_t atom_tag) {
+ INVOKE_METHOD(AStatsManager_clearPullAtomCallback, atom_tag);
+}
diff --git a/libstats/pull_lazy/libstatspull_lazy.h b/libstats/pull_lazy/libstatspull_lazy.h
new file mode 100644
index 0000000..2edddc7
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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
+
+extern "C" void PreventLibstatspullLazyLoadingForTests();
\ No newline at end of file
diff --git a/libstats/pull_lazy/libstatspull_lazy_test.xml b/libstats/pull_lazy/libstatspull_lazy_test.xml
new file mode 100644
index 0000000..1b619af
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy_test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Runs libstatspull_lazy_test.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+ <option name="test-suite-tag" value="mts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="libstatspull_lazy_test->/data/local/tmp/libstatspull_lazy_test" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="libstatspull_lazy_test" />
+ </test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+ </object>
+</configuration>
\ No newline at end of file
diff --git a/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp
new file mode 100644
index 0000000..41f82d0
--- /dev/null
+++ b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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 "../libstatspull_lazy.h"
+
+#include <gtest/gtest.h>
+
+#include "stats_pull_atom_callback.h"
+//#include "stats_event.h"
+
+// The tests here are just for the case when libstatspull.so cannot be loaded by
+// libstatspull_lazy.
+class LibstatspullLazyTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ ::testing::Test::SetUp();
+ PreventLibstatspullLazyLoadingForTests();
+ }
+};
+
+static const char* kLoadFailed = "Failed to load libstatspull.so";
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomMetadata) {
+ AStatsManager_PullAtomMetadata* metadata = NULL;
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_obtain(), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_release(metadata), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_setAdditiveFields(metadata, NULL, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_PullAtomMetadata_getAdditiveFields(metadata, NULL), kLoadFailed);
+}
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForAStatsEventList) {
+ AStatsEventList* event_list = NULL;
+ EXPECT_DEATH(AStatsEventList_addStatsEvent(event_list), kLoadFailed);
+}
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomCallback) {
+ AStatsManager_PullAtomCallback callback = NULL;
+ EXPECT_DEATH(AStatsManager_setPullAtomCallback(0, NULL, callback, NULL), kLoadFailed);
+ EXPECT_DEATH(AStatsManager_clearPullAtomCallback(0), kLoadFailed);
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp
new file mode 100644
index 0000000..b2cd7b2
--- /dev/null
+++ b/libstats/socket_lazy/Android.bp
@@ -0,0 +1,44 @@
+// Lazy loading version of libstatssocket that can be used by code
+// that is running before the statsd APEX is mounted and
+// libstatssocket.so is available.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libstatssocket_lazy",
+ header_libs: [
+ "libstatssocket_headers",
+ ],
+ export_header_lib_headers: [
+ "libstatssocket_headers",
+ ],
+ apex_available: ["//apex_available:platform"],
+ srcs: ["libstatssocket_lazy.cpp"],
+}
+
+cc_test {
+ name: "libstatssocket_lazy_test",
+ srcs: [
+ "tests/libstatssocket_lazy_test.cpp",
+ ],
+ static_libs: ["libstatssocket_lazy"],
+ shared_libs: ["liblog"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ test_suites: ["device-tests", "mts-statsd"],
+ test_config: "libstatssocket_lazy_test.xml",
+ // TODO(b/153588990): Remove when the build system properly separates.
+ // 32bit and 64bit architectures.
+ compile_multilib: "both",
+ multilib: {
+ lib64: {
+ suffix: "64",
+ },
+ lib32: {
+ suffix: "32",
+ },
+ },
+}
diff --git a/libstats/socket_lazy/TEST_MAPPING b/libstats/socket_lazy/TEST_MAPPING
new file mode 100644
index 0000000..13afc00
--- /dev/null
+++ b/libstats/socket_lazy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit" : [
+ {
+ "name" : "libstatssocket_lazy_test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy.cpp b/libstats/socket_lazy/libstatssocket_lazy.cpp
new file mode 100644
index 0000000..dd93eeb
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2021 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 "libstatssocket_lazy.h"
+
+#include <mutex>
+
+#include <dlfcn.h>
+#include <stdatomic.h>
+
+#include "log/log.h"
+
+#include "stats_event.h"
+#include "stats_socket.h"
+
+// This file provides a lazy interface to libstatssocket.so to address early boot dependencies.
+// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
+// libstatssocket.so is in the statsd APEX.
+
+// Method pointers to libstatssocket methods are held in an array which simplifies checking
+// all pointers are initialized.
+enum MethodIndex {
+ // Stats Event APIs in stats_event.h.
+ k_AStatsEvent_obtain,
+ k_AStatsEvent_build,
+ k_AStatsEvent_write,
+ k_AStatsEvent_release,
+ k_AStatsEvent_setAtomId,
+ k_AStatsEvent_writeInt32,
+ k_AStatsEvent_writeInt64,
+ k_AStatsEvent_writeFloat,
+ k_AStatsEvent_writeBool,
+ k_AStatsEvent_writeByteArray,
+ k_AStatsEvent_writeString,
+ k_AStatsEvent_writeAttributionChain,
+ k_AStatsEvent_addBoolAnnotation,
+ k_AStatsEvent_addInt32Annotation,
+
+ // Stats Socket APIs in stats_socket.h.
+ k_AStatsSocket_close,
+
+ // Marker for count of methods
+ k_MethodCount
+};
+
+// Table of methods pointers in libstatssocket APIs.
+static void* g_Methods[k_MethodCount];
+
+//
+// Libstatssocket lazy loading.
+//
+
+static atomic_bool gPreventLibstatssocketLoading = false; // Allows tests to block loading.
+
+void PreventLibstatssocketLazyLoadingForTests() {
+ gPreventLibstatssocketLoading.store(true);
+}
+
+static void* LoadLibstatssocket(int dlopen_flags) {
+ if (gPreventLibstatssocketLoading.load()) {
+ return nullptr;
+ }
+ return dlopen("libstatssocket.so", dlopen_flags);
+}
+
+//
+// Initialization and symbol binding.
+
+static void BindSymbol(void* handle, const char* name, enum MethodIndex index) {
+ void* symbol = dlsym(handle, name);
+ LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatssocket.so: %s",
+ name, dlerror());
+ g_Methods[index] = symbol;
+}
+
+static void InitializeOnce() {
+ void* handle = LoadLibstatssocket(RTLD_NOW);
+ LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatssocket.so: %s", dlerror());
+
+#undef BIND_SYMBOL
+#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name);
+ // Methods in stats_event.h.
+ BIND_SYMBOL(AStatsEvent_obtain);
+ BIND_SYMBOL(AStatsEvent_build);
+ BIND_SYMBOL(AStatsEvent_write);
+ BIND_SYMBOL(AStatsEvent_release);
+ BIND_SYMBOL(AStatsEvent_setAtomId);
+ BIND_SYMBOL(AStatsEvent_writeInt32);
+ BIND_SYMBOL(AStatsEvent_writeInt64);
+ BIND_SYMBOL(AStatsEvent_writeFloat);
+ BIND_SYMBOL(AStatsEvent_writeBool);
+ BIND_SYMBOL(AStatsEvent_writeByteArray);
+ BIND_SYMBOL(AStatsEvent_writeString);
+ BIND_SYMBOL(AStatsEvent_writeAttributionChain);
+ BIND_SYMBOL(AStatsEvent_addBoolAnnotation);
+ BIND_SYMBOL(AStatsEvent_addInt32Annotation);
+
+ // Methods in stats_socket.h.
+ BIND_SYMBOL(AStatsSocket_close);
+#undef BIND_SYMBOL
+
+ // Check every symbol is bound.
+ for (int i = 0; i < k_MethodCount; ++i) {
+ LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr,
+ "Uninitialized method in libstatssocket_lazy at index: %d", i);
+ }
+}
+
+static void EnsureInitialized() {
+ static std::once_flag initialize_flag;
+ std::call_once(initialize_flag, InitializeOnce);
+}
+
+#define INVOKE_METHOD(name, args...) \
+ do { \
+ EnsureInitialized(); \
+ void* method = g_Methods[k_##name]; \
+ return reinterpret_cast<decltype(&name)>(method)(args); \
+ } while (0)
+
+//
+// Forwarding for methods in stats_event.h.
+//
+
+AStatsEvent* AStatsEvent_obtain() {
+ INVOKE_METHOD(AStatsEvent_obtain);
+}
+
+void AStatsEvent_build(AStatsEvent* event) {
+ INVOKE_METHOD(AStatsEvent_build, event);
+}
+
+int AStatsEvent_write(AStatsEvent* event) {
+ INVOKE_METHOD(AStatsEvent_write, event);
+}
+
+void AStatsEvent_release(AStatsEvent* event) {
+ INVOKE_METHOD(AStatsEvent_release, event);
+}
+
+void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId) {
+ INVOKE_METHOD(AStatsEvent_setAtomId, event, atomId);
+}
+
+void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value) {
+ INVOKE_METHOD(AStatsEvent_writeInt32, event, value);
+}
+
+void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value) {
+ INVOKE_METHOD(AStatsEvent_writeInt64, event, value);
+}
+
+void AStatsEvent_writeFloat(AStatsEvent* event, float value) {
+ INVOKE_METHOD(AStatsEvent_writeFloat, event, value);
+}
+
+void AStatsEvent_writeBool(AStatsEvent* event, bool value) {
+ INVOKE_METHOD(AStatsEvent_writeBool, event, value);
+}
+
+void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes) {
+ INVOKE_METHOD(AStatsEvent_writeByteArray, event, buf, numBytes);
+}
+
+void AStatsEvent_writeString(AStatsEvent* event, const char* value) {
+ INVOKE_METHOD(AStatsEvent_writeString, event, value);
+}
+
+void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids,
+ const char* const* tags, uint8_t numNodes) {
+ INVOKE_METHOD(AStatsEvent_writeAttributionChain, event, uids, tags, numNodes);
+}
+
+void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value) {
+ INVOKE_METHOD(AStatsEvent_addBoolAnnotation, event, annotationId, value);
+}
+
+void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value) {
+ INVOKE_METHOD(AStatsEvent_addInt32Annotation, event, annotationId, value);
+}
+
+//
+// Forwarding for methods in stats_socket.h.
+//
+
+void AStatsSocket_close() {
+ INVOKE_METHOD(AStatsSocket_close);
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy.h b/libstats/socket_lazy/libstatssocket_lazy.h
new file mode 100644
index 0000000..3ff87cb
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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
+
+extern "C" void PreventLibstatssocketLazyLoadingForTests();
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy_test.xml b/libstats/socket_lazy/libstatssocket_lazy_test.xml
new file mode 100644
index 0000000..ca6339b
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy_test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Runs libstatssocket_lazy_test.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+ <option name="test-suite-tag" value="mts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="libstatssocket_lazy_test->/data/local/tmp/libstatssocket_lazy_test" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="libstatssocket_lazy_test" />
+ </test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+ </object>
+</configuration>
\ No newline at end of file
diff --git a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
new file mode 100644
index 0000000..fe13598
--- /dev/null
+++ b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 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 "../libstatssocket_lazy.h"
+
+#include <gtest/gtest.h>
+
+#include "stats_event.h"
+#include "stats_socket.h"
+
+// The tests here are just for the case when libstatssocket.so cannot be loaded by
+// libstatssocket_lazy.
+class LibstatssocketLazyTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ ::testing::Test::SetUp();
+ PreventLibstatssocketLazyLoadingForTests();
+ }
+};
+
+static const char* kLoadFailed = "Failed to load libstatssocket.so";
+
+TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsEvent) {
+ AStatsEvent* event = NULL;
+ EXPECT_DEATH(AStatsEvent_obtain(), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_build(event), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_write(event), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_release(event), kLoadFailed);
+
+ EXPECT_DEATH(AStatsEvent_setAtomId(event, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeInt32(event, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeInt64(event, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeFloat(event, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeBool(event, false), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeByteArray(event, NULL, 0), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeString(event, NULL), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_writeAttributionChain(event, NULL, NULL, 0), kLoadFailed);
+
+ EXPECT_DEATH(AStatsEvent_addBoolAnnotation(event, 0, false), kLoadFailed);
+ EXPECT_DEATH(AStatsEvent_addInt32Annotation(event, 0, 0), kLoadFailed);
+}
+
+TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsSocket) {
+ EXPECT_DEATH(AStatsSocket_close(), kLoadFailed);
+}
\ No newline at end of file
diff --git a/llkd/README.md b/llkd/README.md
index 6f92f14..9bcf806 100644
--- a/llkd/README.md
+++ b/llkd/README.md
@@ -207,7 +207,7 @@
The `llkd` does not monitor the specified subset of processes for live lock stack
signatures. Default is process names
-`init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd`. Prevents the sepolicy
+`init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd,logd`. Prevents the sepolicy
violation associated with processes that block `ptrace` (as these can't be
checked). **Active only on userdebug and eng builds**. For details on build
types, refer to [Building Android](/setup/build/building#choose-a-target).
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index 4b20a56..0822a3e 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -60,7 +60,7 @@
#define LLK_IGNORELIST_UID_PROPERTY "ro.llk.ignorelist.uid"
#define LLK_IGNORELIST_UID_DEFAULT ""
#define LLK_IGNORELIST_STACK_PROPERTY "ro.llk.ignorelist.process.stack"
-#define LLK_IGNORELIST_STACK_DEFAULT "init,lmkd.llkd,llkd,keystore,ueventd,apexd"
+#define LLK_IGNORELIST_STACK_DEFAULT "init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd"
/* clang-format on */
__END_DECLS
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index 9f3e218..c4c58ee 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -115,8 +115,8 @@
// list of uids, and uid names, to skip, default nothing
std::unordered_set<std::string> llkIgnorelistUid;
#ifdef __PTRACE_ENABLED__
-// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore" or
-// "logd" (if not userdebug).
+// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore",
+// "keystore2", or "logd" (if not userdebug).
std::unordered_set<std::string> llkIgnorelistStack;
#endif
@@ -962,7 +962,8 @@
//
// This alarm is effectively the live lock detection of llkd, as
// we understandably can not monitor ourselves otherwise.
- ::alarm(duration_cast<seconds>(llkTimeoutMs * 2 * android::base::TimeoutMultiplier()).count());
+ ::alarm(duration_cast<seconds>(llkTimeoutMs * 2 * android::base::HwTimeoutMultiplier())
+ .count());
// kernel jiffy precision fastest acquisition
static timespec last;
diff --git a/libkeyutils/mini_keyctl/Android.bp b/mini_keyctl/Android.bp
similarity index 100%
rename from libkeyutils/mini_keyctl/Android.bp
rename to mini_keyctl/Android.bp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl.cpp b/mini_keyctl/mini_keyctl.cpp
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl.cpp
rename to mini_keyctl/mini_keyctl.cpp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp b/mini_keyctl/mini_keyctl_utils.cpp
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl_utils.cpp
rename to mini_keyctl/mini_keyctl_utils.cpp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.h b/mini_keyctl/mini_keyctl_utils.h
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl_utils.h
rename to mini_keyctl/mini_keyctl_utils.h
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 58e161d..11b3da7 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -1074,7 +1074,7 @@
chown root radio /proc/cmdline
# Define default initial receive window size in segments.
- setprop net.tcp.default_init_rwnd 60
+ setprop net.tcp_def_init_rwnd 60
# Start standard binderized HAL daemons
class_start hal
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index 24b0f98..3258944 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -51,13 +51,21 @@
exit(-1);
}
+ /* Make sure lazy-loaded TAs have started and connected to coverage service. */
+ TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
+ auto ret = ta.Connect();
+ if (!ret.ok()) {
+ std::cerr << ret.error() << std::endl;
+ exit(-1);
+ }
+
record = std::make_unique<CoverageRecord>(TIPC_DEV, &module_uuid, TRUSTY_APP_FILENAME);
if (!record) {
std::cerr << "Failed to allocate coverage record" << std::endl;
exit(-1);
}
- auto ret = record->Open();
+ ret = record->Open();
if (!ret.ok()) {
std::cerr << ret.error() << std::endl;
exit(-1);
diff --git a/trusty/utils/acvp/Android.bp b/trusty/utils/acvp/Android.bp
new file mode 100644
index 0000000..b851e39
--- /dev/null
+++ b/trusty/utils/acvp/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "trusty_acvp_modulewrapper",
+ vendor: true,
+
+ srcs: [
+ "trusty_modulewrapper.cpp",
+ ],
+ static_libs: [
+ "libacvp_modulewrapper",
+ ],
+ shared_libs: [
+ "libbase",
+ "libc",
+ "libdmabufheap",
+ "liblog",
+ "libtrusty",
+ "libssl",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+}
diff --git a/trusty/utils/acvp/acvp_ipc.h b/trusty/utils/acvp/acvp_ipc.h
new file mode 100644
index 0000000..8b48ae3
--- /dev/null
+++ b/trusty/utils/acvp/acvp_ipc.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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 <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ACVP_PORT "com.android.trusty.acvp"
+
+/*
+ * Maximum number of arguments
+ */
+#define ACVP_MAX_NUM_ARGUMENTS 8
+
+/*
+ * Maximum length of an algorithm name
+ */
+#define ACVP_MAX_NAME_LENGTH 30
+
+/*
+ * Maximum length of an ACVP request message
+ */
+#define ACVP_MAX_MESSAGE_LENGTH sizeof(struct acvp_req)
+
+/*
+ * Minimum length of the shared memory buffer
+ *
+ * This must be at least as long as the longest reply from the ACVP service
+ * (currently the reply from getConfig()).
+ */
+#define ACVP_MIN_SHARED_MEMORY 16384
+
+/**
+ * acvp_req - Request for the Trusty ACVP app
+ * @num_args: Number of acvp_arg structures following this struct
+ * @buffer_size: Total size of shared memory buffer
+ * @lengths: Length of each argument in the shared memory buffer
+ *
+ * @num_args copies of the acvp_arg struct follow this structure.
+ */
+struct acvp_req {
+ uint32_t num_args;
+ uint32_t buffer_size;
+ uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS];
+};
+
+/**
+ * acvp_resp - Response to a ACVP request
+ *
+ * @num_spans: Number of response sections
+ * @lengths: Length of each response section
+ */
+struct acvp_resp {
+ uint32_t num_spans;
+ uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS];
+};
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/trusty/utils/acvp/trusty_modulewrapper.cpp b/trusty/utils/acvp/trusty_modulewrapper.cpp
new file mode 100644
index 0000000..70ffb52
--- /dev/null
+++ b/trusty/utils/acvp/trusty_modulewrapper.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2021, 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 LOG_TAG "TrustyAcvpModulewrapper"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <errno.h>
+#include <log/log.h>
+#include <modulewrapper.h>
+#include <openssl/span.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+#include <iostream>
+
+#include "acvp_ipc.h"
+
+constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0";
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+using android::base::WriteFully;
+
+static inline size_t AlignUpToPage(size_t size) {
+ return (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+namespace {
+
+class ModuleWrapper {
+ private:
+ static const char* kAcvpPort_;
+ static const char* kTrustyDeviceName_;
+
+ public:
+ ModuleWrapper();
+ ~ModuleWrapper();
+
+ Result<void> SendMessage(bssl::Span<const bssl::Span<const uint8_t>>);
+
+ Result<void> ForwardResponse();
+
+ private:
+ // Connection to the Trusty ACVP service
+ int tipc_fd_ = -1;
+
+ // Shared memory DMA buf
+ unique_fd dmabuf_fd_;
+
+ // Size of shared memory mapping
+ size_t shm_size_ = 0;
+
+ // Shared memory mapping
+ uint8_t* shm_buffer_ = nullptr;
+};
+
+} // namespace
+
+const char* ModuleWrapper::kAcvpPort_ = ACVP_PORT;
+const char* ModuleWrapper::kTrustyDeviceName_ = kTrustyDeviceName;
+
+ModuleWrapper::ModuleWrapper() {
+ tipc_fd_ = tipc_connect(kTrustyDeviceName_, kAcvpPort_);
+ if (tipc_fd_ < 0) {
+ fprintf(stderr, "Failed to connect to Trusty ACVP test app: %s\n", strerror(-tipc_fd_));
+ }
+}
+
+ModuleWrapper::~ModuleWrapper() {
+ if (tipc_fd_ >= 0) {
+ tipc_close(tipc_fd_);
+ }
+
+ if (shm_buffer_) {
+ munmap(shm_buffer_, shm_size_);
+ }
+}
+
+Result<void> ModuleWrapper::SendMessage(bssl::Span<const bssl::Span<const uint8_t>> args) {
+ assert(args.size() < ACVP_MAX_NUM_ARGUMENTS);
+ assert(args[0].size() < ACVP_MAX_NAME_LENGTH);
+
+ struct acvp_req request;
+ request.num_args = args.size();
+
+ size_t total_args_size = 0;
+ for (auto arg : args) {
+ total_args_size += arg.size();
+ }
+
+ shm_size_ = ACVP_MIN_SHARED_MEMORY;
+ if (total_args_size > shm_size_) {
+ shm_size_ = AlignUpToPage(total_args_size);
+ }
+ request.buffer_size = shm_size_;
+
+ struct iovec iov = {
+ .iov_base = &request,
+ .iov_len = sizeof(struct acvp_req),
+ };
+
+ BufferAllocator alloc;
+ dmabuf_fd_.reset(alloc.Alloc(kDmabufSystemHeapName, shm_size_));
+ if (!dmabuf_fd_.ok()) {
+ return ErrnoError() << "Error creating dmabuf";
+ }
+
+ shm_buffer_ = (uint8_t*)mmap(0, shm_size_, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd_, 0);
+ if (shm_buffer_ == MAP_FAILED) {
+ return ErrnoError() << "Failed to map shared memory dmabuf";
+ }
+
+ size_t cur_offset = 0;
+ for (int i = 0; i < args.size(); ++i) {
+ request.lengths[i] = args[i].size();
+ memcpy(shm_buffer_ + cur_offset, args[i].data(), args[i].size());
+ cur_offset += args[i].size();
+ }
+
+ struct trusty_shm shm = {
+ .fd = dmabuf_fd_.get(),
+ .transfer = TRUSTY_SHARE,
+ };
+
+ int rc = tipc_send(tipc_fd_, &iov, 1, &shm, 1);
+ if (rc != sizeof(struct acvp_req)) {
+ return ErrnoError() << "Failed to send request to Trusty ACVP service";
+ }
+
+ return {};
+}
+
+Result<void> ModuleWrapper::ForwardResponse() {
+ struct acvp_resp resp;
+ int bytes_read = read(tipc_fd_, &resp, sizeof(struct acvp_resp));
+ if (bytes_read < 0) {
+ return ErrnoError() << "Failed to read response from Trusty ACVP service";
+ }
+
+ if (bytes_read != sizeof(struct acvp_resp)) {
+ return Error() << "Trusty ACVP response overflowed expected size";
+ }
+
+ size_t total_args_size = 0;
+ for (size_t i = 0; i < resp.num_spans; i++) {
+ total_args_size += resp.lengths[i];
+ }
+
+ iovec iovs[2];
+ iovs[0].iov_base = &resp;
+ iovs[0].iov_len = sizeof(uint32_t) * (1 + resp.num_spans);
+
+ iovs[1].iov_base = shm_buffer_;
+ iovs[1].iov_len = total_args_size;
+
+ size_t iov_done = 0;
+ while (iov_done < 2) {
+ ssize_t r;
+ do {
+ r = writev(STDOUT_FILENO, &iovs[iov_done], 2 - iov_done);
+ } while (r == -1 && errno == EINTR);
+
+ if (r <= 0) {
+ return Error() << "Failed to write ACVP response to standard out";
+ }
+
+ size_t written = r;
+ for (size_t i = iov_done; i < 2 && written > 0; i++) {
+ iovec& iov = iovs[i];
+
+ size_t done = written;
+ if (done > iov.iov_len) {
+ done = iov.iov_len;
+ }
+
+ iov.iov_base = reinterpret_cast<uint8_t*>(iov.iov_base) + done;
+ iov.iov_len -= done;
+ written -= done;
+
+ if (iov.iov_len == 0) {
+ iov_done++;
+ }
+ }
+
+ assert(written == 0);
+ }
+
+ return {};
+}
+
+int main() {
+ for (;;) {
+ auto buffer = bssl::acvp::RequestBuffer::New();
+ auto args = bssl::acvp::ParseArgsFromFd(STDIN_FILENO, buffer.get());
+ if (args.empty()) {
+ ALOGE("Could not parse arguments\n");
+ return EXIT_FAILURE;
+ }
+
+ ModuleWrapper wrapper;
+ auto res = wrapper.SendMessage(args);
+ if (!res.ok()) {
+ std::cerr << res.error() << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ res = wrapper.ForwardResponse();
+ if (!res.ok()) {
+ std::cerr << res.error() << std::endl;
+ return EXIT_FAILURE;
+ }
+ }
+
+ return EXIT_SUCCESS;
+};