Merge "first_stage_init: run first_stage.sh regardless of console presence"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 89bd66a..52cff94 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -22,12 +22,6 @@
"name": "CtsInitTestCases"
},
{
- "name": "CtsLiblogTestCases"
- },
- {
- "name": "CtsLogdTestCases"
- },
- {
"name": "debuggerd_test"
},
{
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 5ed9e57..5565e8b 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -312,7 +312,7 @@
if (mte_supported()) {
// Test that the default TAGGED_ADDR_CTRL value is set.
- ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)");
+ ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff5)");
}
}
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 1bf4c9c..333ca50 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -67,7 +67,7 @@
if ((partition + device->GetCurrentSlot()) == partition_name) {
mount_metadata.emplace();
- fs_mgr_overlayfs_teardown(entry.mount_point.c_str());
+ android::fs_mgr::TeardownAllOverlayForMountPoint(entry.mount_point);
}
}
}
@@ -194,7 +194,7 @@
if (!FlashPartitionTable(super_name, *new_metadata.get())) {
return device->WriteFail("Unable to flash new partition table");
}
- fs_mgr_overlayfs_teardown();
+ android::fs_mgr::TeardownAllOverlayForMountPoint();
sync();
return device->WriteOkay("Successfully flashed partition table");
}
@@ -234,7 +234,7 @@
if (!UpdateAllPartitionMetadata(device, super_name, *new_metadata.get())) {
return device->WriteFail("Unable to write new partition table");
}
- fs_mgr_overlayfs_teardown();
+ android::fs_mgr::TeardownAllOverlayForMountPoint();
sync();
return device->WriteOkay("Successfully updated partition table");
}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index fe72393..6294b3f 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1534,6 +1534,8 @@
attempted_entry.mount_point},
nullptr)) {
++error_count;
+ } else if (current_entry.mount_point == "/data") {
+ userdata_mounted = true;
}
encryptable = FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED;
continue;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 616a06f..4b79466 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -591,7 +591,7 @@
FstabEntry userdata;
if (FstabEntry* entry = GetEntryForMountPoint(fstab, "/data")) {
userdata = *entry;
- userdata.blk_device = "userdata_gsi";
+ userdata.blk_device = android::gsi::kDsuUserdata;
userdata.fs_mgr_flags.logical = true;
userdata.fs_mgr_flags.formattable = true;
if (!userdata.metadata_key_dir.empty()) {
@@ -611,7 +611,11 @@
continue;
}
// userdata has been handled
- if (StartsWith(partition, "user")) {
+ if (partition == android::gsi::kDsuUserdata) {
+ continue;
+ }
+ // scratch is handled by fs_mgr_overlayfs
+ if (partition == android::gsi::kDsuScratch) {
continue;
}
// dsu_partition_name = corresponding_partition_name + kDsuPostfix
@@ -690,8 +694,11 @@
if (!is_proc_mounts && !access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
std::string dsu_slot;
if (!android::gsi::GetActiveDsu(&dsu_slot)) {
- PERROR << __FUNCTION__ << "(): failed to get active dsu slot";
- return false;
+ // This is expected to fail if host is android Q, since Q doesn't
+ // support DSU slotting.
+ // In that case, just use the default slot name "dsu".
+ PWARNING << __FUNCTION__ << "(): failed to get active dsu slot";
+ dsu_slot = "dsu";
}
std::string lp_names;
ReadFileToString(gsi::kGsiLpNamesFile, &lp_names);
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index a7704de..e52d8d5 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -113,8 +113,11 @@
namespace android {
namespace fs_mgr {
-void MapScratchPartitionIfNeeded(Fstab*,
- const std::function<bool(const std::set<std::string>&)>&) {}
+void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
+}
+
+void TeardownAllOverlayForMountPoint(const std::string&) {}
+
} // namespace fs_mgr
} // namespace android
@@ -166,11 +169,34 @@
(vst.f_bfree * vst.f_bsize) >= kSizeThreshold;
}
+bool fs_mgr_in_recovery() {
+ // Check the existence of recovery binary instead of using the compile time
+ // macro, because first-stage-init is compiled with __ANDROID_RECOVERY__
+ // defined, albeit not in recovery. More details: system/core/init/README.md
+ return fs_mgr_access("/system/bin/recovery");
+}
+
+bool fs_mgr_is_dsu_running() {
+ // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is
+ // never called in recovery, the return value of android::gsi::IsGsiRunning()
+ // is not well-defined. In this case, just return false as being in recovery
+ // implies not running a DSU system.
+ if (fs_mgr_in_recovery()) return false;
+ auto saved_errno = errno;
+ auto ret = android::gsi::IsGsiRunning();
+ errno = saved_errno;
+ return ret;
+}
+
const auto kPhysicalDevice = "/dev/block/by-name/"s;
constexpr char kScratchImageMetadata[] = "/metadata/gsi/remount/lp_metadata";
// Note: this is meant only for recovery/first-stage init.
bool ScratchIsOnData() {
+ // The scratch partition of DSU is managed by gsid.
+ if (fs_mgr_is_dsu_running()) {
+ return false;
+ }
return fs_mgr_access(kScratchImageMetadata);
}
@@ -464,6 +490,12 @@
// umount and delete kScratchMountPoint storage if we have logical partitions
if (overlay != kScratchMountPoint) return true;
+ // Validation check.
+ if (fs_mgr_is_dsu_running()) {
+ LERROR << "Destroying DSU scratch is not allowed.";
+ return false;
+ }
+
auto save_errno = errno;
if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
fs_mgr_overlayfs_umount_scratch();
@@ -512,10 +544,13 @@
}
bool fs_mgr_overlayfs_teardown_one(const std::string& overlay, const std::string& mount_point,
- bool* change) {
+ bool* change, bool* should_destroy_scratch = nullptr) {
const auto top = overlay + kOverlayTopDir;
- if (!fs_mgr_access(top)) return fs_mgr_overlayfs_teardown_scratch(overlay, change);
+ if (!fs_mgr_access(top)) {
+ if (should_destroy_scratch) *should_destroy_scratch = true;
+ return true;
+ }
auto cleanup_all = mount_point.empty();
const auto partition_name = android::base::Basename(mount_point);
@@ -571,7 +606,7 @@
PERROR << "rmdir " << top;
}
}
- if (cleanup_all) ret &= fs_mgr_overlayfs_teardown_scratch(overlay, change);
+ if (should_destroy_scratch) *should_destroy_scratch = cleanup_all;
return ret;
}
@@ -881,12 +916,29 @@
return "";
}
+// Note: The scratch partition of DSU is managed by gsid, and should be initialized during
+// first-stage-mount. Just check if the DM device for DSU scratch partition is created or not.
+static std::string GetDsuScratchDevice() {
+ auto& dm = DeviceMapper::Instance();
+ std::string device;
+ if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID &&
+ dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) {
+ return device;
+ }
+ return "";
+}
+
// This returns the scratch device that was detected during early boot (first-
// stage init). If the device was created later, for example during setup for
// the adb remount command, it can return an empty string since it does not
// query ImageManager. (Note that ImageManager in first-stage init will always
// use device-mapper, since /data is not available to use loop devices.)
static std::string GetBootScratchDevice() {
+ // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
+ if (fs_mgr_is_dsu_running()) {
+ return GetDsuScratchDevice();
+ }
+
auto& dm = DeviceMapper::Instance();
// If there is a scratch partition allocated in /data or on super, we
@@ -1108,6 +1160,14 @@
bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
bool* partition_exists, bool* change) {
+ // Use the DSU scratch device managed by gsid if within a DSU system.
+ if (fs_mgr_is_dsu_running()) {
+ *scratch_device = GetDsuScratchDevice();
+ *partition_exists = !scratch_device->empty();
+ *change = false;
+ return *partition_exists;
+ }
+
// Try a physical partition first.
*scratch_device = GetPhysicalScratchDevice();
if (!scratch_device->empty() && fs_mgr_rw_access(*scratch_device)) {
@@ -1166,12 +1226,8 @@
bool fs_mgr_overlayfs_invalid() {
if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) return true;
- // in recovery, fastbootd, or gsi mode, not allowed!
- if (fs_mgr_access("/system/bin/recovery")) return true;
- auto save_errno = errno;
- auto ret = android::gsi::IsGsiRunning();
- errno = save_errno;
- return ret;
+ // in recovery or fastbootd, not allowed!
+ return fs_mgr_in_recovery();
}
} // namespace
@@ -1314,6 +1370,8 @@
return ret;
}
+// Note: This function never returns the DSU scratch device in recovery or fastbootd,
+// because the DSU scratch is created in the first-stage-mount, which is not run in recovery.
static bool EnsureScratchMapped(std::string* device, bool* mapped) {
*mapped = false;
*device = GetBootScratchDevice();
@@ -1321,6 +1379,11 @@
return true;
}
+ if (!fs_mgr_in_recovery()) {
+ errno = EINVAL;
+ return false;
+ }
+
auto partition_name = android::base::Basename(kScratchMountPoint);
// Check for scratch on /data first, before looking for a modified super
@@ -1362,10 +1425,27 @@
return true;
}
-static void UnmapScratchDevice() {
- // This should only be reachable in recovery, where scratch is not
- // automatically mapped and therefore can be unmapped.
- DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
+// This should only be reachable in recovery, where DSU scratch is not
+// automatically mapped.
+static bool MapDsuScratchDevice(std::string* device) {
+ std::string dsu_slot;
+ if (!android::gsi::IsGsiInstalled() || !android::gsi::GetActiveDsu(&dsu_slot) ||
+ dsu_slot.empty()) {
+ // Nothing to do if no DSU installation present.
+ return false;
+ }
+
+ auto images = IImageManager::Open("dsu/" + dsu_slot, 10s);
+ if (!images || !images->BackingImageExists(android::gsi::kDsuScratch)) {
+ // Nothing to do if DSU scratch device doesn't exist.
+ return false;
+ }
+
+ images->UnmapImageDevice(android::gsi::kDsuScratch);
+ if (!images->MapImageDevice(android::gsi::kDsuScratch, 10s, device)) {
+ return false;
+ }
+ return true;
}
// Returns false if teardown not permitted, errno set to last error.
@@ -1377,21 +1457,27 @@
// If scratch exists, but is not mounted, lets gain access to clean
// specific override entries.
auto mount_scratch = false;
- bool unmap = false;
if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
- std::string scratch_device;
- if (EnsureScratchMapped(&scratch_device, &unmap)) {
+ std::string scratch_device = GetBootScratchDevice();
+ if (!scratch_device.empty()) {
mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
fs_mgr_overlayfs_scratch_mount_type());
}
}
+ bool should_destroy_scratch = false;
for (const auto& overlay_mount_point : kOverlayMountPoints) {
ret &= fs_mgr_overlayfs_teardown_one(
- overlay_mount_point, mount_point ? fs_mgr_mount_point(mount_point) : "", change);
+ overlay_mount_point, mount_point ? fs_mgr_mount_point(mount_point) : "", change,
+ overlay_mount_point == kScratchMountPoint ? &should_destroy_scratch : nullptr);
+ }
+ // Do not attempt to destroy DSU scratch if within a DSU system,
+ // because DSU scratch partition is managed by gsid.
+ if (should_destroy_scratch && !fs_mgr_is_dsu_running()) {
+ ret &= fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, change);
}
if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) {
// After obligatory teardown to make sure everything is clean, but if
- // we didn't want overlayfs in the the first place, we do not want to
+ // we didn't want overlayfs in the first place, we do not want to
// waste time on a reboot (or reboot request message).
if (change) *change = false;
}
@@ -1405,9 +1491,6 @@
if (mount_scratch) {
fs_mgr_overlayfs_umount_scratch();
}
- if (unmap) {
- UnmapScratchDevice();
- }
return ret;
}
@@ -1475,6 +1558,54 @@
}
}
+void TeardownAllOverlayForMountPoint(const std::string& mount_point) {
+ if (!fs_mgr_in_recovery()) {
+ LERROR << __FUNCTION__ << "(): must be called within recovery.";
+ return;
+ }
+
+ // Empty string means teardown everything.
+ const std::string teardown_dir = mount_point.empty() ? "" : fs_mgr_mount_point(mount_point);
+ constexpr bool* ignore_change = nullptr;
+
+ // Teardown legacy overlay mount points that's not backed by a scratch device.
+ for (const auto& overlay_mount_point : kOverlayMountPoints) {
+ if (overlay_mount_point == kScratchMountPoint) {
+ continue;
+ }
+ fs_mgr_overlayfs_teardown_one(overlay_mount_point, teardown_dir, ignore_change);
+ }
+
+ // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint.
+ bool mapped = false;
+ std::string scratch_device;
+ if (EnsureScratchMapped(&scratch_device, &mapped)) {
+ fs_mgr_overlayfs_umount_scratch();
+ if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
+ bool should_destroy_scratch = false;
+ fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change,
+ &should_destroy_scratch);
+ if (should_destroy_scratch) {
+ fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
+ }
+ fs_mgr_overlayfs_umount_scratch();
+ }
+ if (mapped) {
+ DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
+ }
+ }
+
+ // Teardown DSU overlay if present.
+ if (MapDsuScratchDevice(&scratch_device)) {
+ fs_mgr_overlayfs_umount_scratch();
+ if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
+ fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change);
+ fs_mgr_overlayfs_umount_scratch();
+ }
+ DestroyLogicalPartition(android::gsi::kDsuScratch);
+ }
+}
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index 34aded9..d45e2de 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -49,5 +49,12 @@
const std::function<bool(const std::set<std::string>&)>& init);
void CleanupOldScratchFiles();
+// Teardown overlays of all sources (cache dir, scratch device, DSU) for |mount_point|.
+// Teardown all overlays if |mount_point| is empty.
+//
+// Note: This should be called if and only if in recovery or fastbootd to teardown
+// overlays if any partition is flashed or updated.
+void TeardownAllOverlayForMountPoint(const std::string& mount_point = {});
+
} // namespace fs_mgr
} // namespace android
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 059a469..910911e 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -31,19 +31,22 @@
"libbrotli",
"libdm",
"libfstab",
- "libsnapshot_cow",
"update_metadata-protos",
],
whole_static_libs: [
+ "libbrotli",
"libcutils",
"libext2_uuid",
"libext4_utils",
"libfstab",
+ "libsnapshot_cow",
"libsnapshot_snapuserd",
+ "libz",
],
header_libs: [
"libchrome",
"libfiemap_headers",
+ "libstorage_literals_headers",
"libupdate_engine_headers",
],
export_static_lib_headers: [
@@ -432,6 +435,7 @@
init_rc: [
"snapuserd.rc",
],
+ static_executable: true,
}
cc_binary {
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 3d0321f..76d69ac 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -272,7 +272,6 @@
}
TEST_F(CowTest, Append) {
- cow_->DoNotRemove();
CowOptions options;
auto writer = std::make_unique<CowWriter>(options);
ASSERT_TRUE(writer->Initialize(cow_->fd));
@@ -335,9 +334,8 @@
std::string data = "This is some data, believe it";
data.resize(options.block_size, '\0');
- ASSERT_TRUE(writer->AddLabel(0));
ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
- ASSERT_TRUE(writer->AddLabel(1));
+ ASSERT_TRUE(writer->AddLabel(0));
ASSERT_TRUE(writer->AddZeroBlocks(50, 1));
ASSERT_TRUE(writer->Finalize());
// Drop the tail end of the header. Last entry may be corrupted.
@@ -348,13 +346,14 @@
writer = std::make_unique<CowWriter>(options);
ASSERT_TRUE(writer->Initialize(cow_->fd, CowWriter::OpenMode::APPEND));
- ASSERT_TRUE(writer->AddLabel(2));
- ASSERT_TRUE(writer->AddZeroBlocks(50, 1));
+ ASSERT_TRUE(writer->AddZeroBlocks(51, 1));
+ ASSERT_TRUE(writer->AddLabel(1));
std::string data2 = "More data!";
data2.resize(options.block_size, '\0');
- ASSERT_TRUE(writer->AddLabel(3));
- ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
+ ASSERT_TRUE(writer->AddRawBlocks(52, data2.data(), data2.size()));
+ ASSERT_TRUE(writer->AddLabel(2));
+
ASSERT_TRUE(writer->Finalize());
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
@@ -374,13 +373,6 @@
ASSERT_FALSE(iter->Done());
auto op = &iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(op->source, 0);
-
- iter->Next();
-
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
ASSERT_TRUE(reader.ReadData(*op, &sink));
ASSERT_EQ(sink.stream(), data);
@@ -391,7 +383,7 @@
ASSERT_FALSE(iter->Done());
op = &iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(op->source, 2);
+ ASSERT_EQ(op->source, 0);
iter->Next();
@@ -404,7 +396,7 @@
ASSERT_FALSE(iter->Done());
op = &iter->Get();
ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(op->source, 3);
+ ASSERT_EQ(op->source, 1);
iter->Next();
@@ -415,6 +407,13 @@
ASSERT_EQ(sink.stream(), data2);
iter->Next();
+
+ ASSERT_FALSE(iter->Done());
+ op = &iter->Get();
+ ASSERT_EQ(op->type, kCowLabelOp);
+ ASSERT_EQ(op->source, 2);
+ iter->Next();
+
ASSERT_TRUE(iter->Done());
}
@@ -424,11 +423,11 @@
ASSERT_TRUE(writer->Initialize(cow_->fd));
ASSERT_TRUE(writer->AddLabel(5));
- ASSERT_TRUE(writer->AddLabel(6));
std::string data = "This is some data, believe it";
data.resize(options.block_size * 2, '\0');
ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+ ASSERT_TRUE(writer->AddLabel(6));
// fail to write the footer
@@ -452,7 +451,7 @@
ASSERT_EQ(fstat(cow_->fd, &buf), 0);
ASSERT_EQ(buf.st_size, writer->GetCowSize());
- // Read back all three operations.
+ // Read back all valid operations
CowReader reader;
ASSERT_TRUE(reader.Parse(cow_->fd));
@@ -475,16 +474,20 @@
auto writer = std::make_unique<CowWriter>(options);
ASSERT_TRUE(writer->Initialize(cow_->fd));
- ASSERT_TRUE(writer->AddLabel(4));
-
- ASSERT_TRUE(writer->AddLabel(5));
std::string data = "This is some data, believe it";
data.resize(options.block_size * 2, '\0');
ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
- ASSERT_TRUE(writer->AddLabel(6));
+ ASSERT_TRUE(writer->AddLabel(4));
+
ASSERT_TRUE(writer->AddZeroBlocks(50, 2));
+ ASSERT_TRUE(writer->AddLabel(5));
+
+ ASSERT_TRUE(writer->AddCopy(5, 6));
+
+ ASSERT_TRUE(writer->AddLabel(6));
+
ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
writer = std::make_unique<CowWriter>(options);
@@ -509,20 +512,6 @@
ASSERT_FALSE(iter->Done());
auto op = &iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(op->source, 4);
-
- iter->Next();
-
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
- ASSERT_EQ(op->type, kCowLabelOp);
- ASSERT_EQ(op->source, 5);
-
- iter->Next();
-
- ASSERT_FALSE(iter->Done());
- op = &iter->Get();
ASSERT_EQ(op->type, kCowReplaceOp);
ASSERT_TRUE(reader.ReadData(*op, &sink));
ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
@@ -539,6 +528,31 @@
iter->Next();
sink.Reset();
+ ASSERT_FALSE(iter->Done());
+ op = &iter->Get();
+ ASSERT_EQ(op->type, kCowLabelOp);
+ ASSERT_EQ(op->source, 4);
+
+ iter->Next();
+
+ ASSERT_FALSE(iter->Done());
+ op = &iter->Get();
+ ASSERT_EQ(op->type, kCowZeroOp);
+
+ iter->Next();
+
+ ASSERT_FALSE(iter->Done());
+ op = &iter->Get();
+ ASSERT_EQ(op->type, kCowZeroOp);
+
+ iter->Next();
+ ASSERT_FALSE(iter->Done());
+ op = &iter->Get();
+ ASSERT_EQ(op->type, kCowLabelOp);
+ ASSERT_EQ(op->source, 5);
+
+ iter->Next();
+
ASSERT_TRUE(iter->Done());
}
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index f10ccb6..517cd9c 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -140,21 +140,23 @@
return false;
}
current_op_num++;
+ if (next_last_label) {
+ last_label_ = next_last_label.value();
+ has_last_label_ = true;
+ }
if (current_op.type == kCowLabelOp) {
- // If we don't have a footer, the last label may be incomplete
+ // If we don't have a footer, the last label may be incomplete.
+ // If we see any operation after it, we can infer the flush finished.
if (has_footer_) {
has_last_label_ = true;
last_label_ = current_op.source;
} else {
- if (next_last_label) {
- last_label_ = next_last_label.value();
- has_last_label_ = true;
- }
next_last_label = {current_op.source};
}
} else if (current_op.type == kCowFooterOp) {
memcpy(&footer_.op, ¤t_op, sizeof(footer_.op));
-
+ // we don't consider this an operation for the checksum
+ current_op_num--;
if (android::base::ReadFully(fd_, &footer_.data, sizeof(footer_.data))) {
has_footer_ = true;
if (next_last_label) {
@@ -170,6 +172,19 @@
memset(csum, 0, sizeof(uint8_t) * 32);
if (has_footer_) {
+ if (ops_buffer->size() != footer_.op.num_ops) {
+ LOG(ERROR) << "num ops does not match";
+ return false;
+ }
+ if (ops_buffer->size() * sizeof(CowOperation) != footer_.op.ops_size) {
+ LOG(ERROR) << "ops size does not match ";
+ return false;
+ }
+ SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
+ if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
+ LOG(ERROR) << "ops checksum does not match";
+ return false;
+ }
SHA256(ops_buffer.get()->data(), footer_.op.ops_size, csum);
if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
LOG(ERROR) << "ops checksum does not match";
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index ec2dc96..632c866 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -110,15 +110,28 @@
return true;
}
+bool CowWriter::SetFd(android::base::borrowed_fd fd) {
+ if (fd.get() < 0) {
+ owned_fd_.reset(open("/dev/null", O_RDWR | O_CLOEXEC));
+ if (owned_fd_ < 0) {
+ PLOG(ERROR) << "open /dev/null failed";
+ return false;
+ }
+ fd_ = owned_fd_;
+ is_dev_null_ = true;
+ } else {
+ fd_ = fd;
+ }
+ return true;
+}
+
bool CowWriter::Initialize(unique_fd&& fd, OpenMode mode) {
owned_fd_ = std::move(fd);
return Initialize(borrowed_fd{owned_fd_}, mode);
}
bool CowWriter::Initialize(borrowed_fd fd, OpenMode mode) {
- fd_ = fd;
-
- if (!ParseOptions()) {
+ if (!SetFd(fd) || !ParseOptions()) {
return false;
}
@@ -139,9 +152,7 @@
}
bool CowWriter::InitializeAppend(android::base::borrowed_fd fd, uint64_t label) {
- fd_ = fd;
-
- if (!ParseOptions()) {
+ if (!SetFd(fd) || !ParseOptions()) {
return false;
}
@@ -171,10 +182,13 @@
return true;
}
-bool CowWriter::OpenForAppend() {
+bool CowWriter::OpenForAppend(std::optional<uint64_t> label) {
auto reader = std::make_unique<CowReader>();
bool incomplete = false;
+ bool add_next = false;
std::queue<CowOperation> toAdd;
+ bool found_label = false;
+
if (!reader->Parse(fd_) || !reader->GetHeader(&header_)) {
return false;
}
@@ -185,69 +199,41 @@
// Reset this, since we're going to reimport all operations.
footer_.op.num_ops = 0;
next_op_pos_ = sizeof(header_);
+ ops_.resize(0);
auto iter = reader->GetOpIter();
- while (!iter->Done()) {
+ while (!iter->Done() && !found_label) {
CowOperation op = iter->Get();
if (op.type == kCowFooterOp) break;
- if (incomplete) {
- // Last operation translation may be corrupt. Wait to add it.
+ if (label.has_value()) {
+ if (op.type == kCowFooterOp) break;
if (op.type == kCowLabelOp) {
- while (!toAdd.empty()) {
- AddOperation(toAdd.front());
- toAdd.pop();
- }
+ if (op.source == label) found_label = true;
}
- toAdd.push(op);
- } else {
AddOperation(op);
+ } else {
+ if (incomplete) {
+ // Last set of labeled operations may be corrupt. Wait to add it.
+ // We always sync after a label. If we see ops after a label, we
+ // can infer that sync must have completed.
+ if (add_next) {
+ add_next = false;
+ while (!toAdd.empty()) {
+ AddOperation(toAdd.front());
+ toAdd.pop();
+ }
+ }
+ toAdd.push(op);
+ if (op.type == kCowLabelOp) add_next = true;
+ } else {
+ AddOperation(op);
+ }
}
iter->Next();
}
- // Free reader so we own the descriptor position again.
- reader = nullptr;
-
- // Position for new writing
- if (ftruncate(fd_.get(), next_op_pos_) != 0) {
- PLOG(ERROR) << "Failed to trim file";
- return false;
- }
- if (lseek(fd_.get(), 0, SEEK_END) < 0) {
- PLOG(ERROR) << "lseek failed";
- return false;
- }
- return true;
-}
-
-bool CowWriter::OpenForAppend(uint64_t label) {
- auto reader = std::make_unique<CowReader>();
- std::queue<CowOperation> toAdd;
- if (!reader->Parse(fd_) || !reader->GetHeader(&header_)) {
- return false;
- }
-
- options_.block_size = header_.block_size;
- bool found_label = false;
-
- // Reset this, since we're going to reimport all operations.
- footer_.op.num_ops = 0;
- next_op_pos_ = sizeof(header_);
-
- auto iter = reader->GetOpIter();
- while (!iter->Done()) {
- CowOperation op = iter->Get();
- if (op.type == kCowFooterOp) break;
- if (op.type == kCowLabelOp) {
- if (found_label) break;
- if (op.source == label) found_label = true;
- }
- AddOperation(op);
- iter->Next();
- }
-
- if (!found_label) {
- PLOG(ERROR) << "Failed to find last label";
+ if (label.has_value() && !found_label) {
+ LOG(ERROR) << "Failed to find last label";
return false;
}
@@ -329,7 +315,7 @@
CowOperation op = {};
op.type = kCowLabelOp;
op.source = label;
- return WriteOperation(op);
+ return WriteOperation(op) && Sync();
}
std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
@@ -384,7 +370,7 @@
}
bool CowWriter::Finalize() {
- footer_.op.ops_size = ops_.size() + sizeof(footer_.op);
+ footer_.op.ops_size = ops_.size();
uint64_t pos;
if (!GetDataPos(&pos)) {
@@ -408,7 +394,7 @@
PLOG(ERROR) << "lseek ops failed";
return false;
}
- return true;
+ return Sync();
}
uint64_t CowWriter::GetCowSize() {
@@ -429,10 +415,11 @@
if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&op), sizeof(op))) {
return false;
}
- if (data != NULL && size > 0)
+ if (data != nullptr && size > 0) {
if (!WriteRawData(data, size)) return false;
+ }
AddOperation(op);
- return !fsync(fd_.get());
+ return true;
}
void CowWriter::AddOperation(const CowOperation& op) {
@@ -448,5 +435,16 @@
return true;
}
+bool CowWriter::Sync() {
+ if (is_dev_null_) {
+ return true;
+ }
+ if (fsync(fd_.get()) < 0) {
+ PLOG(ERROR) << "fsync failed";
+ return false;
+ }
+ return true;
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index c031d63..35690d4 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -90,6 +90,9 @@
// If opening for write, the file starts from the beginning.
// If opening for append, if the file has a footer, we start appending to the last op.
// If the footer isn't found, the last label is considered corrupt, and dropped.
+ //
+ // If fd is < 0, the CowWriter will be opened against /dev/null. This is for
+ // computing COW sizes without using storage space.
bool Initialize(android::base::unique_fd&& fd, OpenMode mode = OpenMode::WRITE);
bool Initialize(android::base::borrowed_fd fd, OpenMode mode = OpenMode::WRITE);
// Set up a writer, assuming that the given label is the last valid label.
@@ -112,15 +115,16 @@
void SetupHeaders();
bool ParseOptions();
bool OpenForWrite();
- bool OpenForAppend();
- bool OpenForAppend(uint64_t label);
- bool ImportOps(std::unique_ptr<ICowOpIter> iter);
+ bool OpenForAppend(std::optional<uint64_t> label = std::nullopt);
bool GetDataPos(uint64_t* pos);
bool WriteRawData(const void* data, size_t size);
bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
void AddOperation(const CowOperation& op);
std::basic_string<uint8_t> Compress(const void* data, size_t length);
+ bool SetFd(android::base::borrowed_fd fd);
+ bool Sync();
+
private:
android::base::unique_fd owned_fd_;
android::base::borrowed_fd fd_;
@@ -128,6 +132,7 @@
CowFooter footer_{};
int compression_ = 0;
uint64_t next_op_pos_ = 0;
+ bool is_dev_null_ = false;
// :TODO: this is not efficient, but stringstream ubsan aborts because some
// bytes overflow a signed char.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 35ed04a..8bed1b9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -15,6 +15,7 @@
#pragma once
#include <stdint.h>
+#include <unistd.h>
#include <chrono>
#include <map>
@@ -77,6 +78,7 @@
class SnapshotStatus;
static constexpr const std::string_view kCowGroupName = "cow";
+static constexpr char kVirtualAbCompressionProp[] = "ro.virtual_ab.compression.enabled";
bool OptimizeSourceCopyOperation(const chromeos_update_engine::InstallOperation& operation,
chromeos_update_engine::InstallOperation* optimized);
@@ -104,6 +106,7 @@
android::hardware::boot::V1_1::MergeStatus status) = 0;
virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
virtual bool IsRecovery() const = 0;
+ virtual bool IsTestDevice() const { return false; }
};
virtual ~ISnapshotManager() = default;
@@ -303,6 +306,14 @@
// Helper function for second stage init to restorecon on the rollback indicator.
static std::string GetGlobalRollbackIndicatorPath();
+ // Initiate the transition from first-stage to second-stage snapuserd. This
+ // process involves re-creating the dm-user table entries for each device,
+ // so that they connect to the new daemon. Once all new tables have been
+ // activated, we ask the first-stage daemon to cleanly exit.
+ //
+ // The caller must pass a function which starts snapuserd.
+ bool PerformSecondStageTransition();
+
// ISnapshotManager overrides.
bool BeginUpdate() override;
bool CancelUpdate() override;
@@ -345,6 +356,7 @@
FRIEND_TEST(SnapshotTest, Merge);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
+ FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
@@ -372,11 +384,13 @@
// Ensure we're connected to snapuserd.
bool EnsureSnapuserdConnected();
- // Helper for first-stage init.
+ // Helpers for first-stage init.
bool ForceLocalImageManager();
+ const std::unique_ptr<IDeviceInfo>& device() const { return device_; }
- // Helper function for tests.
+ // Helper functions for tests.
IImageManager* image_manager() const { return images_.get(); }
+ void set_use_first_stage_snapuserd(bool value) { use_first_stage_snapuserd_ = value; }
// Since libsnapshot is included into multiple processes, we flock() our
// files for simple synchronization. LockedFile is a helper to assist with
@@ -545,6 +559,9 @@
std::string GetSnapshotDeviceName(const std::string& snapshot_name,
const SnapshotStatus& status);
+ bool MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot,
+ const std::chrono::milliseconds& timeout_ms);
+
// Reason for calling MapPartitionWithSnapshot.
enum class SnapshotContext {
// For writing or verification (during update_engine).
@@ -618,9 +635,12 @@
const LpMetadata* exported_target_metadata, const std::string& target_suffix,
const std::map<std::string, SnapshotStatus>& all_snapshot_status);
+ // Implementation of UnmapAllSnapshots(), with the lock provided.
+ bool UnmapAllSnapshots(LockedFile* lock);
+
// Unmap all partitions that were mapped by CreateLogicalAndSnapshotPartitions.
// This should only be called in recovery.
- bool UnmapAllPartitions();
+ bool UnmapAllPartitionsInRecovery();
// Check no snapshot overflows. Note that this returns false negatives if the snapshot
// overflows, then is remapped and not written afterwards.
@@ -660,6 +680,7 @@
std::unique_ptr<IDeviceInfo> device_;
std::unique_ptr<IImageManager> images_;
bool has_local_image_manager_ = false;
+ bool use_first_stage_snapuserd_ = false;
bool in_factory_data_reset_ = false;
std::unique_ptr<SnapuserdClient> snapuserd_client_;
};
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
index 0bbdaa5..aaec229 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
@@ -14,6 +14,8 @@
#pragma once
+#include <unistd.h>
+
#include <chrono>
#include <cstring>
#include <iostream>
@@ -31,9 +33,15 @@
static constexpr char kSnapuserdSocketFirstStage[] = "snapuserd_first_stage";
static constexpr char kSnapuserdSocket[] = "snapuserd";
+static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";
+
// Ensure that the second-stage daemon for snapuserd is running.
bool EnsureSnapuserdStarted();
+// Start the first-stage version of snapuserd, returning its pid. This is used
+// by first-stage init, as well as vts_libsnapshot_test. On failure, -1 is returned.
+pid_t StartFirstStageSnapuserd();
+
class SnapuserdClient {
private:
android::base::unique_fd sockfd_;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 197aeaa..7aef086 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -95,6 +95,7 @@
unbootable_slots_.insert(slot);
return true;
}
+ bool IsTestDevice() const override { return true; }
bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 0df5664..da6fc9d 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -18,6 +18,7 @@
#include <android-base/logging.h>
#include <android/snapshot/snapshot.pb.h>
+#include <storage_literals/storage_literals.h>
#include "dm_snapshot_internals.h"
#include "utility.h"
@@ -34,6 +35,10 @@
namespace android {
namespace snapshot {
+static constexpr uint64_t kBlockSize = 4096;
+
+using namespace android::storage_literals;
+
// Intersect two linear extents. If no intersection, return an extent with length 0.
static std::unique_ptr<Extent> Intersect(Extent* target_extent, Extent* existing_extent) {
// Convert target_extent and existing_extent to linear extents. Zero extents
@@ -138,6 +143,22 @@
}
uint64_t PartitionCowCreator::GetCowSize() {
+ if (compression_enabled) {
+ if (update == nullptr || !update->has_estimate_cow_size()) {
+ LOG(ERROR) << "Update manifest does not include a COW size";
+ return 0;
+ }
+
+ // Add an extra 2MB of wiggle room for any minor differences in labels/metadata
+ // that might come up.
+ auto size = update->estimate_cow_size() + 2_MiB;
+
+ // Align to nearest block.
+ size += kBlockSize - 1;
+ size &= ~(kBlockSize - 1);
+ return size;
+ }
+
// WARNING: The origin partition should be READ-ONLY
const uint64_t logical_block_size = current_metadata->logical_block_size();
const unsigned int sectors_per_block = logical_block_size / kSectorSize;
@@ -149,9 +170,9 @@
WriteExtent(&sc, de, sectors_per_block);
}
- if (operations == nullptr) return sc.cow_size_bytes();
+ if (update == nullptr) return sc.cow_size_bytes();
- for (const auto& iop : *operations) {
+ for (const auto& iop : update->operations()) {
const InstallOperation* written_op = &iop;
InstallOperation buf;
// Do not allocate space for extents that are going to be skipped
@@ -213,6 +234,9 @@
LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
auto cow_size = GetCowSize();
+ if (!cow_size) {
+ return {};
+ }
// Compute the COW partition size.
uint64_t cow_partition_size = std::min(cow_size, free_region_length);
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 699f9a1..64d186b 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -36,6 +36,7 @@
using MetadataBuilder = android::fs_mgr::MetadataBuilder;
using Partition = android::fs_mgr::Partition;
using InstallOperation = chromeos_update_engine::InstallOperation;
+ using PartitionUpdate = chromeos_update_engine::PartitionUpdate;
template <typename T>
using RepeatedPtrField = google::protobuf::RepeatedPtrField<T>;
@@ -50,11 +51,13 @@
MetadataBuilder* current_metadata = nullptr;
// The suffix of the current slot.
std::string current_suffix;
- // List of operations to be applied on the partition.
- const RepeatedPtrField<InstallOperation>* operations = nullptr;
+ // Partition information from the OTA manifest.
+ const PartitionUpdate* update = nullptr;
// Extra extents that are going to be invalidated during the update
// process.
std::vector<ChromeOSExtent> extra_extents = {};
+ // True if compression is enabled.
+ bool compression_enabled = false;
struct Return {
SnapshotStatus snapshot_status;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index 2970222..e4b476f 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -145,13 +145,15 @@
auto cow_device_size = [](const std::vector<InstallOperation>& iopv, MetadataBuilder* builder_a,
MetadataBuilder* builder_b, Partition* system_b) {
- RepeatedInstallOperationPtr riop(iopv.begin(), iopv.end());
+ PartitionUpdate update;
+ *update.mutable_operations() = RepeatedInstallOperationPtr(iopv.begin(), iopv.end());
+
PartitionCowCreator creator{.target_metadata = builder_b,
.target_suffix = "_b",
.target_partition = system_b,
.current_metadata = builder_a,
.current_suffix = "_a",
- .operations = &riop};
+ .update = &update};
auto ret = creator.Run();
@@ -218,7 +220,7 @@
.target_partition = system_b,
.current_metadata = builder_a.get(),
.current_suffix = "_a",
- .operations = nullptr};
+ .update = nullptr};
auto ret = creator.Run();
@@ -228,6 +230,58 @@
ASSERT_EQ(0u, ret->snapshot_status.cow_partition_size());
}
+TEST_F(PartitionCowCreatorTest, CompressionEnabled) {
+ constexpr uint64_t super_size = 1_MiB;
+ auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2);
+ ASSERT_NE(builder_a, nullptr);
+
+ auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2);
+ ASSERT_NE(builder_b, nullptr);
+ auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(system_b, nullptr);
+ ASSERT_TRUE(builder_b->ResizePartition(system_b, 128_KiB));
+
+ PartitionUpdate update;
+ update.set_estimate_cow_size(256_KiB);
+
+ PartitionCowCreator creator{.target_metadata = builder_b.get(),
+ .target_suffix = "_b",
+ .target_partition = system_b,
+ .current_metadata = builder_a.get(),
+ .current_suffix = "_a",
+ .compression_enabled = true,
+ .update = &update};
+
+ auto ret = creator.Run();
+ ASSERT_TRUE(ret.has_value());
+ ASSERT_EQ(ret->snapshot_status.cow_file_size(), 1458176);
+}
+
+TEST_F(PartitionCowCreatorTest, CompressionWithNoManifest) {
+ constexpr uint64_t super_size = 1_MiB;
+ auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2);
+ ASSERT_NE(builder_a, nullptr);
+
+ auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2);
+ ASSERT_NE(builder_b, nullptr);
+ auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY);
+ ASSERT_NE(system_b, nullptr);
+ ASSERT_TRUE(builder_b->ResizePartition(system_b, 128_KiB));
+
+ PartitionUpdate update;
+
+ PartitionCowCreator creator{.target_metadata = builder_b.get(),
+ .target_suffix = "_b",
+ .target_partition = system_b,
+ .current_metadata = builder_a.get(),
+ .current_suffix = "_a",
+ .compression_enabled = true,
+ .update = nullptr};
+
+ auto ret = creator.Run();
+ ASSERT_FALSE(ret.has_value());
+}
+
TEST(DmSnapshotInternals, CowSizeCalculator) {
SKIP_IF_NON_VIRTUAL_AB();
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 7061d56..793680b 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -31,8 +31,10 @@
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
+#include <fs_mgr/file_wait.h>
#include <fs_mgr_dm_linear.h>
#include <fstab/fstab.h>
#include <libdm/dm.h>
@@ -73,7 +75,7 @@
using chromeos_update_engine::DeltaArchiveManifest;
using chromeos_update_engine::Extent;
using chromeos_update_engine::FileDescriptor;
-using chromeos_update_engine::InstallOperation;
+using chromeos_update_engine::PartitionUpdate;
template <typename T>
using RepeatedPtrField = google::protobuf::RepeatedPtrField<T>;
using std::chrono::duration_cast;
@@ -100,6 +102,12 @@
if (!sm || !sm->ForceLocalImageManager()) {
return nullptr;
}
+
+ // The first-stage version of snapuserd is explicitly started by init. Do
+ // not attempt to using it during tests (which run in normal AOSP).
+ if (!sm->device()->IsTestDevice()) {
+ sm->use_first_stage_snapuserd_ = true;
+ }
return sm;
}
@@ -108,10 +116,6 @@
metadata_dir_ = device_->GetMetadataDir();
}
-static inline bool IsCompressionEnabled() {
- return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
-}
-
static std::string GetCowName(const std::string& snapshot_name) {
return snapshot_name + "-cow";
}
@@ -400,8 +404,15 @@
base_sectors = dev_size / kSectorSize;
}
+ // Use an extra decoration for first-stage init, so we can transition
+ // to a new table entry in second-stage.
+ std::string misc_name = name;
+ if (use_first_stage_snapuserd_) {
+ misc_name += "-init";
+ }
+
DmTable table;
- table.Emplace<DmTargetUser>(0, base_sectors, name);
+ table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
if (!dm.CreateDevice(name, table, path, timeout_ms)) {
return false;
}
@@ -410,7 +421,7 @@
return false;
}
- auto control_device = "/dev/dm-user/" + name;
+ auto control_device = "/dev/dm-user/" + misc_name;
return snapuserd_client_->InitializeSnapuserd(cow_file, base_device, control_device);
}
@@ -1284,6 +1295,107 @@
return RemoveAllUpdateState(lock, before_cancel);
}
+bool SnapshotManager::PerformSecondStageTransition() {
+ LOG(INFO) << "Performing second-stage transition for snapuserd.";
+
+ // Don't use EnsuerSnapuserdConnected() because this is called from init,
+ // and attempting to do so will deadlock.
+ if (!snapuserd_client_) {
+ snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
+ if (!snapuserd_client_) {
+ LOG(ERROR) << "Unable to connect to snapuserd";
+ return false;
+ }
+ }
+
+ auto& dm = DeviceMapper::Instance();
+
+ auto lock = LockExclusive();
+ if (!lock) return false;
+
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock.get(), &snapshots)) {
+ LOG(ERROR) << "Failed to list snapshots.";
+ return false;
+ }
+
+ size_t num_cows = 0;
+ size_t ok_cows = 0;
+ for (const auto& snapshot : snapshots) {
+ std::string cow_name = GetDmUserCowName(snapshot);
+ if (dm.GetState(cow_name) == DmDeviceState::INVALID) {
+ continue;
+ }
+
+ DeviceMapper::TargetInfo target;
+ if (!GetSingleTarget(cow_name, TableQuery::Table, &target)) {
+ continue;
+ }
+
+ auto target_type = DeviceMapper::GetTargetType(target.spec);
+ if (target_type != "user") {
+ LOG(ERROR) << "Unexpected target type for " << cow_name << ": " << target_type;
+ continue;
+ }
+
+ num_cows++;
+
+ DmTable table;
+ table.Emplace<DmTargetUser>(0, target.spec.length, cow_name);
+ if (!dm.LoadTableAndActivate(cow_name, table)) {
+ LOG(ERROR) << "Unable to swap tables for " << cow_name;
+ continue;
+ }
+
+ std::string backing_device;
+ if (!dm.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &backing_device)) {
+ LOG(ERROR) << "Could not get device path for " << GetBaseDeviceName(snapshot);
+ continue;
+ }
+
+ std::string cow_device;
+ if (!dm.GetDmDevicePathByName(GetCowName(snapshot), &cow_device)) {
+ LOG(ERROR) << "Could not get device path for " << GetCowName(snapshot);
+ continue;
+ }
+
+ // Wait for ueventd to acknowledge and create the control device node.
+ std::string control_device = "/dev/dm-user/" + cow_name;
+ if (!android::fs_mgr::WaitForFile(control_device, 10s)) {
+ LOG(ERROR) << "Could not find control device: " << control_device;
+ continue;
+ }
+
+ if (!snapuserd_client_->InitializeSnapuserd(cow_device, backing_device, control_device)) {
+ // This error is unrecoverable. We cannot proceed because reads to
+ // the underlying device will fail.
+ LOG(FATAL) << "Could not initialize snapuserd for " << cow_name;
+ return false;
+ }
+
+ ok_cows++;
+ }
+
+ if (ok_cows != num_cows) {
+ LOG(ERROR) << "Could not transition all snapuserd consumers.";
+ return false;
+ }
+
+ int pid;
+ const char* pid_str = getenv(kSnapuserdFirstStagePidVar);
+ if (pid_str && android::base::ParseInt(pid_str, &pid)) {
+ if (kill(pid, SIGTERM) < 0 && errno != ESRCH) {
+ LOG(ERROR) << "kill snapuserd failed";
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "Could not find or parse " << kSnapuserdFirstStagePidVar
+ << " for snapuserd pid";
+ return false;
+ }
+ return true;
+}
+
std::unique_ptr<LpMetadata> SnapshotManager::ReadCurrentMetadata() {
const auto& opener = device_->GetPartitionOpener();
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
@@ -1593,8 +1705,13 @@
auto lock = LockExclusive();
if (!lock) return false;
- const auto& opener = device_->GetPartitionOpener();
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+ return MapAllPartitions(lock.get(), super_device, slot, timeout_ms);
+}
+
+bool SnapshotManager::MapAllPartitions(LockedFile* lock, const std::string& super_device,
+ uint32_t slot, const std::chrono::milliseconds& timeout_ms) {
+ const auto& opener = device_->GetPartitionOpener();
auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
if (!metadata) {
LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
@@ -1615,12 +1732,20 @@
.partition_opener = &opener,
.timeout_ms = timeout_ms,
};
- if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount,
- nullptr)) {
+ if (!MapPartitionWithSnapshot(lock, std::move(params), SnapshotContext::Mount, nullptr)) {
return false;
}
}
+ if (use_first_stage_snapuserd_) {
+ // Remove the first-stage socket as a precaution, there is no need to
+ // access the daemon anymore and we'll be killing it once second-stage
+ // is running.
+ auto socket = ANDROID_SOCKET_DIR + "/"s + kSnapuserdSocketFirstStage;
+ snapuserd_client_ = nullptr;
+ unlink(socket.c_str());
+ }
+
LOG(INFO) << "Created logical partitions with snapshot.";
return true;
}
@@ -1925,10 +2050,18 @@
LOG(ERROR) << "Cannot unmap " << dm_user_name;
return false;
}
- if (!snapuserd_client_->WaitForDeviceDelete("/dev/dm-user/" + dm_user_name)) {
+
+ auto control_device = "/dev/dm-user/" + dm_user_name;
+ if (!snapuserd_client_->WaitForDeviceDelete(control_device)) {
LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
return false;
}
+
+ // Ensure the control device is gone so we don't run into ABA problems.
+ if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
+ LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
+ return false;
+ }
}
auto cow_name = GetCowName(name);
@@ -1945,14 +2078,49 @@
return true;
}
-bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds&) {
- LOG(ERROR) << "Not yet implemented.";
- return false;
+bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
+ auto lock = LockExclusive();
+ if (!lock) return false;
+
+ auto state = ReadUpdateState(lock.get());
+ if (state == UpdateState::Unverified) {
+ if (GetCurrentSlot() == Slot::Target) {
+ LOG(ERROR) << "Cannot call MapAllSnapshots when booting from the target slot.";
+ return false;
+ }
+ } else if (state != UpdateState::Initiated) {
+ LOG(ERROR) << "Cannot call MapAllSnapshots from update state: " << state;
+ return false;
+ }
+
+ if (!UnmapAllSnapshots(lock.get())) {
+ return false;
+ }
+
+ uint32_t slot = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix());
+ return MapAllPartitions(lock.get(), device_->GetSuperDevice(slot), slot, timeout_ms);
}
bool SnapshotManager::UnmapAllSnapshots() {
- LOG(ERROR) << "Not yet implemented.";
- return false;
+ auto lock = LockExclusive();
+ if (!lock) return false;
+
+ return UnmapAllSnapshots(lock.get());
+}
+
+bool SnapshotManager::UnmapAllSnapshots(LockedFile* lock) {
+ std::vector<std::string> snapshots;
+ if (!ListSnapshots(lock, &snapshots)) {
+ return false;
+ }
+
+ for (const auto& snapshot : snapshots) {
+ if (!UnmapPartitionWithSnapshot(lock, snapshot)) {
+ LOG(ERROR) << "Failed to unmap snapshot: " << snapshot;
+ return false;
+ }
+ }
+ return true;
}
auto SnapshotManager::OpenFile(const std::string& file, int lock_flags)
@@ -2212,15 +2380,35 @@
}
bool SnapshotManager::EnsureSnapuserdConnected() {
- if (!snapuserd_client_) {
+ if (snapuserd_client_) {
+ return true;
+ }
+
+ std::string socket;
+ if (use_first_stage_snapuserd_) {
+ auto pid = StartFirstStageSnapuserd();
+ if (pid < 0) {
+ LOG(ERROR) << "Failed to start snapuserd";
+ return false;
+ }
+
+ auto pid_str = std::to_string(static_cast<int>(pid));
+ if (setenv(kSnapuserdFirstStagePidVar, pid_str.c_str(), 1) < 0) {
+ PLOG(ERROR) << "setenv failed storing the snapuserd pid";
+ }
+
+ socket = kSnapuserdSocketFirstStage;
+ } else {
if (!EnsureSnapuserdStarted()) {
return false;
}
- snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
- if (!snapuserd_client_) {
- LOG(ERROR) << "Unable to connect to snapuserd";
- return false;
- }
+ socket = kSnapuserdSocket;
+ }
+
+ snapuserd_client_ = SnapuserdClient::Connect(socket, 10s);
+ if (!snapuserd_client_) {
+ LOG(ERROR) << "Unable to connect to snapuserd";
+ return false;
}
return true;
}
@@ -2335,8 +2523,9 @@
.target_partition = nullptr,
.current_metadata = current_metadata.get(),
.current_suffix = current_suffix,
- .operations = nullptr,
+ .update = nullptr,
.extra_extents = {},
+ .compression_enabled = IsCompressionEnabled(),
};
auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
@@ -2380,12 +2569,11 @@
return Return::Error();
}
- std::map<std::string, const RepeatedPtrField<InstallOperation>*> install_operation_map;
+ std::map<std::string, const PartitionUpdate*> partition_map;
std::map<std::string, std::vector<Extent>> extra_extents_map;
for (const auto& partition_update : manifest.partitions()) {
auto suffixed_name = partition_update.partition_name() + target_suffix;
- auto&& [it, inserted] =
- install_operation_map.emplace(suffixed_name, &partition_update.operations());
+ auto&& [it, inserted] = partition_map.emplace(suffixed_name, &partition_update);
if (!inserted) {
LOG(ERROR) << "Duplicated partition " << partition_update.partition_name()
<< " in update manifest.";
@@ -2403,10 +2591,10 @@
for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
cow_creator->target_partition = target_partition;
- cow_creator->operations = nullptr;
- auto operations_it = install_operation_map.find(target_partition->name());
- if (operations_it != install_operation_map.end()) {
- cow_creator->operations = operations_it->second;
+ cow_creator->update = nullptr;
+ auto iter = partition_map.find(target_partition->name());
+ if (iter != partition_map.end()) {
+ cow_creator->update = iter->second;
} else {
LOG(INFO) << target_partition->name()
<< " isn't included in the payload, skipping the cow creation.";
@@ -2422,6 +2610,7 @@
// Compute the device sizes for the partition.
auto cow_creator_ret = cow_creator->Run();
if (!cow_creator_ret.has_value()) {
+ LOG(ERROR) << "PartitionCowCreator returned no value for " << target_partition->name();
return Return::Error();
}
@@ -2538,11 +2727,26 @@
return Return::Error();
}
- auto ret = InitializeCow(cow_path);
- if (!ret.is_ok()) {
- LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
- << cow_path;
- return AddRequiredSpace(ret, all_snapshot_status);
+ if (IsCompressionEnabled()) {
+ unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
+ if (fd < 0) {
+ PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
+ << cow_params.partition_name;
+ return Return::Error();
+ }
+
+ CowWriter writer(CowOptions{});
+ if (!writer.Initialize(fd) || !writer.Finalize()) {
+ LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
+ return Return::Error();
+ }
+ } else {
+ auto ret = InitializeKernelCow(cow_path);
+ if (!ret.is_ok()) {
+ LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
+ << cow_path;
+ return AddRequiredSpace(ret, all_snapshot_status);
+ }
}
// Let destructor of created_devices_for_cow to unmap the COW devices.
};
@@ -2700,7 +2904,7 @@
return UnmapPartitionWithSnapshot(lock.get(), target_partition_name);
}
-bool SnapshotManager::UnmapAllPartitions() {
+bool SnapshotManager::UnmapAllPartitionsInRecovery() {
auto lock = LockExclusive();
if (!lock) return false;
@@ -2844,7 +3048,7 @@
}
// Nothing should be depending on partitions now, so unmap them all.
- if (!UnmapAllPartitions()) {
+ if (!UnmapAllPartitionsInRecovery()) {
LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
}
return true;
@@ -2875,7 +3079,7 @@
}
// Nothing should be depending on partitions now, so unmap them all.
- if (!UnmapAllPartitions()) {
+ if (!UnmapAllPartitionsInRecovery()) {
LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
}
return true;
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index a4a652a..0ac79a1 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -90,6 +90,9 @@
op_iter_ = cow_->GetOpIter();
while (!op_iter_->Done()) {
const CowOperation* op = &op_iter_->Get();
+ if (op->type == kCowLabelOp || op->type == kCowFooterOp) {
+ continue;
+ }
if (op->new_block >= ops_.size()) {
ops_.resize(op->new_block + 1, nullptr);
}
@@ -274,7 +277,7 @@
return -1;
}
} else {
- LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << op->type;
+ LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
errno = EINVAL;
return -1;
}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 9660357..74558ab 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -15,6 +15,7 @@
#include <libsnapshot/snapshot.h>
#include <fcntl.h>
+#include <signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -29,6 +30,7 @@
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
#include <fs_mgr/roots.h>
#include <fs_mgr_dm_linear.h>
#include <gtest/gtest.h>
@@ -80,7 +82,6 @@
std::string fake_super;
void MountMetadata();
-bool IsCompressionEnabled();
class SnapshotTest : public ::testing::Test {
public:
@@ -118,6 +119,8 @@
image_manager_ = sm->image_manager();
test_device->set_slot_suffix("_a");
+
+ sm->set_use_first_stage_snapuserd(false);
}
void CleanupTestArtifacts() {
@@ -265,7 +268,7 @@
if (!map_res) {
return map_res;
}
- if (!InitializeCow(cow_device)) {
+ if (!InitializeKernelCow(cow_device)) {
return AssertionFailure() << "Cannot zero fill " << cow_device;
}
if (!sm->UnmapCowImage(name)) {
@@ -792,12 +795,15 @@
group_->add_partition_names("prd");
sys_ = manifest_.add_partitions();
sys_->set_partition_name("sys");
+ sys_->set_estimate_cow_size(6_MiB);
SetSize(sys_, 3_MiB);
vnd_ = manifest_.add_partitions();
vnd_->set_partition_name("vnd");
+ vnd_->set_estimate_cow_size(6_MiB);
SetSize(vnd_, 3_MiB);
prd_ = manifest_.add_partitions();
prd_->set_partition_name("prd");
+ prd_->set_estimate_cow_size(6_MiB);
SetSize(prd_, 3_MiB);
// Initialize source partition metadata using |manifest_|.
@@ -1041,7 +1047,11 @@
auto tgt = MetadataBuilder::New(*opener_, "super", 1);
ASSERT_NE(tgt, nullptr);
ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
- ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
+ if (IsCompressionEnabled()) {
+ ASSERT_EQ(nullptr, tgt->FindPartition("vnd_b-cow"));
+ } else {
+ ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
+ }
ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
// Write some data to target partitions.
@@ -1449,10 +1459,6 @@
MetadataMountedTest().TearDown();
}
-bool IsCompressionEnabled() {
- return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
-}
-
TEST_F(MetadataMountedTest, Android) {
auto device = sm->EnsureMetadataMounted();
EXPECT_NE(nullptr, device);
@@ -1736,6 +1742,74 @@
ASSERT_LT(res.required_size(), 15_MiB);
}
+class AutoKill final {
+ public:
+ explicit AutoKill(pid_t pid) : pid_(pid) {}
+ ~AutoKill() {
+ if (pid_ > 0) kill(pid_, SIGKILL);
+ }
+
+ bool valid() const { return pid_ > 0; }
+
+ private:
+ pid_t pid_;
+};
+
+TEST_F(SnapshotUpdateTest, DaemonTransition) {
+ if (!IsCompressionEnabled()) {
+ GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+ }
+
+ AutoKill auto_kill(StartFirstStageSnapuserd());
+ ASSERT_TRUE(auto_kill.valid());
+
+ // Ensure a connection to the second-stage daemon, but use the first-stage
+ // code paths thereafter.
+ ASSERT_TRUE(sm->EnsureSnapuserdConnected());
+ sm->set_use_first_stage_snapuserd(true);
+
+ AddOperationForPartitions();
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ ASSERT_TRUE(MapUpdateSnapshots());
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+ ASSERT_TRUE(UnmapAll());
+
+ auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+ ASSERT_NE(init, nullptr);
+
+ ASSERT_TRUE(init->EnsureSnapuserdConnected());
+ init->set_use_first_stage_snapuserd(true);
+
+ ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+ ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+ ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
+ ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
+
+ ASSERT_TRUE(init->PerformSecondStageTransition());
+
+ // The control device should have been renamed.
+ ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
+ ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
+}
+
+TEST_F(SnapshotUpdateTest, MapAllSnapshots) {
+ AddOperationForPartitions();
+ // Execute the update.
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+ for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+ ASSERT_TRUE(WriteSnapshotAndHash(name));
+ }
+ ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+ ASSERT_TRUE(sm->MapAllSnapshots(10s));
+
+ // Read bytes back and verify they match the cache.
+ ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
+}
+
class FlashAfterUpdateTest : public SnapshotUpdateTest,
public WithParamInterface<std::tuple<uint32_t, bool>> {
public:
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 35bb29b..5650139 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -51,6 +51,25 @@
return true;
}
+pid_t StartFirstStageSnapuserd() {
+ pid_t pid = fork();
+ if (pid < 0) {
+ PLOG(ERROR) << "fork failed";
+ return pid;
+ }
+ if (pid != 0) {
+ return pid;
+ }
+
+ std::string arg0 = "/system/bin/snapuserd";
+ std::string arg1 = kSnapuserdSocketFirstStage;
+ char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+ if (execv(arg0.c_str(), argv) < 0) {
+ PLOG(FATAL) << "execv failed";
+ }
+ return pid;
+}
+
SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {}
static inline bool IsRetryErrno() {
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 6b8cdd9..6a89218 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -191,6 +191,8 @@
}
void SnapuserdServer::RunThread(DmUserHandler* handler) {
+ LOG(INFO) << "Entering thread for handler: " << handler->GetControlDevice();
+
while (!StopRequested()) {
if (handler->snapuserd()->Run() < 0) {
LOG(INFO) << "Snapuserd: Thread terminating as control device is de-registered";
@@ -198,6 +200,8 @@
}
}
+ LOG(INFO) << "Exiting thread for handler: " << handler->GetControlDevice();
+
if (auto client = RemoveHandler(handler->GetControlDevice())) {
// The main thread did not receive a WaitForDelete request for this
// control device. Since we transferred ownership within the lock,
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index 202e39b..dda214e 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -62,6 +62,7 @@
repeated InstallOperation operations = 8;
optional Extent hash_tree_extent = 11;
optional Extent fec_extent = 15;
+ optional uint64 estimate_cow_size = 19;
}
message DynamicPartitionGroup {
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index d32b61e..7342fd4 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -22,6 +22,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <fs_mgr/roots.h>
@@ -91,7 +92,7 @@
}
}
-Return InitializeCow(const std::string& device) {
+Return InitializeKernelCow(const std::string& device) {
// When the kernel creates a persistent dm-snapshot, it requires a CoW file
// to store the modifications. The kernel interface does not specify how
// the CoW is used, and there is no standard associated.
@@ -182,5 +183,9 @@
new_extent->set_num_blocks(num_blocks);
}
+bool IsCompressionEnabled() {
+ return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
+}
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index e69bdad..3e6873b 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -112,7 +112,7 @@
android::fs_mgr::MetadataBuilder* builder, const std::string& suffix);
// Initialize a device before using it as the COW device for a dm-snapshot device.
-Return InitializeCow(const std::string& device);
+Return InitializeKernelCow(const std::string& device);
// "Atomically" write string to file. This is done by a series of actions:
// 1. Write to path + ".tmp"
@@ -129,5 +129,7 @@
void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Extent>* extents,
uint64_t start_block, uint64_t num_blocks);
+bool IsCompressionEnabled();
+
} // namespace snapshot
} // namespace android
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index e995888..f5bbe35 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -1380,9 +1380,9 @@
check_eq "${VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/hello </dev/null`" vendor devt after reboot
check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
check_eq "${VENDOR_INO}" "`adb_sh stat --format=%i /vendor/hello </dev/null`" vendor inode after reboot
-check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" base system devt after reboot
-check_eq "${BASE_VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/bin/stat </dev/null`" base system devt after reboot
-check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" devt for su after reboot
+check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" --warning base system devt after reboot
+check_eq "${BASE_VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/bin/stat </dev/null`" --warning base vendor devt after reboot
+check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
# Feed log with selinux denials as a result of overlays
adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
@@ -1509,8 +1509,8 @@
check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
- check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" base system devt after reboot
- check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" devt for su after reboot
+ check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" --warning base system devt after reboot
+ check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
fi
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index b9f9a19..843ac5c 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -99,6 +99,34 @@
return cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
}
+// Move e2fsck before switching root, so that it is available at the same path
+// after switching root.
+void PrepareSwitchRoot() {
+ constexpr const char* src = "/system/bin/e2fsck";
+ constexpr const char* dst = "/first_stage_ramdisk/system/bin/e2fsck";
+
+ if (access(dst, X_OK) == 0) {
+ LOG(INFO) << dst << " already exists and it can be executed";
+ return;
+ }
+
+ if (access(src, F_OK) != 0) {
+ PLOG(INFO) << "Not moving " << src << " because it cannot be accessed";
+ return;
+ }
+
+ auto dst_dir = android::base::Dirname(dst);
+ std::error_code ec;
+ if (!fs::create_directories(dst_dir, ec)) {
+ LOG(FATAL) << "Cannot create " << dst_dir << ": " << ec.message();
+ }
+ if (rename(src, dst) != 0) {
+ PLOG(FATAL) << "Cannot move " << src << " to " << dst
+ << ". Either install e2fsck.ramdisk so that it is at the correct place (" << dst
+ << "), or make ramdisk writable";
+ }
+}
+
} // namespace
std::string GetModuleLoadList(bool recovery, const std::string& dir_path) {
@@ -298,6 +326,7 @@
if (ForceNormalBoot(cmdline)) {
mkdir("/first_stage_ramdisk", 0755);
+ PrepareSwitchRoot();
// SwitchRoot() must be called with a mount point as the target, so we bind mount the
// target directory to itself here.
if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
diff --git a/init/init.cpp b/init/init.cpp
index ea04494..c6f2066 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -53,6 +53,7 @@
#include <keyutils.h>
#include <libavb/libavb.h>
#include <libgsi/libgsi.h>
+#include <libsnapshot/snapshot.h>
#include <processgroup/processgroup.h>
#include <processgroup/setup.h>
#include <selinux/android.h>
@@ -94,6 +95,7 @@
using android::base::Timer;
using android::base::Trim;
using android::fs_mgr::AvbHandle;
+using android::snapshot::SnapshotManager;
namespace android {
namespace init {
@@ -722,6 +724,32 @@
}
}
+static Result<void> TransitionSnapuserdAction(const BuiltinArguments&) {
+ if (!SnapshotManager::IsSnapshotManagerNeeded() ||
+ !android::base::GetBoolProperty(android::snapshot::kVirtualAbCompressionProp, false)) {
+ return {};
+ }
+
+ auto sm = SnapshotManager::New();
+ if (!sm) {
+ LOG(FATAL) << "Failed to create SnapshotManager, will not transition snapuserd";
+ return {};
+ }
+
+ ServiceList& service_list = ServiceList::GetInstance();
+ auto svc = service_list.FindService("snapuserd");
+ if (!svc) {
+ LOG(FATAL) << "Failed to find snapuserd service, aborting transition";
+ return {};
+ }
+ svc->Start();
+
+ if (!sm->PerformSecondStageTransition()) {
+ LOG(FATAL) << "Failed to transition snapuserd to second-stage";
+ }
+ return {};
+}
+
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
@@ -847,6 +875,7 @@
SetProperty(gsi::kGsiInstalledProp, is_installed);
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
+ am.QueueBuiltinAction(TransitionSnapuserdAction, "TransitionSnapuserd");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueEventTrigger("early-init");
diff --git a/init/service.cpp b/init/service.cpp
index ecc86d9..7b98392 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -329,8 +329,8 @@
LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
<< exit_reason;
} else {
- LOG(ERROR) << "updatable process '" << name_ << "' exited 4 times "
- << exit_reason;
+ LOG(ERROR) << "process with updatable components '" << name_
+ << "' exited 4 times " << exit_reason;
// Notifies update_verifier and apexd
SetProperty("sys.init.updatable_crashing_process_name", name_);
SetProperty("sys.init.updatable_crashing", "1");
diff --git a/libbacktrace b/libbacktrace
deleted file mode 120000
index 571194c..0000000
--- a/libbacktrace
+++ /dev/null
@@ -1 +0,0 @@
-../unwinding/libbacktrace
\ No newline at end of file
diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp
index b56dcdb..c3f8692 100644
--- a/libpackagelistparser/Android.bp
+++ b/libpackagelistparser/Android.bp
@@ -1,5 +1,7 @@
cc_library {
name: "libpackagelistparser",
+ ramdisk_available: true,
+ vendor_ramdisk_available: true,
recovery_available: true,
srcs: ["packagelistparser.cpp"],
shared_libs: ["liblog"],
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index d101774..71e2b91 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -32,6 +32,8 @@
name: "libprocessgroup",
host_supported: true,
native_bridge_supported: true,
+ ramdisk_available: true,
+ vendor_ramdisk_available: true,
recovery_available: true,
vendor_available: true,
vndk: {
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index a107baa..bb59942 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -15,6 +15,8 @@
cc_library {
name: "libcgrouprc",
host_supported: true,
+ ramdisk_available: true,
+ vendor_ramdisk_available: true,
recovery_available: true,
// Do not ever mark this as vendor_available; otherwise, vendor modules
// that links to the static library will behave unexpectedly. All on-device
diff --git a/libprocessgroup/cgrouprc_format/Android.bp b/libprocessgroup/cgrouprc_format/Android.bp
index 559a869..6428930 100644
--- a/libprocessgroup/cgrouprc_format/Android.bp
+++ b/libprocessgroup/cgrouprc_format/Android.bp
@@ -15,6 +15,8 @@
cc_library_static {
name: "libcgrouprc_format",
host_supported: true,
+ ramdisk_available: true,
+ vendor_ramdisk_available: true,
recovery_available: true,
native_bridge_supported: true,
srcs: [
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index a638fca..4e767db 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -24,6 +24,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/threads.h>
#include <cutils/android_filesystem_config.h>
@@ -38,6 +39,7 @@
using android::base::GetThreadId;
using android::base::StringPrintf;
+using android::base::StringReplace;
using android::base::unique_fd;
using android::base::WriteStringToFile;
@@ -257,6 +259,39 @@
return true;
}
+bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+ std::string filepath(filepath_), value(value_);
+
+ filepath = StringReplace(filepath, "<uid>", std::to_string(uid), true);
+ filepath = StringReplace(filepath, "<pid>", std::to_string(pid), true);
+ value = StringReplace(value, "<uid>", std::to_string(uid), true);
+ value = StringReplace(value, "<pid>", std::to_string(pid), true);
+
+ if (!WriteStringToFile(value, filepath)) {
+ PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+ return false;
+ }
+
+ return true;
+}
+
+bool WriteFileAction::ExecuteForTask(int tid) const {
+ std::string filepath(filepath_), value(value_);
+ int uid = getuid();
+
+ filepath = StringReplace(filepath, "<uid>", std::to_string(uid), true);
+ filepath = StringReplace(filepath, "<pid>", std::to_string(tid), true);
+ value = StringReplace(value, "<uid>", std::to_string(uid), true);
+ value = StringReplace(value, "<pid>", std::to_string(tid), true);
+
+ if (!WriteStringToFile(value, filepath)) {
+ PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+ return false;
+ }
+
+ return true;
+}
+
bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
for (const auto& profile : profiles_) {
if (!profile->ExecuteForProcess(uid, pid)) {
@@ -459,6 +494,18 @@
} else {
LOG(WARNING) << "SetClamps: invalid parameter: " << boost_value;
}
+ } else if (action_name == "WriteFile") {
+ std::string attr_filepath = params_val["FilePath"].asString();
+ std::string attr_value = params_val["Value"].asString();
+ if (!attr_filepath.empty() && !attr_value.empty()) {
+ profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value));
+ } else if (attr_filepath.empty()) {
+ LOG(WARNING) << "WriteFile: invalid parameter: "
+ << "empty filepath";
+ } else if (attr_value.empty()) {
+ LOG(WARNING) << "WriteFile: invalid parameter: "
+ << "empty value";
+ }
} else {
LOG(WARNING) << "Unknown profile action: " << action_name;
}
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 2983a09..98bcb0e 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -139,6 +139,19 @@
bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
};
+// Write to file action
+class WriteFileAction : public ProfileAction {
+ public:
+ WriteFileAction(const std::string& filepath, const std::string& value) noexcept
+ : filepath_(filepath), value_(value) {}
+
+ virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+ virtual bool ExecuteForTask(int tid) const;
+
+ private:
+ std::string filepath_, value_;
+};
+
class TaskProfile {
public:
TaskProfile() : res_cached_(false) {}
diff --git a/libprocinfo b/libprocinfo
deleted file mode 120000
index dec8cf8..0000000
--- a/libprocinfo
+++ /dev/null
@@ -1 +0,0 @@
-../libprocinfo
\ No newline at end of file
diff --git a/libunwindstack b/libunwindstack
deleted file mode 120000
index 9a12403..0000000
--- a/libunwindstack
+++ /dev/null
@@ -1 +0,0 @@
-../unwinding/libunwindstack
\ No newline at end of file
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index f83c43e..5e013fe 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -51,3 +51,13 @@
"toybox_vendor",
],
}
+
+// shell and utilities for first stage console. The list of binaries are
+// enough for debugging purposes.
+phony {
+ name: "shell_and_utilities_vendor_ramdisk",
+ required: [
+ "sh.vendor_ramdisk",
+ "toybox.vendor_ramdisk",
+ ],
+}
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
new file mode 100644
index 0000000..969431c
--- /dev/null
+++ b/trusty/fuzz/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 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.
+
+cc_defaults {
+ name: "trusty_fuzzer_defaults",
+ static_libs: [
+ "libtrusty_fuzz_utils",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ fuzz_config: {
+ fuzz_on_haiku_device: false,
+ fuzz_on_haiku_host: false,
+ },
+}
+
+cc_library {
+ name: "libtrusty_fuzz_utils",
+ srcs: ["utils.cpp"],
+ export_include_dirs: ["include"],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+}
diff --git a/trusty/fuzz/include/trusty/fuzz/utils.h b/trusty/fuzz/include/trusty/fuzz/utils.h
new file mode 100644
index 0000000..bca84e9
--- /dev/null
+++ b/trusty/fuzz/include/trusty/fuzz/utils.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+#define TIPC_MAX_MSG_SIZE PAGE_SIZE
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+class TrustyApp {
+ public:
+ TrustyApp(std::string tipc_dev, std::string ta_port);
+
+ android::base::Result<void> Connect();
+ android::base::Result<void> Read(void* buf, size_t len);
+ android::base::Result<void> Write(const void* buf, size_t len);
+
+ android::base::Result<int> GetRawFd();
+
+ private:
+ std::string tipc_dev_;
+ std::string ta_port_;
+ android::base::unique_fd ta_fd_;
+};
+
+void Abort();
+
+} // namespace fuzz
+} // namespace trusty
+} // namespace android
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
new file mode 100644
index 0000000..240afe7
--- /dev/null
+++ b/trusty/fuzz/utils.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 The Android Open Sourete Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "trusty-fuzz-utils"
+
+#include <trusty/fuzz/utils.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+#include <linux/uio.h>
+#include <log/log_read.h>
+#include <time.h>
+#include <iostream>
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+
+#define TIPC_IOC_MAGIC 'r'
+#define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char*)
+
+namespace {
+
+const size_t kTimeoutSeconds = 5;
+const std::string kTrustyLogTag = "trusty-log";
+
+const time_t kInitialTime = time(nullptr);
+
+void PrintTrustyLog() {
+ auto logger_list = android_logger_list_open(LOG_ID_KERNEL, ANDROID_LOG_NONBLOCK, 1000, 0);
+ if (logger_list == nullptr) {
+ std::cerr << "Could not open android kernel log\n";
+ return;
+ }
+
+ while (true) {
+ log_msg log_msg;
+ int rc = android_logger_list_read(logger_list, &log_msg);
+ if (rc < 0) {
+ break;
+ }
+ if (log_msg.entry.sec < kInitialTime) {
+ continue;
+ }
+ char* msg = log_msg.msg();
+ if (msg) {
+ std::string line(msg, log_msg.entry.len);
+ if (line.find(kTrustyLogTag) != std::string::npos) {
+ std::cerr << line.substr(kTrustyLogTag.length() + 2) << std::endl;
+ }
+ }
+ }
+
+ android_logger_list_free(logger_list);
+}
+
+} // namespace
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+TrustyApp::TrustyApp(std::string tipc_dev, std::string ta_port)
+ : tipc_dev_(tipc_dev), ta_port_(ta_port), ta_fd_(-1) {}
+
+Result<void> TrustyApp::Connect() {
+ /*
+ * TODO: We can't use libtrusty because (yet)
+ * (1) cc_fuzz can't deal with vendor components (b/170753563)
+ * (2) We need non-blocking behavior to detect Trusty going down.
+ * (we could implement the timeout in the fuzzing code though, as
+ * it needs to be around the call to read())
+ */
+ alarm(kTimeoutSeconds);
+ int fd = open(tipc_dev_.c_str(), O_RDWR);
+ alarm(0);
+ if (fd < 0) {
+ return ErrnoError() << "failed to open TIPC device: ";
+ }
+ ta_fd_.reset(fd);
+
+ // This ioctl will time out in the kernel if it can't connect.
+ int rc = TEMP_FAILURE_RETRY(ioctl(ta_fd_, TIPC_IOC_CONNECT, ta_port_.c_str()));
+ if (rc < 0) {
+ return ErrnoError() << "failed to connect to TIPC service: ";
+ }
+
+ return {};
+}
+
+Result<void> TrustyApp::Read(void* buf, size_t len) {
+ if (ta_fd_ == -1) {
+ return Error() << "TA is not connected to yet: ";
+ }
+
+ alarm(kTimeoutSeconds);
+ int rc = read(ta_fd_, buf, len);
+ alarm(0);
+ if (rc < 0) {
+ return Error() << "failed to read TIPC message from TA: ";
+ }
+
+ return {};
+}
+
+Result<void> TrustyApp::Write(const void* buf, size_t len) {
+ if (ta_fd_ == -1) {
+ return Error() << "TA is not connected to yet: ";
+ }
+
+ alarm(kTimeoutSeconds);
+ int rc = write(ta_fd_, buf, len);
+ alarm(0);
+ if (rc < 0) {
+ return Error() << "failed to read TIPC message from TA: ";
+ }
+
+ return {};
+}
+
+Result<int> TrustyApp::GetRawFd() {
+ if (ta_fd_ == -1) {
+ return Error() << "TA is not connected to yet: ";
+ }
+
+ return ta_fd_;
+}
+
+void Abort() {
+ PrintTrustyLog();
+ exit(-1);
+}
+
+} // namespace fuzz
+} // namespace trusty
+} // namespace android
diff --git a/trusty/gatekeeper/fuzz/Android.bp b/trusty/gatekeeper/fuzz/Android.bp
new file mode 100644
index 0000000..7ffa776
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 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.
+
+cc_fuzz {
+ name: "trusty_gatekeeper_fuzzer",
+ defaults: ["trusty_fuzzer_defaults"],
+ srcs: ["fuzz.cpp"],
+
+ // The initial corpus for this fuzzer was derived by dumping messages from
+ // the `secure_env` emulator interface for cuttlefish while enrolling a new
+ // password in the emulator.
+ corpus: ["corpus/*"],
+}
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-2MMzSr b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-2MMzSr
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-2MMzSr
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Et63W0 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Et63W0
new file mode 100644
index 0000000..b3e6585
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Et63W0
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-G41Iz8 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-G41Iz8
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-G41Iz8
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-ItEoqJ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-ItEoqJ
new file mode 100644
index 0000000..85d38c7
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-ItEoqJ
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-MGXdfu b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-MGXdfu
new file mode 100644
index 0000000..f8e1467
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-MGXdfu
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Yq4f10 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Yq4f10
new file mode 100644
index 0000000..c221077
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Yq4f10
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-agxKZa b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-agxKZa
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-agxKZa
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-alhn2v b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-alhn2v
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-alhn2v
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-eVJFHV b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-eVJFHV
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-eVJFHV
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-et5K21 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-et5K21
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-et5K21
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-gun5YX b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-gun5YX
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-gun5YX
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-kXw1R9 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-kXw1R9
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-kXw1R9
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-moapss b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-moapss
new file mode 100644
index 0000000..85d38c7
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-moapss
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-u5QySb b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-u5QySb
new file mode 100644
index 0000000..09f9d74
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-u5QySb
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-uZtvkq b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-uZtvkq
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-uZtvkq
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-w5G2SF b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-w5G2SF
new file mode 100644
index 0000000..d42956d
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-w5G2SF
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-y3H74x b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-y3H74x
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-y3H74x
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-yALfeS b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-yALfeS
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-yALfeS
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-2S1GLi b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-2S1GLi
new file mode 100644
index 0000000..08b3449
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-2S1GLi
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-4j7hUc b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-4j7hUc
new file mode 100644
index 0000000..5507400
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-4j7hUc
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-6hsSQG b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-6hsSQG
new file mode 100644
index 0000000..ffa74cb
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-6hsSQG
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-E8CE7b b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-E8CE7b
new file mode 100644
index 0000000..21cdd9c
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-E8CE7b
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-GEDmHj b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-GEDmHj
new file mode 100644
index 0000000..23a8c08
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-GEDmHj
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-MpwDEN b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-MpwDEN
new file mode 100644
index 0000000..1795d09
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-MpwDEN
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Qutf8O b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Qutf8O
new file mode 100644
index 0000000..4f69edf
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Qutf8O
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Sg1WMt b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Sg1WMt
new file mode 100644
index 0000000..ba6d1cb
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Sg1WMt
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-U6Y1My b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-U6Y1My
new file mode 100644
index 0000000..631ef79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-U6Y1My
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-WdSRky b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-WdSRky
new file mode 100644
index 0000000..02d4820
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-WdSRky
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Ypw6WP b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Ypw6WP
new file mode 100644
index 0000000..6d7574f
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Ypw6WP
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Yyj4Af b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Yyj4Af
new file mode 100644
index 0000000..47f518d
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Yyj4Af
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-amyF62 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-amyF62
new file mode 100644
index 0000000..3a5fdf5
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-amyF62
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-gu8ziA b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-gu8ziA
new file mode 100644
index 0000000..bab5da1
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-gu8ziA
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-iCATsM b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-iCATsM
new file mode 100644
index 0000000..fae9173
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-iCATsM
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-kawT3I b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-kawT3I
new file mode 100644
index 0000000..51e3630
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-kawT3I
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-sYFzM5 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-sYFzM5
new file mode 100644
index 0000000..173d77e
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-sYFzM5
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-yNFMdn b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-yNFMdn
new file mode 100644
index 0000000..96f9e42
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-yNFMdn
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/fuzz.cpp b/trusty/gatekeeper/fuzz/fuzz.cpp
new file mode 100644
index 0000000..f8ec931
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/fuzz.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <trusty/fuzz/utils.h>
+#include <unistd.h>
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define GATEKEEPER_PORT "com.android.trusty.gatekeeper"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static uint8_t buf[TIPC_MAX_MSG_SIZE];
+
+ android::trusty::fuzz::TrustyApp ta(TIPC_DEV, GATEKEEPER_PORT);
+
+ auto ret = ta.Connect();
+ /*
+ * If we can't connect, then assume TA crashed.
+ * TODO: Get some more info, e.g. stacks, to help Haiku dedup crashes.
+ */
+ if (!ret.ok()) {
+ android::trusty::fuzz::Abort();
+ }
+
+ /* Send message to test server */
+ ret = ta.Write(data, size);
+ if (!ret.ok()) {
+ return -1;
+ }
+
+ /* Read message from test server */
+ ret = ta.Read(&buf, sizeof(buf));
+ if (!ret.ok()) {
+ return -1;
+ }
+
+ return 0;
+}