Merge "fs_mgr: License boilerplate"
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 8555b3d..d7e8f32 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -8,6 +8,7 @@
"-Wall",
"-Wextra",
"-Werror",
+ "-Wno-gcc-compat",
"-Wno-unused-argument",
"-Wno-unused-function",
"-Wno-nullability-completeness",
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 530e0e8..b302918 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -47,19 +47,30 @@
using android::base::ReadFileToString;
using android::base::SendFileDescriptors;
+using android::base::StringAppendV;
using android::base::unique_fd;
using android::base::WriteStringToFd;
-static bool send_signal(pid_t pid, const DebuggerdDumpType dump_type) {
- const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
- sigval val;
- val.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0;
+#define TAG "libdebuggerd_client: "
- if (sigqueue(pid, signal, val) != 0) {
- PLOG(ERROR) << "libdebuggerd_client: failed to send signal to pid " << pid;
- return false;
+// Log an error both to the log (via LOG(ERROR)) and to the given fd.
+static void log_error(int fd, int errno_value, const char* format, ...) __printflike(3, 4) {
+ std::string message(TAG);
+
+ va_list ap;
+ va_start(ap, format);
+ StringAppendV(&message, format, ap);
+ va_end(ap);
+
+ if (errno_value != 0) {
+ message = message + ": " + strerror(errno_value);
}
- return true;
+
+ if (fd != -1) {
+ dprintf(fd, "%s\n", message.c_str());
+ }
+
+ LOG(ERROR) << message;
}
template <typename Duration>
@@ -74,13 +85,11 @@
* Returns the wchan data for each thread in the process,
* or empty string if unable to obtain any data.
*/
-static std::string get_wchan_data(pid_t pid) {
- std::stringstream buffer;
+static std::string get_wchan_data(int fd, pid_t pid) {
std::vector<pid_t> tids;
-
if (!android::procinfo::GetProcessTids(pid, &tids)) {
- LOG(WARNING) << "libdebuggerd_client: Failed to get process tids";
- return buffer.str();
+ log_error(fd, 0, "failed to get process tids");
+ return "";
}
std::stringstream data;
@@ -88,12 +97,13 @@
std::string path = "/proc/" + std::to_string(pid) + "/task/" + std::to_string(tid) + "/wchan";
std::string wchan_str;
if (!ReadFileToString(path, &wchan_str, true)) {
- PLOG(WARNING) << "libdebuggerd_client: Failed to read \"" << path << "\"";
+ log_error(fd, errno, "failed to read \"%s\"", path.c_str());
continue;
}
data << "sysTid=" << std::left << std::setw(10) << tid << wchan_str << "\n";
}
+ std::stringstream buffer;
if (std::string str = data.str(); !str.empty()) {
buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
<< "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n";
@@ -101,16 +111,9 @@
buffer << "----- end " << std::to_string(pid) << " -----\n";
buffer << "\n";
}
-
return buffer.str();
}
-static void dump_wchan_data(const std::string& data, int fd, pid_t pid) {
- if (!WriteStringToFd(data, fd)) {
- LOG(WARNING) << "libdebuggerd_client: Failed to dump wchan data for pid: " << pid;
- }
-}
-
bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms,
unique_fd output_fd) {
pid_t pid = tid;
@@ -119,51 +122,51 @@
android::procinfo::ProcessInfo procinfo;
std::string error;
if (!android::procinfo::GetProcessInfo(tid, &procinfo, &error)) {
- LOG(ERROR) << "libdebugged_client: failed to get process info: " << error;
+ log_error(output_fd, 0, "failed to get process info: %s", error.c_str());
return false;
}
pid = procinfo.pid;
}
- LOG(INFO) << "libdebuggerd_client: started dumping process " << pid;
- unique_fd sockfd;
- const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
- auto time_left = [&end]() { return end - std::chrono::steady_clock::now(); };
- auto set_timeout = [timeout_ms, &time_left](int sockfd) {
- if (timeout_ms <= 0) {
- return sockfd;
- }
+ LOG(INFO) << TAG "started dumping process " << pid;
- auto remaining = time_left();
+ // Rather than try to deal with poll() all the way through the flow, we update
+ // the socket timeout between each step (and only use poll() during the final
+ // copy loop).
+ const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
+ auto update_timeout = [timeout_ms, &output_fd](int sockfd, auto end) {
+ if (timeout_ms <= 0) return true;
+
+ auto remaining = end - std::chrono::steady_clock::now();
if (remaining < decltype(remaining)::zero()) {
- LOG(ERROR) << "libdebuggerd_client: timeout expired";
- return -1;
+ log_error(output_fd, 0, "timeout expired");
+ return false;
}
struct timeval timeout;
populate_timeval(&timeout, remaining);
-
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) {
- PLOG(ERROR) << "libdebuggerd_client: failed to set receive timeout";
- return -1;
+ log_error(output_fd, errno, "failed to set receive timeout");
+ return false;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0) {
- PLOG(ERROR) << "libdebuggerd_client: failed to set send timeout";
- return -1;
+ log_error(output_fd, errno, "failed to set send timeout");
+ return false;
}
-
- return sockfd;
+ return true;
};
- sockfd.reset(socket(AF_LOCAL, SOCK_SEQPACKET, 0));
+ unique_fd sockfd(socket(AF_LOCAL, SOCK_SEQPACKET, 0));
if (sockfd == -1) {
- PLOG(ERROR) << "libdebugger_client: failed to create socket";
+ log_error(output_fd, errno, "failed to create socket");
return false;
}
- if (socket_local_client_connect(set_timeout(sockfd.get()), kTombstonedInterceptSocketName,
+ if (!update_timeout(sockfd, end)) return false;
+
+ if (socket_local_client_connect(sockfd.get(), kTombstonedInterceptSocketName,
ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET) == -1) {
- PLOG(ERROR) << "libdebuggerd_client: failed to connect to tombstoned";
+ log_error(output_fd, errno, "failed to connect to tombstoned");
return false;
}
@@ -171,15 +174,11 @@
.dump_type = dump_type,
.pid = pid,
};
- if (!set_timeout(sockfd)) {
- PLOG(ERROR) << "libdebugger_client: failed to set timeout";
- return false;
- }
// Create an intermediate pipe to pass to the other end.
unique_fd pipe_read, pipe_write;
if (!Pipe(&pipe_read, &pipe_write)) {
- PLOG(ERROR) << "libdebuggerd_client: failed to create pipe";
+ log_error(output_fd, errno, "failed to create pipe");
return false;
}
@@ -194,71 +193,69 @@
}
if (fcntl(pipe_read.get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) {
- PLOG(ERROR) << "failed to set pipe buffer size";
+ log_error(output_fd, errno, "failed to set pipe buffer size");
}
- ssize_t rc = SendFileDescriptors(set_timeout(sockfd), &req, sizeof(req), pipe_write.get());
+ if (!update_timeout(sockfd, end)) return false;
+ ssize_t rc = SendFileDescriptors(sockfd, &req, sizeof(req), pipe_write.get());
pipe_write.reset();
if (rc != sizeof(req)) {
- PLOG(ERROR) << "libdebuggerd_client: failed to send output fd to tombstoned";
+ log_error(output_fd, errno, "failed to send output fd to tombstoned");
return false;
}
+ auto get_response = [&output_fd](const char* kind, int sockfd, InterceptResponse* response) {
+ ssize_t rc = TEMP_FAILURE_RETRY(recv(sockfd, response, sizeof(*response), MSG_TRUNC));
+ if (rc == 0) {
+ log_error(output_fd, 0, "failed to read %s response from tombstoned: timeout reached?", kind);
+ return false;
+ } else if (rc == -1) {
+ log_error(output_fd, errno, "failed to read %s response from tombstoned", kind);
+ return false;
+ } else if (rc != sizeof(*response)) {
+ log_error(output_fd, 0,
+ "received packet of unexpected length from tombstoned while reading %s response: "
+ "expected %zd, received %zd",
+ kind, sizeof(response), rc);
+ return false;
+ }
+ return true;
+ };
+
// Check to make sure we've successfully registered.
InterceptResponse response;
- rc = TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
- if (rc == 0) {
- LOG(ERROR) << "libdebuggerd_client: failed to read initial response from tombstoned: "
- << "timeout reached?";
- return false;
- } else if (rc == -1) {
- PLOG(ERROR) << "libdebuggerd_client: failed to read initial response from tombstoned";
- return false;
- } else if (rc != sizeof(response)) {
- LOG(ERROR) << "libdebuggerd_client: received packet of unexpected length from tombstoned while "
- "reading initial response: expected "
- << sizeof(response) << ", received " << rc;
- return false;
- }
-
+ if (!update_timeout(sockfd, end)) return false;
+ if (!get_response("initial", sockfd, &response)) return false;
if (response.status != InterceptStatus::kRegistered) {
- LOG(ERROR) << "libdebuggerd_client: unexpected registration response: "
- << static_cast<int>(response.status);
+ log_error(output_fd, 0, "unexpected registration response: %d",
+ static_cast<int>(response.status));
return false;
}
- if (!send_signal(tid, dump_type)) {
+ // Send the signal.
+ const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
+ sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0};
+ if (sigqueue(pid, signal, val) != 0) {
+ log_error(output_fd, errno, "failed to send signal to pid %d", pid);
return false;
}
- rc = TEMP_FAILURE_RETRY(recv(set_timeout(sockfd.get()), &response, sizeof(response), MSG_TRUNC));
- if (rc == 0) {
- LOG(ERROR) << "libdebuggerd_client: failed to read status response from tombstoned: "
- "timeout reached?";
- return false;
- } else if (rc == -1) {
- PLOG(ERROR) << "libdebuggerd_client: failed to read status response from tombstoned";
- return false;
- } else if (rc != sizeof(response)) {
- LOG(ERROR) << "libdebuggerd_client: received packet of unexpected length from tombstoned while "
- "reading confirmation response: expected "
- << sizeof(response) << ", received " << rc;
- return false;
- }
-
+ if (!update_timeout(sockfd, end)) return false;
+ if (!get_response("status", sockfd, &response)) return false;
if (response.status != InterceptStatus::kStarted) {
response.error_message[sizeof(response.error_message) - 1] = '\0';
- LOG(ERROR) << "libdebuggerd_client: tombstoned reported failure: " << response.error_message;
+ log_error(output_fd, 0, "tombstoned reported failure: %s", response.error_message);
return false;
}
// Forward output from the pipe to the output fd.
while (true) {
- auto remaining_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_left()).count();
+ auto remaining = end - std::chrono::steady_clock::now();
+ auto remaining_ms = std::chrono::duration_cast<std::chrono::milliseconds>(remaining).count();
if (timeout_ms <= 0) {
remaining_ms = -1;
} else if (remaining_ms < 0) {
- LOG(ERROR) << "libdebuggerd_client: timeout expired";
+ log_error(output_fd, 0, "timeout expired");
return false;
}
@@ -271,11 +268,11 @@
if (errno == EINTR) {
continue;
} else {
- PLOG(ERROR) << "libdebuggerd_client: error while polling";
+ log_error(output_fd, errno, "error while polling");
return false;
}
} else if (rc == 0) {
- LOG(ERROR) << "libdebuggerd_client: timeout expired";
+ log_error(output_fd, 0, "timeout expired");
return false;
}
@@ -285,17 +282,17 @@
// Done.
break;
} else if (rc == -1) {
- PLOG(ERROR) << "libdebuggerd_client: error while reading";
+ log_error(output_fd, errno, "error while reading");
return false;
}
if (!android::base::WriteFully(output_fd.get(), buf, rc)) {
- PLOG(ERROR) << "libdebuggerd_client: error while writing";
+ log_error(output_fd, errno, "error while writing");
return false;
}
}
- LOG(INFO) << "libdebuggerd_client: done dumping process " << pid;
+ LOG(INFO) << TAG "done dumping process " << pid;
return true;
}
@@ -313,14 +310,16 @@
// debuggerd_trigger_dump results in every thread in the process being interrupted
// by a signal, so we need to fetch the wchan data before calling that.
- std::string wchan_data = get_wchan_data(tid);
+ std::string wchan_data = get_wchan_data(fd, tid);
int timeout_ms = timeout_secs > 0 ? timeout_secs * 1000 : 0;
int ret = debuggerd_trigger_dump(tid, dump_type, timeout_ms, std::move(copy)) ? 0 : -1;
// Dump wchan data, since only privileged processes (CAP_SYS_ADMIN) can read
// kernel stack traces (/proc/*/stack).
- dump_wchan_data(wchan_data, fd, tid);
+ if (!WriteStringToFd(wchan_data, fd)) {
+ LOG(WARNING) << TAG "Failed to dump wchan data for pid: " << tid;
+ }
return ret;
}
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 24804d0..abda071 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -58,6 +58,7 @@
#include <scoped_minijail.h>
#include "debuggerd/handler.h"
+#include "libdebuggerd/utility.h"
#include "protocol.h"
#include "tombstoned/tombstoned.h"
#include "util.h"
@@ -526,6 +527,8 @@
std::vector<std::string> log_sources(2);
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
+ // Tag dump only available in the tombstone, not logcat.
+ ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
@@ -597,6 +600,12 @@
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
+ // Tag dump only in tombstone, not logcat, and tagging is not used for
+ // overflow protection in the scudo secondary (guard pages are used instead).
+ if (GetParam() < 0x10000) {
+ ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
+ }
+
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
@@ -637,6 +646,7 @@
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"((^|\s)allocated by thread .*
#00 pc)");
+ ASSERT_MATCH(result, "Memory tags around the fault address");
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -686,6 +696,9 @@
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
+ // Tag dump only in the tombstone, not logcat.
+ ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
+
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, "
@@ -706,21 +719,26 @@
#if defined(__aarch64__)
static uintptr_t CreateTagMapping() {
- uintptr_t mapping =
- reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
- if (reinterpret_cast<void*>(mapping) == MAP_FAILED) {
+ // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right
+ // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE.
+ size_t page_size = getpagesize();
+ void* mapping = mmap(nullptr, page_size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ uintptr_t mapping_uptr = reinterpret_cast<uintptr_t>(mapping);
+ if (mapping == MAP_FAILED) {
return 0;
}
- __asm__ __volatile__(".arch_extension mte; stg %0, [%0]"
- :
- : "r"(mapping + (1ULL << 56))
- : "memory");
- return mapping;
+ mprotect(reinterpret_cast<void*>(mapping_uptr + page_size), page_size,
+ PROT_READ | PROT_WRITE | PROT_MTE);
+ // Stripe the mapping, where even granules get tag '1', and odd granules get tag '0'.
+ for (uintptr_t offset = 0; offset < page_size; offset += 2 * kTagGranuleSize) {
+ uintptr_t tagged_addr = mapping_uptr + page_size + offset + (1ULL << 56);
+ __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(tagged_addr) : "memory");
+ }
+ return mapping_uptr + page_size;
}
#endif
-TEST_F(CrasherTest, mte_tag_dump) {
+TEST_F(CrasherTest, mte_register_tag_dump) {
#if defined(__aarch64__)
if (!mte_supported()) {
GTEST_SKIP() << "Requires MTE";
@@ -753,6 +771,107 @@
#endif
}
+TEST_F(CrasherTest, mte_fault_tag_dump_front_truncated) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+ p[0] = 0; // Untagged pointer, tagged memory.
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ ASSERT_MATCH(result, R"(Memory tags around the fault address.*
+\s*=>0x[0-9a-f]+000:\[1\] 0 1 0)");
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_F(CrasherTest, mte_fault_tag_dump) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+ p[320] = 0; // Untagged pointer, tagged memory.
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ ASSERT_MATCH(result, R"(Memory tags around the fault address.*
+\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
+\s*=>0x[0-9a-f]+: 1 0 1 0 \[1\] 0 1 0 1 0 1 0 1 0 1 0
+\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
+)");
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_F(CrasherTest, mte_fault_tag_dump_rear_truncated) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ size_t page_size = getpagesize();
+ volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+ p[page_size - kTagGranuleSize * 2] = 0; // Untagged pointer, tagged memory.
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ ASSERT_MATCH(result, R"(Memory tags around the fault address)");
+ ASSERT_MATCH(result,
+ R"(\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
+\s*=>0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \[1\] 0
+
+)"); // Ensure truncation happened and there's a newline after the tag fault.
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
TEST_F(CrasherTest, LD_PRELOAD) {
int intercept_result;
unique_fd output_fd;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index c490fb1..24ae169 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -96,4 +96,8 @@
// Number of bytes per MTE granule.
constexpr size_t kTagGranuleSize = 16;
+// Number of rows and columns to display in an MTE tag dump.
+constexpr size_t kNumTagColumns = 16;
+constexpr size_t kNumTagRows = 16;
+
#endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index abd1f12..ff12017 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -362,8 +362,10 @@
dump.set_mapping_name(map_info->name());
}
- char buf[256];
- uint8_t tags[256 / kTagGranuleSize];
+ constexpr size_t kNumBytesAroundRegister = 256;
+ constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize;
+ char buf[kNumBytesAroundRegister];
+ uint8_t tags[kNumTagsAroundRegister];
size_t start_offset = 0;
ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
if (bytes == -1) {
@@ -377,7 +379,19 @@
}
dump.set_memory(buf, bytes);
- dump.set_tags(tags, bytes / kTagGranuleSize);
+
+ bool has_tags = false;
+#if defined(__aarch64__)
+ for (size_t i = 0; i < kNumTagsAroundRegister; ++i) {
+ if (tags[i] != 0) {
+ has_tags = true;
+ }
+ }
+#endif // defined(__aarch64__)
+
+ if (has_tags) {
+ dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister);
+ }
*thread.add_memory_dump() = std::move(dump);
}
@@ -531,6 +545,50 @@
dump_log_file(tombstone, "main", pid);
}
+static void dump_tags_around_fault_addr(Signal* signal, const Tombstone& tombstone,
+ unwindstack::Unwinder* unwinder, uintptr_t fault_addr) {
+ if (tombstone.arch() != Architecture::ARM64) return;
+
+ fault_addr = untag_address(fault_addr);
+ constexpr size_t kNumGranules = kNumTagRows * kNumTagColumns;
+ constexpr size_t kBytesToRead = kNumGranules * kTagGranuleSize;
+
+ // If the low part of the tag dump would underflow to the high address space, it's probably not
+ // a valid address for us to dump tags from.
+ if (fault_addr < kBytesToRead / 2) return;
+
+ unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
+
+ constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1);
+ size_t start_address = (fault_addr & kRowStartMask) - kBytesToRead / 2;
+ MemoryDump tag_dump;
+ size_t granules_to_read = kNumGranules;
+
+ // Attempt to read the first tag. If reading fails, this likely indicates the
+ // lowest touched page is inaccessible or not marked with PROT_MTE.
+ // Fast-forward over pages until one has tags, or we exhaust the search range.
+ while (memory->ReadTag(start_address) < 0) {
+ size_t page_size = sysconf(_SC_PAGE_SIZE);
+ size_t bytes_to_next_page = page_size - (start_address % page_size);
+ if (bytes_to_next_page >= granules_to_read * kTagGranuleSize) return;
+ start_address += bytes_to_next_page;
+ granules_to_read -= bytes_to_next_page / kTagGranuleSize;
+ }
+ tag_dump.set_begin_address(start_address);
+
+ std::string* mte_tags = tag_dump.mutable_arm_mte_metadata()->mutable_memory_tags();
+
+ for (size_t i = 0; i < granules_to_read; ++i) {
+ long tag = memory->ReadTag(start_address + i * kTagGranuleSize);
+ if (tag < 0) break;
+ mte_tags->push_back(static_cast<uint8_t>(tag));
+ }
+
+ if (!mte_tags->empty()) {
+ *signal->mutable_fault_adjacent_metadata() = tag_dump;
+ }
+}
+
static std::optional<uint64_t> read_uptime_secs() {
std::string uptime;
if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
@@ -594,7 +652,9 @@
if (process_info.has_fault_address) {
sig.set_has_fault_address(true);
- sig.set_fault_address(process_info.maybe_tagged_fault_address);
+ uintptr_t fault_addr = process_info.maybe_tagged_fault_address;
+ sig.set_fault_address(fault_addr);
+ dump_tags_around_fault_addr(&sig, result, unwinder, fault_addr);
}
*result.mutable_signal_info() = sig;
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index a932d48..053299a 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -29,6 +29,7 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
+#include <bionic/macros.h>
#include "tombstone.pb.h"
@@ -193,8 +194,11 @@
uint64_t addr = mem.begin_address();
for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) {
uint64_t tagged_addr = addr;
- if (mem.tags().size() > offset / kTagGranuleSize) {
- tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56;
+ if (mem.has_arm_mte_metadata() &&
+ mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) {
+ tagged_addr |=
+ static_cast<uint64_t>(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize])
+ << 56;
}
std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset);
@@ -232,6 +236,60 @@
print_thread_memory_dump(callback, tombstone, thread);
}
+static void print_tag_dump(CallbackType callback, const Tombstone& tombstone) {
+ if (!tombstone.has_signal_info()) return;
+
+ const Signal& signal = tombstone.signal_info();
+
+ if (!signal.has_fault_address() || !signal.has_fault_adjacent_metadata()) {
+ return;
+ }
+
+ const MemoryDump& memory_dump = signal.fault_adjacent_metadata();
+
+ if (!memory_dump.has_arm_mte_metadata() || memory_dump.arm_mte_metadata().memory_tags().empty()) {
+ return;
+ }
+
+ const std::string& tags = memory_dump.arm_mte_metadata().memory_tags();
+
+ CBS("");
+ CBS("Memory tags around the fault address (0x%" PRIx64 "), one tag per %zu bytes:",
+ signal.fault_address(), kTagGranuleSize);
+ constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1);
+
+ size_t tag_index = 0;
+ size_t num_tags = tags.length();
+ uintptr_t fault_granule = untag_address(signal.fault_address()) & ~(kTagGranuleSize - 1);
+ for (size_t row = 0; tag_index < num_tags; ++row) {
+ uintptr_t row_addr =
+ (memory_dump.begin_address() + row * kNumTagColumns * kTagGranuleSize) & kRowStartMask;
+ std::string row_contents;
+ bool row_has_fault = false;
+
+ for (size_t column = 0; column < kNumTagColumns; ++column) {
+ uintptr_t granule_addr = row_addr + column * kTagGranuleSize;
+ if (granule_addr < memory_dump.begin_address() ||
+ granule_addr >= memory_dump.begin_address() + num_tags * kTagGranuleSize) {
+ row_contents += " . ";
+ } else if (granule_addr == fault_granule) {
+ row_contents += StringPrintf("[%1hhx]", tags[tag_index++]);
+ row_has_fault = true;
+ } else {
+ row_contents += StringPrintf(" %1hhx ", tags[tag_index++]);
+ }
+ }
+
+ if (row_contents.back() == ' ') row_contents.pop_back();
+
+ if (row_has_fault) {
+ CBS(" =>0x%" PRIxPTR ":%s", row_addr, row_contents.c_str());
+ } else {
+ CBS(" 0x%" PRIxPTR ":%s", row_addr, row_contents.c_str());
+ }
+ }
+}
+
static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
const Thread& thread) {
print_thread_header(callback, tombstone, thread, true);
@@ -299,6 +357,8 @@
}
}
+ print_tag_dump(callback, tombstone);
+
print_thread_memory_dump(callback, tombstone, thread);
CBS("");
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 2c645b5..a7506b7 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -402,6 +402,8 @@
case TRAP_HWBKPT: return "TRAP_HWBKPT";
case TRAP_UNK:
return "TRAP_UNDIAGNOSED";
+ case TRAP_PERF:
+ return "TRAP_PERF";
}
if ((si->si_code & 0xff) == SIGTRAP) {
switch ((si->si_code >> 8) & 0xff) {
@@ -423,7 +425,7 @@
return "PTRACE_EVENT_STOP";
}
}
- static_assert(NSIGTRAP == TRAP_UNK, "missing TRAP_* si_code");
+ static_assert(NSIGTRAP == TRAP_PERF, "missing TRAP_* si_code");
break;
}
// Then the other codes...
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index 22fc30e..a701212 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -56,8 +56,11 @@
bool has_fault_address = 8;
uint64 fault_address = 9;
+ // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we
+ // only include metadata, and not the contents.
+ MemoryDump fault_adjacent_metadata = 10;
- reserved 10 to 999;
+ reserved 11 to 999;
}
message HeapObject {
@@ -142,14 +145,22 @@
reserved 9 to 999;
}
+message ArmMTEMetadata {
+ // One memory tag per granule (e.g. every 16 bytes) of regular memory.
+ bytes memory_tags = 1;
+ reserved 2 to 999;
+}
+
message MemoryDump {
string register_name = 1;
string mapping_name = 2;
uint64 begin_address = 3;
bytes memory = 4;
- bytes tags = 5;
+ oneof metadata {
+ ArmMTEMetadata arm_mte_metadata = 6;
+ }
- reserved 6 to 999;
+ reserved 5, 7 to 999;
}
message MemoryMapping {
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index e3ef232..75d1e0d 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -91,12 +91,6 @@
if (key == bootconfig_key) {
*out_val = value;
return true;
- } else if (android_key == "hardware" && android_key == key) {
- // bootconfig doesn't allow subkeys and values to coexist, so
- // "androidboot.hardware" cannot be used. It is replaced in
- // bootconfig with "hardware"
- *out_val = value;
- return true;
}
}
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index aa1f415..acfaa84 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -311,7 +311,6 @@
"android.hardware.boot@1.0",
"android.hardware.boot@1.1",
"libbase",
- "libbinder",
"libext2_uuid",
"libext4_utils",
"libfs_mgr_binder",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index c0649ca..e2abdba 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -191,6 +191,9 @@
// Merge failure code, filled if state == MergeFailed.
MergeFailureCode merge_failure_code = 7;
+
+ // Source build fingerprint.
+ string source_build_fingerprint = 8;
}
// Next: 10
@@ -223,4 +226,7 @@
// Merge failure code, filled if the merge failed at any time (regardless
// of whether it succeeded at a later time).
MergeFailureCode merge_failure_code = 9;
+
+ // The source fingerprint at the time the OTA was downloaded.
+ string source_build_fingerprint = 10;
}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index b75b154..7f7e40a 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -981,6 +981,137 @@
ASSERT_EQ(num_clusters, 1);
}
+TEST_F(CowTest, BigSeqOp) {
+ CowOptions options;
+ CowWriter writer(options);
+ const int seq_len = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t) + 1;
+ uint32_t sequence[seq_len];
+ for (int i = 0; i < seq_len; i++) {
+ sequence[i] = i + 1;
+ }
+
+ ASSERT_TRUE(writer.Initialize(cow_->fd));
+
+ ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence));
+ ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len));
+ ASSERT_TRUE(writer.Finalize());
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ auto iter = reader.GetRevMergeOpIter();
+
+ for (int i = 0; i < seq_len; i++) {
+ ASSERT_TRUE(!iter->Done());
+ const auto& op = iter->Get();
+
+ ASSERT_EQ(op.new_block, seq_len - i);
+
+ iter->Next();
+ }
+ ASSERT_TRUE(iter->Done());
+}
+
+TEST_F(CowTest, RevMergeOpItrTest) {
+ CowOptions options;
+ options.cluster_ops = 5;
+ options.num_merge_ops = 1;
+ CowWriter writer(options);
+ uint32_t sequence[] = {2, 10, 6, 7, 3, 5};
+
+ ASSERT_TRUE(writer.Initialize(cow_->fd));
+
+ ASSERT_TRUE(writer.AddSequenceData(6, sequence));
+ ASSERT_TRUE(writer.AddCopy(6, 3));
+ ASSERT_TRUE(writer.AddZeroBlocks(12, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(8, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(11, 1));
+ ASSERT_TRUE(writer.AddCopy(3, 5));
+ ASSERT_TRUE(writer.AddCopy(2, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(4, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(9, 1));
+ ASSERT_TRUE(writer.AddCopy(5, 6));
+ ASSERT_TRUE(writer.AddZeroBlocks(1, 1));
+ ASSERT_TRUE(writer.AddCopy(10, 2));
+ ASSERT_TRUE(writer.AddCopy(7, 4));
+ ASSERT_TRUE(writer.Finalize());
+
+ // New block in cow order is 6, 12, 8, 11, 3, 2, 4, 9, 5, 1, 10, 7
+ // New block in merge order is 2, 10, 6, 7, 3, 5, 12, 11, 9, 8, 4, 1
+ // RevMergeOrder is 1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10, 2
+ // new block 2 is "already merged", so will be left out.
+
+ std::vector<uint64_t> revMergeOpSequence = {1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10};
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ auto iter = reader.GetRevMergeOpIter();
+ auto expected_new_block = revMergeOpSequence.begin();
+
+ while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+ const auto& op = iter->Get();
+
+ ASSERT_EQ(op.new_block, *expected_new_block);
+
+ iter->Next();
+ expected_new_block++;
+ }
+ ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
+ ASSERT_TRUE(iter->Done());
+}
+
+TEST_F(CowTest, LegacyRevMergeOpItrTest) {
+ CowOptions options;
+ options.cluster_ops = 5;
+ options.num_merge_ops = 1;
+ CowWriter writer(options);
+
+ ASSERT_TRUE(writer.Initialize(cow_->fd));
+
+ ASSERT_TRUE(writer.AddCopy(2, 1));
+ ASSERT_TRUE(writer.AddCopy(10, 2));
+ ASSERT_TRUE(writer.AddCopy(6, 3));
+ ASSERT_TRUE(writer.AddCopy(7, 4));
+ ASSERT_TRUE(writer.AddCopy(3, 5));
+ ASSERT_TRUE(writer.AddCopy(5, 6));
+ ASSERT_TRUE(writer.AddZeroBlocks(12, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(8, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(11, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(4, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(9, 1));
+ ASSERT_TRUE(writer.AddZeroBlocks(1, 1));
+
+ ASSERT_TRUE(writer.Finalize());
+
+ // New block in cow order is 2, 10, 6, 7, 3, 5, 12, 8, 11, 4, 9, 1
+ // New block in merge order is 2, 10, 6, 7, 3, 5, 12, 11, 9, 8, 4, 1
+ // RevMergeOrder is 1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10, 2
+ // new block 2 is "already merged", so will be left out.
+
+ std::vector<uint64_t> revMergeOpSequence = {1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10};
+
+ ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+ CowReader reader;
+ ASSERT_TRUE(reader.Parse(cow_->fd));
+ auto iter = reader.GetRevMergeOpIter();
+ auto expected_new_block = revMergeOpSequence.begin();
+
+ while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+ const auto& op = iter->Get();
+
+ ASSERT_EQ(op.new_block, *expected_new_block);
+
+ iter->Next();
+ expected_new_block++;
+ }
+ ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
+ ASSERT_TRUE(iter->Done());
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp
index 0753c49..3085f80 100644
--- a/fs_mgr/libsnapshot/cow_format.cpp
+++ b/fs_mgr/libsnapshot/cow_format.cpp
@@ -37,6 +37,8 @@
os << "kCowLabelOp, ";
else if (op.type == kCowClusterOp)
os << "kCowClusterOp ";
+ else if (op.type == kCowSequenceOp)
+ os << "kCowSequenceOp ";
else if (op.type == kCowFooterOp)
os << "kCowFooterOp ";
else
@@ -81,6 +83,16 @@
case kCowLabelOp:
case kCowClusterOp:
case kCowFooterOp:
+ case kCowSequenceOp:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool IsOrderedOp(const CowOperation& op) {
+ switch (op.type) {
+ case kCowCopyOp:
return true;
default:
return false;
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 2349e4a..b3198a0 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -19,6 +19,9 @@
#include <limits>
#include <optional>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include <android-base/file.h>
@@ -127,7 +130,10 @@
return false;
}
- return ParseOps(label);
+ if (!ParseOps(label)) {
+ return false;
+ }
+ return PrepMergeOps();
}
bool CowReader::ParseOps(std::optional<uint64_t> label) {
@@ -201,6 +207,8 @@
current_op_num--;
done = true;
break;
+ } else if (current_op.type == kCowSequenceOp) {
+ has_seq_ops_ = true;
}
}
@@ -251,7 +259,7 @@
LOG(ERROR) << "ops checksum does not match";
return false;
}
- SHA256(ops_buffer.get()->data(), footer_->op.ops_size, csum);
+ SHA256(ops_buffer->data(), footer_->op.ops_size, csum);
if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
LOG(ERROR) << "ops checksum does not match";
return false;
@@ -264,138 +272,165 @@
return true;
}
-void CowReader::InitializeMerge() {
- uint64_t num_copy_ops = 0;
+//
+// This sets up the data needed for MergeOpIter. MergeOpIter presents
+// data in the order we intend to merge in.
+//
+// We merge all order sensitive ops up front, and sort the rest to allow for
+// batch merging. Order sensitive ops can either be presented in their proper
+// order in the cow, or be ordered by sequence ops (kCowSequenceOp), in which
+// case we want to merge those ops first, followed by any ops not specified by
+// new_block value by the sequence op, in sorted order.
+// We will re-arrange the vector in such a way that
+// kernel can batch merge. Ex:
+//
+// Existing COW format; All the copy operations
+// are at the beginning.
+// =======================================
+// Copy-op-1 - cow_op->new_block = 1
+// Copy-op-2 - cow_op->new_block = 2
+// Copy-op-3 - cow_op->new_block = 3
+// Replace-op-4 - cow_op->new_block = 6
+// Replace-op-5 - cow_op->new_block = 4
+// Replace-op-6 - cow_op->new_block = 8
+// Replace-op-7 - cow_op->new_block = 9
+// Zero-op-8 - cow_op->new_block = 7
+// Zero-op-9 - cow_op->new_block = 5
+// =======================================
+//
+// First find the operation which isn't a copy-op
+// and then sort all the operations in descending order
+// with the key being cow_op->new_block (source block)
+//
+// The data-structure will look like:
+//
+// =======================================
+// Copy-op-1 - cow_op->new_block = 1
+// Copy-op-2 - cow_op->new_block = 2
+// Copy-op-3 - cow_op->new_block = 3
+// Replace-op-7 - cow_op->new_block = 9
+// Replace-op-6 - cow_op->new_block = 8
+// Zero-op-8 - cow_op->new_block = 7
+// Replace-op-4 - cow_op->new_block = 6
+// Zero-op-9 - cow_op->new_block = 5
+// Replace-op-5 - cow_op->new_block = 4
+// =======================================
+//
+// Daemon will read the above data-structure in reverse-order
+// when reading metadata. Thus, kernel will get the metadata
+// in the following order:
+//
+// ========================================
+// Replace-op-5 - cow_op->new_block = 4
+// Zero-op-9 - cow_op->new_block = 5
+// Replace-op-4 - cow_op->new_block = 6
+// Zero-op-8 - cow_op->new_block = 7
+// Replace-op-6 - cow_op->new_block = 8
+// Replace-op-7 - cow_op->new_block = 9
+// Copy-op-3 - cow_op->new_block = 3
+// Copy-op-2 - cow_op->new_block = 2
+// Copy-op-1 - cow_op->new_block = 1
+// ===========================================
+//
+// When merging begins, kernel will start from the last
+// metadata which was read: In the above format, Copy-op-1
+// will be the first merge operation.
+//
+// Now, batching of the merge operations happens only when
+// 1: origin block numbers in the base device are contiguous
+// (cow_op->new_block) and,
+// 2: cow block numbers which are assigned by daemon in ReadMetadata()
+// are contiguous. These are monotonically increasing numbers.
+//
+// When both (1) and (2) are true, kernel will batch merge the operations.
+// In the above case, we have to ensure that the copy operations
+// are merged first before replace operations are done. Hence,
+// we will not change the order of copy operations. Since,
+// cow_op->new_block numbers are contiguous, we will ensure that the
+// cow block numbers assigned in ReadMetadata() for these respective copy
+// operations are not contiguous forcing kernel to issue merge for each
+// copy operations without batch merging.
+//
+// For all the other operations viz. Replace and Zero op, the cow block
+// numbers assigned by daemon will be contiguous allowing kernel to batch
+// merge.
+//
+// The final format after assiging COW block numbers by the daemon will
+// look something like:
+//
+// =========================================================
+// Replace-op-5 - cow_op->new_block = 4 cow-block-num = 2
+// Zero-op-9 - cow_op->new_block = 5 cow-block-num = 3
+// Replace-op-4 - cow_op->new_block = 6 cow-block-num = 4
+// Zero-op-8 - cow_op->new_block = 7 cow-block-num = 5
+// Replace-op-6 - cow_op->new_block = 8 cow-block-num = 6
+// Replace-op-7 - cow_op->new_block = 9 cow-block-num = 7
+// Copy-op-3 - cow_op->new_block = 3 cow-block-num = 9
+// Copy-op-2 - cow_op->new_block = 2 cow-block-num = 11
+// Copy-op-1 - cow_op->new_block = 1 cow-block-num = 13
+// ==========================================================
+//
+// Merge sequence will look like:
+//
+// Merge-1 - Batch-merge { Copy-op-1, Copy-op-2, Copy-op-3 }
+// Merge-2 - Batch-merge {Replace-op-7, Replace-op-6, Zero-op-8,
+// Replace-op-4, Zero-op-9, Replace-op-5 }
+//==============================================================
+bool CowReader::PrepMergeOps() {
+ auto merge_op_blocks = std::make_shared<std::vector<uint32_t>>();
+ std::set<int, std::greater<int>> other_ops;
+ auto seq_ops_set = std::unordered_set<uint32_t>();
+ auto block_map = std::make_shared<std::unordered_map<uint32_t, int>>();
+ int num_seqs = 0;
+ size_t read;
- // Remove all the metadata operations
- ops_->erase(std::remove_if(ops_.get()->begin(), ops_.get()->end(),
- [](CowOperation& op) { return IsMetadataOp(op); }),
- ops_.get()->end());
-
- set_total_data_ops(ops_->size());
- // We will re-arrange the vector in such a way that
- // kernel can batch merge. Ex:
- //
- // Existing COW format; All the copy operations
- // are at the beginning.
- // =======================================
- // Copy-op-1 - cow_op->new_block = 1
- // Copy-op-2 - cow_op->new_block = 2
- // Copy-op-3 - cow_op->new_block = 3
- // Replace-op-4 - cow_op->new_block = 6
- // Replace-op-5 - cow_op->new_block = 4
- // Replace-op-6 - cow_op->new_block = 8
- // Replace-op-7 - cow_op->new_block = 9
- // Zero-op-8 - cow_op->new_block = 7
- // Zero-op-9 - cow_op->new_block = 5
- // =======================================
- //
- // First find the operation which isn't a copy-op
- // and then sort all the operations in descending order
- // with the key being cow_op->new_block (source block)
- //
- // The data-structure will look like:
- //
- // =======================================
- // Copy-op-1 - cow_op->new_block = 1
- // Copy-op-2 - cow_op->new_block = 2
- // Copy-op-3 - cow_op->new_block = 3
- // Replace-op-7 - cow_op->new_block = 9
- // Replace-op-6 - cow_op->new_block = 8
- // Zero-op-8 - cow_op->new_block = 7
- // Replace-op-4 - cow_op->new_block = 6
- // Zero-op-9 - cow_op->new_block = 5
- // Replace-op-5 - cow_op->new_block = 4
- // =======================================
- //
- // Daemon will read the above data-structure in reverse-order
- // when reading metadata. Thus, kernel will get the metadata
- // in the following order:
- //
- // ========================================
- // Replace-op-5 - cow_op->new_block = 4
- // Zero-op-9 - cow_op->new_block = 5
- // Replace-op-4 - cow_op->new_block = 6
- // Zero-op-8 - cow_op->new_block = 7
- // Replace-op-6 - cow_op->new_block = 8
- // Replace-op-7 - cow_op->new_block = 9
- // Copy-op-3 - cow_op->new_block = 3
- // Copy-op-2 - cow_op->new_block = 2
- // Copy-op-1 - cow_op->new_block = 1
- // ===========================================
- //
- // When merging begins, kernel will start from the last
- // metadata which was read: In the above format, Copy-op-1
- // will be the first merge operation.
- //
- // Now, batching of the merge operations happens only when
- // 1: origin block numbers in the base device are contiguous
- // (cow_op->new_block) and,
- // 2: cow block numbers which are assigned by daemon in ReadMetadata()
- // are contiguous. These are monotonically increasing numbers.
- //
- // When both (1) and (2) are true, kernel will batch merge the operations.
- // In the above case, we have to ensure that the copy operations
- // are merged first before replace operations are done. Hence,
- // we will not change the order of copy operations. Since,
- // cow_op->new_block numbers are contiguous, we will ensure that the
- // cow block numbers assigned in ReadMetadata() for these respective copy
- // operations are not contiguous forcing kernel to issue merge for each
- // copy operations without batch merging.
- //
- // For all the other operations viz. Replace and Zero op, the cow block
- // numbers assigned by daemon will be contiguous allowing kernel to batch
- // merge.
- //
- // The final format after assiging COW block numbers by the daemon will
- // look something like:
- //
- // =========================================================
- // Replace-op-5 - cow_op->new_block = 4 cow-block-num = 2
- // Zero-op-9 - cow_op->new_block = 5 cow-block-num = 3
- // Replace-op-4 - cow_op->new_block = 6 cow-block-num = 4
- // Zero-op-8 - cow_op->new_block = 7 cow-block-num = 5
- // Replace-op-6 - cow_op->new_block = 8 cow-block-num = 6
- // Replace-op-7 - cow_op->new_block = 9 cow-block-num = 7
- // Copy-op-3 - cow_op->new_block = 3 cow-block-num = 9
- // Copy-op-2 - cow_op->new_block = 2 cow-block-num = 11
- // Copy-op-1 - cow_op->new_block = 1 cow-block-num = 13
- // ==========================================================
- //
- // Merge sequence will look like:
- //
- // Merge-1 - Batch-merge { Copy-op-1, Copy-op-2, Copy-op-3 }
- // Merge-2 - Batch-merge {Replace-op-7, Replace-op-6, Zero-op-8,
- // Replace-op-4, Zero-op-9, Replace-op-5 }
- //==============================================================
-
- num_copy_ops = FindNumCopyops();
-
- std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
- [](CowOperation& op1, CowOperation& op2) -> bool {
- return op1.new_block > op2.new_block;
- });
-
- if (header_.num_merge_ops > 0) {
- ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
- }
-
- num_copy_ops = FindNumCopyops();
- set_copy_ops(num_copy_ops);
-}
-
-uint64_t CowReader::FindNumCopyops() {
- uint64_t num_copy_ops = 0;
-
- for (uint64_t i = 0; i < ops_->size(); i++) {
+ for (int i = 0; i < ops_->size(); i++) {
auto& current_op = ops_->data()[i];
- if (current_op.type != kCowCopyOp) {
- break;
+
+ if (current_op.type == kCowSequenceOp) {
+ size_t seq_len = current_op.data_length / sizeof(uint32_t);
+
+ merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
+ if (!GetRawBytes(current_op.source, &merge_op_blocks->data()[num_seqs],
+ current_op.data_length, &read)) {
+ PLOG(ERROR) << "Failed to read sequence op!";
+ return false;
+ }
+ for (int j = num_seqs; j < num_seqs + seq_len; j++) {
+ seq_ops_set.insert(merge_op_blocks->data()[j]);
+ }
+ num_seqs += seq_len;
}
- num_copy_ops += 1;
+
+ if (IsMetadataOp(current_op)) {
+ continue;
+ }
+
+ if (!has_seq_ops_ && IsOrderedOp(current_op)) {
+ merge_op_blocks->emplace_back(current_op.new_block);
+ } else if (seq_ops_set.count(current_op.new_block) == 0) {
+ other_ops.insert(current_op.new_block);
+ }
+ block_map->insert({current_op.new_block, i});
+ }
+ if (merge_op_blocks->size() > header_.num_merge_ops)
+ num_ordered_ops_to_merge_ = merge_op_blocks->size() - header_.num_merge_ops;
+ else
+ num_ordered_ops_to_merge_ = 0;
+ merge_op_blocks->reserve(merge_op_blocks->size() + other_ops.size());
+ for (auto block : other_ops) {
+ merge_op_blocks->emplace_back(block);
}
- return num_copy_ops;
+ num_total_data_ops_ = merge_op_blocks->size();
+ if (header_.num_merge_ops > 0) {
+ merge_op_blocks->erase(merge_op_blocks->begin(),
+ merge_op_blocks->begin() + header_.num_merge_ops);
+ }
+
+ block_map_ = block_map;
+ merge_op_blocks_ = merge_op_blocks;
+ return true;
}
bool CowReader::GetHeader(CowHeader* header) {
@@ -430,11 +465,11 @@
CowOpIter::CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops) {
ops_ = ops;
- op_iter_ = ops_.get()->begin();
+ op_iter_ = ops_->begin();
}
bool CowOpIter::Done() {
- return op_iter_ == ops_.get()->end();
+ return op_iter_ == ops_->end();
}
void CowOpIter::Next() {
@@ -447,9 +482,11 @@
return (*op_iter_);
}
-class CowOpReverseIter final : public ICowOpReverseIter {
+class CowRevMergeOpIter final : public ICowOpIter {
public:
- explicit CowOpReverseIter(std::shared_ptr<std::vector<CowOperation>> ops);
+ explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map);
bool Done() override;
const CowOperation& Get() override;
@@ -457,34 +494,41 @@
private:
std::shared_ptr<std::vector<CowOperation>> ops_;
- std::vector<CowOperation>::reverse_iterator op_riter_;
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
+ std::vector<uint32_t>::reverse_iterator block_riter_;
};
-CowOpReverseIter::CowOpReverseIter(std::shared_ptr<std::vector<CowOperation>> ops) {
+CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
+ std::shared_ptr<std::unordered_map<uint32_t, int>> map) {
ops_ = ops;
- op_riter_ = ops_.get()->rbegin();
+ merge_op_blocks_ = merge_op_blocks;
+ map_ = map;
+
+ block_riter_ = merge_op_blocks->rbegin();
}
-bool CowOpReverseIter::Done() {
- return op_riter_ == ops_.get()->rend();
+bool CowRevMergeOpIter::Done() {
+ return block_riter_ == merge_op_blocks_->rend();
}
-void CowOpReverseIter::Next() {
+void CowRevMergeOpIter::Next() {
CHECK(!Done());
- op_riter_++;
+ block_riter_++;
}
-const CowOperation& CowOpReverseIter::Get() {
+const CowOperation& CowRevMergeOpIter::Get() {
CHECK(!Done());
- return (*op_riter_);
+ return ops_->data()[map_->at(*block_riter_)];
}
std::unique_ptr<ICowOpIter> CowReader::GetOpIter() {
return std::make_unique<CowOpIter>(ops_);
}
-std::unique_ptr<ICowOpReverseIter> CowReader::GetRevOpIter() {
- return std::make_unique<CowOpReverseIter>(ops_);
+std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter() {
+ return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_);
}
bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 526fede..ef30e32 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -58,8 +58,8 @@
return EmitRawBlocks(new_block_start, data, size);
}
-bool AddXorBlocks(uint32_t /*new_block_start*/, const void* /*data*/, size_t /*size*/,
- uint32_t /*old_block*/, uint16_t /*offset*/) {
+bool ICowWriter::AddXorBlocks(uint32_t /*new_block_start*/, const void* /*data*/, size_t /*size*/,
+ uint32_t /*old_block*/, uint16_t /*offset*/) {
LOG(ERROR) << "AddXorBlocks not yet implemented";
return false;
}
@@ -76,9 +76,8 @@
return EmitLabel(label);
}
-bool ICowWriter::AddSequenceData(size_t /*num_ops*/, const uint32_t* /*data*/) {
- LOG(ERROR) << "AddSequenceData not yet implemented";
- return false;
+bool ICowWriter::AddSequenceData(size_t num_ops, const uint32_t* data) {
+ return EmitSequenceData(num_ops, data);
}
bool ICowWriter::ValidateNewBlock(uint64_t new_block) {
@@ -103,7 +102,7 @@
header_.footer_size = sizeof(CowFooter);
header_.op_size = sizeof(CowOperation);
header_.block_size = options_.block_size;
- header_.num_merge_ops = 0;
+ header_.num_merge_ops = options_.num_merge_ops;
header_.cluster_ops = options_.cluster_ops;
header_.buffer_size = 0;
footer_ = {};
@@ -337,6 +336,26 @@
return WriteOperation(op) && Sync();
}
+bool CowWriter::EmitSequenceData(size_t num_ops, const uint32_t* data) {
+ CHECK(!merge_in_progress_);
+ size_t to_add = 0;
+ size_t max_ops = std::numeric_limits<uint16_t>::max() / sizeof(uint32_t);
+ while (num_ops > 0) {
+ CowOperation op = {};
+ op.type = kCowSequenceOp;
+ op.source = next_data_pos_;
+ to_add = std::min(num_ops, max_ops);
+ op.data_length = static_cast<uint16_t>(to_add * sizeof(uint32_t));
+ if (!WriteOperation(op, data, op.data_length)) {
+ PLOG(ERROR) << "AddSequenceData: write failed";
+ return false;
+ }
+ num_ops -= to_add;
+ data += to_add;
+ }
+ return true;
+}
+
bool CowWriter::EmitCluster() {
CowOperation op = {};
op.type = kCowClusterOp;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 000e5e1..464046b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -148,6 +148,7 @@
static constexpr uint8_t kCowZeroOp = 3;
static constexpr uint8_t kCowLabelOp = 4;
static constexpr uint8_t kCowClusterOp = 5;
+static constexpr uint8_t kCowSequenceOp = 7;
static constexpr uint8_t kCowFooterOp = -1;
static constexpr uint8_t kCowCompressNone = 0;
@@ -184,7 +185,10 @@
int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_size);
int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_size);
+// Ops that are internal to the Cow Format and not OTA data
bool IsMetadataOp(const CowOperation& op);
+// Ops that have dependencies on old blocks, and must take care in their merge order
+bool IsOrderedOp(const CowOperation& op);
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 669e58a..05f7066 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -19,6 +19,7 @@
#include <functional>
#include <memory>
#include <optional>
+#include <unordered_map>
#include <android-base/unique_fd.h>
#include <libsnapshot/cow_format.h>
@@ -27,7 +28,6 @@
namespace snapshot {
class ICowOpIter;
-class ICowOpReverseIter;
// A ByteSink object handles requests for a buffer of a specific size. It
// always owns the underlying buffer. It's designed to minimize potential
@@ -75,8 +75,8 @@
// Return an iterator for retrieving CowOperation entries.
virtual std::unique_ptr<ICowOpIter> GetOpIter() = 0;
- // Return an reverse iterator for retrieving CowOperation entries.
- virtual std::unique_ptr<ICowOpReverseIter> GetRevOpIter() = 0;
+ // Return an iterator for retrieving CowOperation entries in merge order
+ virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter() = 0;
// Get decoded bytes from the data section, handling any decompression.
// All retrieved data is passed to the sink.
@@ -98,21 +98,6 @@
virtual void Next() = 0;
};
-// Reverse Iterate over a sequence of COW operations.
-class ICowOpReverseIter {
- public:
- virtual ~ICowOpReverseIter() {}
-
- // True if there are more items to read, false otherwise.
- virtual bool Done() = 0;
-
- // Read the current operation.
- virtual const CowOperation& Get() = 0;
-
- // Advance to the next item.
- virtual void Next() = 0;
-};
-
class CowReader : public ICowReader {
public:
CowReader();
@@ -135,25 +120,21 @@
// whose lifetime depends on the CowOpIter object; the return
// value of these will never be null.
std::unique_ptr<ICowOpIter> GetOpIter() override;
- std::unique_ptr<ICowOpReverseIter> GetRevOpIter() override;
+ std::unique_ptr<ICowOpIter> GetRevMergeOpIter() override;
bool ReadData(const CowOperation& op, IByteSink* sink) override;
bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
- void InitializeMerge();
+ uint64_t get_num_total_data_ops() { return num_total_data_ops_; }
- // Number of copy, replace, and zero ops. Set if InitializeMerge is called.
- void set_total_data_ops(uint64_t size) { total_data_ops_ = size; }
- uint64_t total_data_ops() { return total_data_ops_; }
- // Number of copy ops. Set if InitializeMerge is called.
- void set_copy_ops(uint64_t size) { copy_ops_ = size; }
- uint64_t total_copy_ops() { return copy_ops_; }
+ uint64_t get_num_ordered_ops_to_merge() { return num_ordered_ops_to_merge_; }
void CloseCowFd() { owned_fd_ = {}; }
private:
bool ParseOps(std::optional<uint64_t> label);
+ bool PrepMergeOps();
uint64_t FindNumCopyops();
android::base::unique_fd owned_fd_;
@@ -163,8 +144,11 @@
uint64_t fd_size_;
std::optional<uint64_t> last_label_;
std::shared_ptr<std::vector<CowOperation>> ops_;
- uint64_t total_data_ops_;
- uint64_t copy_ops_;
+ std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
+ std::shared_ptr<std::unordered_map<uint32_t, int>> block_map_;
+ uint64_t num_total_data_ops_;
+ uint64_t num_ordered_ops_to_merge_;
+ bool has_seq_ops_;
};
} // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index fbe6461..4a807fb 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -38,6 +38,9 @@
uint32_t cluster_ops = 200;
bool scratch_space = true;
+
+ // Preset the number of merged ops. Only useful for testing.
+ uint64_t num_merge_ops = 0;
};
// Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -85,6 +88,7 @@
virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0;
virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0;
virtual bool EmitLabel(uint64_t label) = 0;
+ virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) = 0;
bool ValidateNewBlock(uint64_t new_block);
@@ -120,6 +124,7 @@
virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
virtual bool EmitLabel(uint64_t label) override;
+ virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
private:
bool EmitCluster();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 94d5055..ec58cca 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -60,6 +60,7 @@
MOCK_METHOD(bool, Dump, (std::ostream & os), (override));
MOCK_METHOD(std::unique_ptr<AutoDevice>, EnsureMetadataMounted, (), (override));
MOCK_METHOD(ISnapshotMergeStats*, GetSnapshotMergeStatsInstance, (), (override));
+ MOCK_METHOD(std::string, ReadSourceBuildFingerprint, (), (override));
};
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
index 067f99c..3d384cc 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -35,13 +35,16 @@
MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
+ MOCK_METHOD(void, set_source_build_fingerprint, (const std::string&), (override));
MOCK_METHOD(uint64_t, cow_file_size, (), (override));
MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override));
MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override));
MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override));
MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override));
+ MOCK_METHOD(std::string, source_build_fingerprint, (), (override));
MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
+ MOCK_METHOD(bool, WriteState, (), (override));
using ISnapshotMergeStats::Result;
// Return nullptr if any failure.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
new file mode 100644
index 0000000..0457986
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
@@ -0,0 +1,52 @@
+//
+// 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 <gmock/gmock.h>
+#include <libsnapshot/snapshot_writer.h>
+
+namespace android::snapshot {
+
+class MockSnapshotWriter : public ISnapshotWriter {
+ public:
+ using FileDescriptor = ISnapshotWriter::FileDescriptor;
+
+ explicit MockSnapshotWriter(const CowOptions& options) : ISnapshotWriter(options) {}
+ MockSnapshotWriter() : ISnapshotWriter({}) {}
+
+ MOCK_METHOD(bool, Finalize, (), (override));
+
+ // Return number of bytes the cow image occupies on disk.
+ MOCK_METHOD(uint64_t, GetCowSize, (), (override));
+
+ // Returns true if AddCopy() operations are supported.
+ MOCK_METHOD(bool, SupportsCopyOperation, (), (const override));
+
+ MOCK_METHOD(bool, EmitCopy, (uint64_t, uint64_t), (override));
+ MOCK_METHOD(bool, EmitRawBlocks, (uint64_t, const void*, size_t), (override));
+ MOCK_METHOD(bool, EmitZeroBlocks, (uint64_t, uint64_t), (override));
+ MOCK_METHOD(bool, EmitLabel, (uint64_t), (override));
+ MOCK_METHOD(bool, EmitSequenceData, (size_t, const uint32_t*), (override));
+
+ // Open the writer in write mode (no append).
+ MOCK_METHOD(bool, Initialize, (), (override));
+
+ // Open the writer in append mode, with the last label to resume
+ // from. See CowWriter::InitializeAppend.
+ MOCK_METHOD(bool, InitializeAppend, (uint64_t label), (override));
+
+ MOCK_METHOD(std::unique_ptr<FileDescriptor>, OpenReader, (), (override));
+};
+} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 65034f7..15882b3 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -177,6 +177,9 @@
// code. Otherwise, MergeFailureCode::Ok is returned.
virtual MergeFailureCode ReadMergeFailureCode() = 0;
+ // If an update is in progress, return the source build fingerprint.
+ virtual std::string ReadSourceBuildFingerprint() = 0;
+
// Find the status of the current update, if any.
//
// |progress| depends on the returned status:
@@ -369,6 +372,7 @@
ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override;
bool UnmapAllSnapshots() override;
+ std::string ReadSourceBuildFingerprint() override;
// We can't use WaitForFile during first-stage init, because ueventd is not
// running and therefore will not automatically create symlinks. Instead,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index 4ce5077..8c2fec7 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -35,12 +35,14 @@
virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
virtual void set_merge_failure_code(MergeFailureCode code) = 0;
+ virtual void set_source_build_fingerprint(const std::string& fingerprint) = 0;
virtual uint64_t cow_file_size() = 0;
virtual uint64_t total_cow_size_bytes() = 0;
virtual uint64_t estimated_cow_size_bytes() = 0;
virtual uint32_t boot_complete_time_ms() = 0;
virtual uint32_t boot_complete_to_merge_start_time_ms() = 0;
virtual MergeFailureCode merge_failure_code() = 0;
+ virtual std::string source_build_fingerprint() = 0;
// Called when merge ends. Properly clean up permanent storage.
class Result {
@@ -52,6 +54,10 @@
};
// Return nullptr if any failure.
virtual std::unique_ptr<Result> Finish() = 0;
+
+ // Write out the current state. This should be called when data might be lost that
+ // cannot be recovered (eg the COW sizes).
+ virtual bool WriteState() = 0;
};
class SnapshotMergeStats : public ISnapshotMergeStats {
@@ -74,11 +80,13 @@
uint32_t boot_complete_to_merge_start_time_ms() override;
void set_merge_failure_code(MergeFailureCode code) override;
MergeFailureCode merge_failure_code() override;
+ void set_source_build_fingerprint(const std::string& fingerprint) override;
+ std::string source_build_fingerprint() override;
std::unique_ptr<Result> Finish() override;
+ bool WriteState() override;
private:
bool ReadState();
- bool WriteState();
bool DeleteState();
SnapshotMergeStats(const std::string& path);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index a7cd939..74b78c5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -57,6 +57,7 @@
ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) override;
bool UnmapAllSnapshots() override;
+ std::string ReadSourceBuildFingerprint() override;
};
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
index bf5ce8b..c00dafa 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
@@ -76,6 +76,7 @@
bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
bool EmitLabel(uint64_t label) override;
+ bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
private:
android::base::unique_fd cow_device_;
@@ -103,6 +104,7 @@
bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
bool EmitLabel(uint64_t label) override;
+ bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
private:
android::base::unique_fd snapshot_fd_;
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 1dc61af..ed86c87 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -40,8 +40,18 @@
LOG(ERROR) << "\t -s Run Silent";
LOG(ERROR) << "\t -d Attempt to decompress";
LOG(ERROR) << "\t -b Show data for failed decompress\n";
+ LOG(ERROR) << "\t -m Show ops in reverse merge order\n";
}
+enum OpIter { Normal, RevMerge };
+
+struct Options {
+ bool silent;
+ bool decompress;
+ bool show_bad;
+ OpIter iter_type;
+};
+
// Sink that always appends to the end of a string.
class StringSink : public IByteSink {
public:
@@ -78,7 +88,7 @@
}
}
-static bool Inspect(const std::string& path, bool silent, bool decompress, bool show_bad) {
+static bool Inspect(const std::string& path, Options opt) {
android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "open failed: " << path;
@@ -100,7 +110,7 @@
bool has_footer = false;
if (reader.GetFooter(&footer)) has_footer = true;
- if (!silent) {
+ if (!opt.silent) {
std::cout << "Major version: " << header.major_version << "\n";
std::cout << "Minor version: " << header.minor_version << "\n";
std::cout << "Header size: " << header.header_size << "\n";
@@ -116,19 +126,24 @@
}
}
- auto iter = reader.GetOpIter();
+ std::unique_ptr<ICowOpIter> iter;
+ if (opt.iter_type == Normal) {
+ iter = reader.GetOpIter();
+ } else if (opt.iter_type == RevMerge) {
+ iter = reader.GetRevMergeOpIter();
+ }
StringSink sink;
bool success = true;
while (!iter->Done()) {
const CowOperation& op = iter->Get();
- if (!silent) std::cout << op << "\n";
+ if (!opt.silent) std::cout << op << "\n";
- if (decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
+ if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
if (!reader.ReadData(op, &sink)) {
std::cerr << "Failed to decompress for :" << op << "\n";
success = false;
- if (show_bad) ShowBad(reader, op);
+ if (opt.show_bad) ShowBad(reader, op);
}
sink.Reset();
}
@@ -144,19 +159,24 @@
int main(int argc, char** argv) {
int ch;
- bool silent = false;
- bool decompress = false;
- bool show_bad = false;
- while ((ch = getopt(argc, argv, "sdb")) != -1) {
+ struct android::snapshot::Options opt;
+ opt.silent = false;
+ opt.decompress = false;
+ opt.show_bad = false;
+ opt.iter_type = android::snapshot::Normal;
+ while ((ch = getopt(argc, argv, "sdbm")) != -1) {
switch (ch) {
case 's':
- silent = true;
+ opt.silent = true;
break;
case 'd':
- decompress = true;
+ opt.decompress = true;
break;
case 'b':
- show_bad = true;
+ opt.show_bad = true;
+ break;
+ case 'm':
+ opt.iter_type = android::snapshot::RevMerge;
break;
default:
android::snapshot::usage();
@@ -169,7 +189,7 @@
return 1;
}
- if (!android::snapshot::Inspect(argv[optind], silent, decompress, show_bad)) {
+ if (!android::snapshot::Inspect(argv[optind], opt)) {
return 1;
}
return 0;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index be732ec..0e36da1 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -716,7 +716,7 @@
}
}
- SnapshotUpdateStatus initial_status;
+ SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
initial_status.set_state(UpdateState::Merging);
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
initial_status.set_total_sectors(initial_target_values.total_sectors);
@@ -2515,15 +2515,25 @@
SnapshotUpdateStatus status;
status.set_state(state);
- if (state == UpdateState::MergeFailed) {
- status.set_merge_failure_code(failure_code);
+ switch (state) {
+ case UpdateState::MergeFailed:
+ status.set_merge_failure_code(failure_code);
+ break;
+ case UpdateState::Initiated:
+ status.set_source_build_fingerprint(
+ android::base::GetProperty("ro.build.fingerprint", ""));
+ break;
+ default:
+ break;
}
// If we're transitioning between two valid states (eg, we're not beginning
- // or ending an OTA), then make sure to propagate the compression bit.
+ // or ending an OTA), then make sure to propagate the compression bit and
+ // build fingerprint.
if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
SnapshotUpdateStatus old_status = ReadSnapshotUpdateStatus(lock);
status.set_compression_enabled(old_status.compression_enabled());
+ status.set_source_build_fingerprint(old_status.source_build_fingerprint());
}
return WriteSnapshotUpdateStatus(lock, status);
}
@@ -2838,7 +2848,7 @@
}
}
- SnapshotUpdateStatus status = {};
+ SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
status.set_state(update_state);
status.set_compression_enabled(cow_creator.compression_enabled);
if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
@@ -3264,9 +3274,10 @@
std::stringstream ss;
+ auto update_status = ReadSnapshotUpdateStatus(file.get());
+
ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
- ss << "Compression: " << ReadSnapshotUpdateStatus(file.get()).compression_enabled()
- << std::endl;
+ ss << "Compression: " << update_status.compression_enabled() << std::endl;
ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
ss << "Rollback indicator: "
@@ -3275,6 +3286,7 @@
ss << "Forward merge indicator: "
<< (access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0 ? "exists" : strerror(errno))
<< std::endl;
+ ss << "Source build fingerprint: " << update_status.source_build_fingerprint() << std::endl;
bool ok = true;
std::vector<std::string> snapshots;
@@ -3792,5 +3804,13 @@
return status.merge_failure_code();
}
+std::string SnapshotManager::ReadSourceBuildFingerprint() {
+ auto lock = LockExclusive();
+ if (!lock) return {};
+
+ SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
+ return status.source_build_fingerprint();
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 4a93d65..712eafb 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -91,7 +91,6 @@
void SnapshotMergeStats::set_cow_file_size(uint64_t cow_file_size) {
report_.set_cow_file_size(cow_file_size);
- WriteState();
}
uint64_t SnapshotMergeStats::cow_file_size() {
@@ -138,6 +137,14 @@
return report_.merge_failure_code();
}
+void SnapshotMergeStats::set_source_build_fingerprint(const std::string& fingerprint) {
+ report_.set_source_build_fingerprint(fingerprint);
+}
+
+std::string SnapshotMergeStats::source_build_fingerprint() {
+ return report_.source_build_fingerprint();
+}
+
class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result {
public:
SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report,
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 1a9eda5..a8d5b8a 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -136,7 +136,10 @@
void set_boot_complete_to_merge_start_time_ms(uint32_t) override {}
uint32_t boot_complete_to_merge_start_time_ms() override { return 0; }
void set_merge_failure_code(MergeFailureCode) override {}
- MergeFailureCode merge_failure_code() { return MergeFailureCode::Ok; }
+ MergeFailureCode merge_failure_code() override { return MergeFailureCode::Ok; }
+ void set_source_build_fingerprint(const std::string&) override {}
+ std::string source_build_fingerprint() override { return {}; }
+ bool WriteState() override { return false; }
};
ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -170,4 +173,9 @@
return MergeFailureCode::Ok;
}
+std::string SnapshotManagerStub::ReadSourceBuildFingerprint() {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return {};
+}
+
} // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 080f3b7..34b3e87 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -114,6 +114,10 @@
return cow_->AddLabel(label);
}
+bool CompressedSnapshotWriter::EmitSequenceData(size_t num_ops, const uint32_t* data) {
+ return cow_->AddSequenceData(num_ops, data);
+}
+
bool CompressedSnapshotWriter::Initialize() {
return cow_->Initialize(cow_device_);
}
@@ -183,6 +187,11 @@
return true;
}
+bool OnlineKernelSnapshotWriter::EmitSequenceData(size_t, const uint32_t*) {
+ // Not Needed
+ return true;
+}
+
std::unique_ptr<FileDescriptor> OnlineKernelSnapshotWriter::OpenReader() {
unique_fd fd(dup(snapshot_fd_.get()));
if (fd < 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 03c2ef6..e578a76 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -266,14 +266,15 @@
void Snapuserd::CheckMergeCompletionStatus() {
if (!merge_initiated_) {
- SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: " << reader_->total_data_ops();
+ SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: "
+ << reader_->get_num_total_data_ops();
return;
}
struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
- << " Total-data-ops: " << reader_->total_data_ops();
+ << " Total-data-ops: " << reader_->get_num_total_data_ops();
}
/*
@@ -352,7 +353,6 @@
return false;
}
- reader_->InitializeMerge();
SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
if (!MmapMetadata()) {
@@ -361,7 +361,7 @@
}
// Initialize the iterator for reading metadata
- cowop_riter_ = reader_->GetRevOpIter();
+ std::unique_ptr<ICowOpIter> cowop_rm_iter = reader_->GetRevMergeOpIter();
exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
@@ -379,23 +379,18 @@
// this memset will ensure that metadata read is completed.
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- while (!cowop_riter_->Done()) {
- const CowOperation* cow_op = &cowop_riter_->Get();
+ while (!cowop_rm_iter->Done()) {
+ const CowOperation* cow_op = &cowop_rm_iter->Get();
struct disk_exception* de =
reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
- if (IsMetadataOp(*cow_op)) {
- cowop_riter_->Next();
- continue;
- }
-
metadata_found = true;
// This loop will handle all the replace and zero ops.
// We will handle the copy ops later as it requires special
// handling of assigning chunk-id's. Furthermore, we make
// sure that replace/zero and copy ops are not batch merged; hence,
// the bump in the chunk_id before break of this loop
- if (cow_op->type == kCowCopyOp) {
+ if (IsOrderedOp(*cow_op)) {
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
break;
}
@@ -415,7 +410,7 @@
chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
num_ops += 1;
offset += sizeof(struct disk_exception);
- cowop_riter_->Next();
+ cowop_rm_iter->Next();
SNAP_LOG(DEBUG) << num_ops << ":"
<< " Old-chunk: " << de->old_chunk << " New-chunk: " << de->new_chunk;
@@ -432,7 +427,7 @@
sizeof(struct disk_exception));
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- if (cowop_riter_->Done()) {
+ if (cowop_rm_iter->Done()) {
vec_.push_back(std::move(de_ptr));
}
}
@@ -445,18 +440,15 @@
std::map<uint64_t, const CowOperation*> map;
std::set<uint64_t> dest_blocks;
size_t pending_copy_ops = exceptions_per_area_ - num_ops;
- uint64_t total_copy_ops = reader_->total_copy_ops();
+ uint64_t total_copy_ops = reader_->get_num_ordered_ops_to_merge();
SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
<< " Number of replace/zero ops completed in this area: " << num_ops
<< " Pending copy ops for this area: " << pending_copy_ops;
- while (!cowop_riter_->Done()) {
+
+ while (!cowop_rm_iter->Done()) {
do {
- const CowOperation* cow_op = &cowop_riter_->Get();
- if (IsMetadataOp(*cow_op)) {
- cowop_riter_->Next();
- continue;
- }
+ const CowOperation* cow_op = &cowop_rm_iter->Get();
// We have two cases specific cases:
//
@@ -572,8 +564,8 @@
map[cow_op->new_block] = cow_op;
dest_blocks.insert(cow_op->source);
prev_id = cow_op->new_block;
- cowop_riter_->Next();
- } while (!cowop_riter_->Done() && pending_copy_ops);
+ cowop_rm_iter->Next();
+ } while (!cowop_rm_iter->Done() && pending_copy_ops);
data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << map.size()
@@ -611,7 +603,7 @@
sizeof(struct disk_exception));
memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
- if (cowop_riter_->Done()) {
+ if (cowop_rm_iter->Done()) {
vec_.push_back(std::move(de_ptr));
SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
}
@@ -661,7 +653,7 @@
<< " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops
<< " Copy-ops: " << copy_ops << " Areas: " << vec_.size()
<< " Num-ops-merged: " << header.num_merge_ops
- << " Total-data-ops: " << reader_->total_data_ops();
+ << " Total-data-ops: " << reader_->get_num_total_data_ops();
// Total number of sectors required for creating dm-user device
num_sectors_ = ChunkToSector(data_chunk_id);
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 212c78e..5d86e4f 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -306,8 +306,6 @@
uint32_t exceptions_per_area_;
uint64_t num_sectors_;
- std::unique_ptr<ICowOpIter> cowop_iter_;
- std::unique_ptr<ICowOpReverseIter> cowop_riter_;
std::unique_ptr<CowReader> reader_;
// Vector of disk exception which is a
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index a1b020a..94e1abb 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -127,7 +127,7 @@
"androidboot.serialno = \"BLAHBLAHBLAH\"\n"
"androidboot.slot_suffix = \"_a\"\n"
"androidboot.hardware.platform = \"sdw813\"\n"
- "hardware = \"foo\"\n"
+ "androidboot.hardware = \"foo\"\n"
"androidboot.revision = \"EVT1.0\"\n"
"androidboot.bootloader = \"burp-0.1-7521\"\n"
"androidboot.hardware.sku = \"mary\"\n"
@@ -159,7 +159,7 @@
{"androidboot.serialno", "BLAHBLAHBLAH"},
{"androidboot.slot_suffix", "_a"},
{"androidboot.hardware.platform", "sdw813"},
- {"hardware", "foo"},
+ {"androidboot.hardware", "foo"},
{"androidboot.revision", "EVT1.0"},
{"androidboot.bootloader", "burp-0.1-7521"},
{"androidboot.hardware.sku", "mary"},
diff --git a/init/Android.bp b/init/Android.bp
index 3e8d4e3..46fa1f6 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -257,7 +257,7 @@
name: "init_first_stage_cc_defaults",
module_type: "cc_defaults",
config_namespace: "ANDROID",
- bool_variables: ["BOARD_BUILD_SYSTEM_ROOT_IMAGE", "BOARD_USES_RECOVERY_AS_BOOT"],
+ bool_variables: ["BOARD_BUILD_SYSTEM_ROOT_IMAGE"],
properties: ["installable"],
}
@@ -269,9 +269,6 @@
BOARD_BUILD_SYSTEM_ROOT_IMAGE: {
installable: false,
},
- BOARD_USES_RECOVERY_AS_BOOT: {
- installable: false,
- },
},
}
diff --git a/init/Android.mk b/init/Android.mk
index c08fe03..c1b0cf9 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -9,10 +9,8 @@
LOCAL_LICENSE_CONDITIONS := notice
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
-ifneq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
LOCAL_REQUIRED_MODULES := \
init_first_stage \
-endif # BOARD_USES_RECOVERY_AS_BOOT
endif # BOARD_BUILD_SYSTEM_ROOT_IMAGE
include $(BUILD_PHONY_PACKAGE)
diff --git a/init/devices.cpp b/init/devices.cpp
index ce6298a..56c6623 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -264,6 +264,8 @@
setfscreatecon(secontext.c_str());
}
+ gid_t new_group = -1;
+
dev_t dev = makedev(major, minor);
/* Temporarily change egid to avoid race condition setting the gid of the
* device node. Unforunately changing the euid would prevent creation of
@@ -291,10 +293,21 @@
PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path
<< "' device";
}
+
+ struct stat s;
+ if (stat(path.c_str(), &s) == 0) {
+ if (gid != s.st_gid) {
+ new_group = gid;
+ }
+ } else {
+ PLOG(ERROR) << "Cannot stat " << path;
+ }
}
out:
- chown(path.c_str(), uid, -1);
+ if (chown(path.c_str(), uid, new_group) < 0) {
+ PLOG(ERROR) << "Cannot chown " << path << " " << uid << " " << new_group;
+ }
if (setegid(AID_ROOT)) {
PLOG(FATAL) << "setegid(AID_ROOT) failed";
}
diff --git a/init/main.cpp b/init/main.cpp
index 23f5530..b01a3ee 100644
--- a/init/main.cpp
+++ b/init/main.cpp
@@ -25,9 +25,11 @@
#if __has_feature(address_sanitizer)
#include <sanitizer/asan_interface.h>
+#elif __has_feature(hwaddress_sanitizer)
+#include <sanitizer/hwasan_interface.h>
#endif
-#if __has_feature(address_sanitizer)
+#if __has_feature(address_sanitizer) || __has_feature(hwaddress_sanitizer)
// Load asan.options if it exists since these are not yet in the environment.
// Always ensure detect_container_overflow=0 as there are false positives with this check.
// Always ensure abort_on_error=1 to ensure we reboot to bootloader for development builds.
@@ -51,6 +53,8 @@
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
__asan_set_error_report_callback(AsanReportCallback);
+#elif __has_feature(hwaddress_sanitizer)
+ __hwasan_set_error_report_callback(AsanReportCallback);
#endif
// Boost prio which will be restored later
setpriority(PRIO_PROCESS, 0, -20);
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 15252a6..2a57808 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -158,7 +158,8 @@
auto on_activate = [&](const std::string& apex_path,
const apex::proto::ApexManifest& apex_manifest) {
apex_infos.emplace_back(apex_manifest.name(), apex_path, apex_path, apex_manifest.version(),
- apex_manifest.versionname(), /*isFactory=*/true, /*isActive=*/true);
+ apex_manifest.versionname(), /*isFactory=*/true, /*isActive=*/true,
+ /* lastUpdateMillis= */ 0);
};
for (const auto& dir : kBuiltinDirsForApexes) {
diff --git a/init/property_service.cpp b/init/property_service.cpp
index ff9da42..2d67bf5 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1238,21 +1238,11 @@
});
}
-// bootconfig does not allow to populate `key=value` simultaneously with
-// `key.subkey=value` which does not work with the existing code for
-// `hardware` (e.g. we want both `ro.boot.hardware=value` and
-// `ro.boot.hardware.sku=value`) and for `qemu` (Android Stidio Emulator
-// specific).
-static bool IsAllowedBootconfigKey(const std::string_view key) {
- return (key == "hardware"sv) || (key == "qemu"sv);
-}
static void ProcessBootconfig() {
ImportBootconfig([&](const std::string& key, const std::string& value) {
if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
- } else if (IsAllowedBootconfigKey(key)) {
- InitPropertySet("ro.boot." + key, value);
}
});
}
diff --git a/init/util.cpp b/init/util.cpp
index a40d104..9f7bfdb 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -253,8 +253,10 @@
for (const auto& entry : android::base::Split(bootconfig, "\n")) {
std::vector<std::string> pieces = android::base::Split(entry, "=");
if (pieces.size() == 2) {
- pieces[1].erase(std::remove(pieces[1].begin(), pieces[1].end(), '"'), pieces[1].end());
- fn(android::base::Trim(pieces[0]), android::base::Trim(pieces[1]));
+ // get rid of the extra space between a list of values and remove the quotes.
+ std::string value = android::base::StringReplace(pieces[1], "\", \"", ",", true);
+ value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
+ fn(android::base::Trim(pieces[0]), android::base::Trim(value));
}
}
}
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 815d2bb..c824376 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -146,12 +146,6 @@
std::this_thread::sleep_for(5ms);
}
- // With the exception of boot or shutdown, system uid_ folders are always populated. Spinning
- // here would needlessly delay most pid removals. Additionally, once empty a uid_ cgroup won't
- // have processes hanging on it (we've already spun for all its pid_), so there's no need to
- // spin anyway.
- rmdir(uid_path.c_str());
-
return ret;
}
@@ -230,7 +224,11 @@
* transferred for the user/group passed as uid/gid before system_server can properly access them.
*/
static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
- if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
+ if (mkdir(path.c_str(), mode) == -1) {
+ if (errno == EEXIST) {
+ // Directory already exists and permissions have been set at the time it was created
+ return true;
+ }
return false;
}
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index faf90c2..c42cada 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -96,6 +96,12 @@
acquire();
}
+String16::String16(String16&& o) noexcept
+ : mString(o.mString)
+{
+ o.mString = getEmptyString();
+}
+
String16::String16(const String16& o, size_t len, size_t begin)
: mString(getEmptyString())
{
@@ -126,6 +132,13 @@
release();
}
+String16& String16::operator=(String16&& other) noexcept {
+ release();
+ mString = other.mString;
+ other.mString = getEmptyString();
+ return *this;
+}
+
size_t String16::size() const
{
if (isStaticString()) {
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 54662ac..7d7230e 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -58,12 +58,27 @@
EXPECT_STR16EQ(u"Verify me", another);
}
+TEST(String16Test, CopyAssign) {
+ String16 tmp("Verify me");
+ String16 another;
+ another = tmp;
+ EXPECT_STR16EQ(u"Verify me", tmp);
+ EXPECT_STR16EQ(u"Verify me", another);
+}
+
TEST(String16Test, Move) {
String16 tmp("Verify me");
String16 another(std::move(tmp));
EXPECT_STR16EQ(u"Verify me", another);
}
+TEST(String16Test, MoveAssign) {
+ String16 tmp("Verify me");
+ String16 another;
+ another = std::move(tmp);
+ EXPECT_STR16EQ(u"Verify me", another);
+}
+
TEST(String16Test, Size) {
String16 tmp("Verify me");
EXPECT_EQ(9U, tmp.size());
@@ -174,10 +189,22 @@
EXPECT_STR16EQ(u"Verify me", another);
}
-TEST(String16Test, StringMoveFromStaticString) {
+TEST(String16Test, StringCopyAssignFromStaticString) {
StaticString16 tmp(u"Verify me");
- String16 another(std::move(tmp));
+ String16 another(u"nonstatic");
+ another = tmp;
EXPECT_STR16EQ(u"Verify me", another);
+ EXPECT_TRUE(another.isStaticString());
+ EXPECT_STR16EQ(u"Verify me", tmp);
+ EXPECT_TRUE(tmp.isStaticString());
+}
+
+TEST(String16Test, StringMoveAssignFromStaticString) {
+ StaticString16 tmp(u"Verify me");
+ String16 another(u"nonstatic");
+ another = std::move(tmp);
+ EXPECT_STR16EQ(u"Verify me", another);
+ EXPECT_TRUE(another.isStaticString());
}
TEST(String16Test, EmptyStringIsStatic) {
diff --git a/libutils/include/utils/String16.h b/libutils/include/utils/String16.h
index 60d523a..3ef56a3 100644
--- a/libutils/include/utils/String16.h
+++ b/libutils/include/utils/String16.h
@@ -41,6 +41,7 @@
public:
String16();
String16(const String16& o);
+ String16(String16&& o) noexcept;
String16(const String16& o,
size_t len,
size_t begin=0);
@@ -69,6 +70,7 @@
status_t append(const char16_t* other, size_t len);
inline String16& operator=(const String16& other);
+ String16& operator=(String16&& other) noexcept;
inline String16& operator+=(const String16& other);
inline String16 operator+(const String16& other) const;
@@ -172,10 +174,6 @@
template <size_t N>
explicit constexpr String16(const StaticData<N>& s) : mString(s.data) {}
-
-public:
- template <size_t N>
- explicit constexpr String16(const StaticString16<N>& s) : mString(s.mString) {}
};
// String16 can be trivially moved using memcpy() because moving does not
diff --git a/rootdir/init.rc b/rootdir/init.rc
index db61fc5..a8bef42 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -438,6 +438,7 @@
# Start logd before any other services run to ensure we capture all of their logs.
start logd
# Start lmkd before any other services run so that it can register them
+ write /proc/sys/vm/watermark_boost_factor 0
chown root system /sys/module/lowmemorykiller/parameters/adj
chmod 0664 /sys/module/lowmemorykiller/parameters/adj
chown root system /sys/module/lowmemorykiller/parameters/minfree
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index b7d7490..97e8d8e 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -18,9 +18,11 @@
"awk",
"bc",
"bzip2",
+ "fsck.exfat",
"ldd",
"logwrapper",
"mini-keyctl",
+ "mkfs.exfat",
"mkshrc",
"newfs_msdos",
"reboot",
diff --git a/storaged/Android.bp b/storaged/Android.bp
index ec27a08..9d5cb48 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -30,7 +30,6 @@
"libhidlbase",
"liblog",
"libprotobuf-cpp-lite",
- "libsysutils",
"libutils",
"libz",
],
diff --git a/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp b/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp
index 41a21e9..9440724 100644
--- a/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp
+++ b/trusty/keymaster/keymint/TrustyKeyMintOperation.cpp
@@ -34,6 +34,7 @@
using ::keymaster::FinishOperationResponse;
using ::keymaster::TAG_ASSOCIATED_DATA;
using ::keymaster::TAG_AUTH_TOKEN;
+using ::keymaster::TAG_CONFIRMATION_TOKEN;
using ::keymaster::UpdateOperationRequest;
using ::keymaster::UpdateOperationResponse;
using km_utils::authToken2AidlVec;
@@ -106,12 +107,12 @@
return ScopedAStatus::ok();
}
-ScopedAStatus TrustyKeyMintOperation::finish(
- const optional<vector<uint8_t>>& input, //
- const optional<vector<uint8_t>>& signature, //
- const optional<HardwareAuthToken>& authToken,
- const optional<TimeStampToken>& /* timestampToken */,
- const optional<vector<uint8_t>>& /* confirmationToken */, vector<uint8_t>* output) {
+ScopedAStatus TrustyKeyMintOperation::finish(const optional<vector<uint8_t>>& input, //
+ const optional<vector<uint8_t>>& signature, //
+ const optional<HardwareAuthToken>& authToken,
+ const optional<TimeStampToken>& /* timestampToken */,
+ const optional<vector<uint8_t>>& confirmationToken,
+ vector<uint8_t>* output) {
if (!output) {
return ScopedAStatus(AStatus_fromServiceSpecificError(
static_cast<int32_t>(ErrorCode::OUTPUT_PARAMETER_NULL)));
@@ -119,6 +120,16 @@
output->clear();
FinishOperationRequest request(impl_->message_version());
+
+ if (authToken) {
+ auto tokenAsVec(authToken2AidlVec(*authToken));
+ request.additional_params.push_back(TAG_AUTH_TOKEN, tokenAsVec.data(), tokenAsVec.size());
+ }
+ if (confirmationToken) {
+ request.additional_params.push_back(TAG_CONFIRMATION_TOKEN, confirmationToken->data(),
+ confirmationToken->size());
+ }
+
request.op_handle = opHandle_;
if (signature) request.signature.Reinitialize(signature->data(), signature->size());
size_t serialized_size = request.SerializedSize();
diff --git a/usbd/Android.bp b/usbd/Android.bp
index 22d171d..27db0fa 100644
--- a/usbd/Android.bp
+++ b/usbd/Android.bp
@@ -13,6 +13,5 @@
"libutils",
"libhardware",
"android.hardware.usb.gadget@1.0",
- "libcutils",
],
}