Merge "Create /system_dlkm mount point for GKI modules."
diff --git a/diagnose_usb/Android.bp b/diagnose_usb/Android.bp
index cb79ffe..d7d87b5 100644
--- a/diagnose_usb/Android.bp
+++ b/diagnose_usb/Android.bp
@@ -7,6 +7,7 @@
cflags: ["-Wall", "-Wextra", "-Werror"],
host_supported: true,
recovery_available: true,
+ min_sdk_version: "apex_inherit",
apex_available: [
"com.android.adbd",
// TODO(b/151398197) remove the below
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 339f392..708a677 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -166,8 +166,10 @@
"android.hardware.boot@1.1",
"android.hardware.fastboot@1.1",
"android.hardware.health@2.0",
+ "android.hardware.health-V1-ndk",
"libasyncio",
"libbase",
+ "libbinder_ndk",
"libbootloader_message",
"libcutils",
"libext2_uuid",
@@ -183,8 +185,10 @@
],
static_libs: [
+ "android.hardware.health-translate-ndk",
"libc++fs",
"libhealthhalutils",
+ "libhealthshim",
"libsnapshot_cow",
"libsnapshot_nobinder",
"update_metadata-protos",
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 64a934d..e6a834e 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -21,10 +21,12 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
+#include <android/binder_manager.h>
#include <android/hardware/boot/1.0/IBootControl.h>
#include <android/hardware/fastboot/1.1/IFastboot.h>
#include <fs_mgr.h>
#include <fs_mgr/roots.h>
+#include <health-shim/shim.h>
#include <healthhalutils/HealthHalUtils.h>
#include "constants.h"
@@ -32,16 +34,36 @@
#include "tcp_client.h"
#include "usb_client.h"
+using std::string_literals::operator""s;
using android::fs_mgr::EnsurePathUnmounted;
using android::fs_mgr::Fstab;
using ::android::hardware::hidl_string;
using ::android::hardware::boot::V1_0::IBootControl;
using ::android::hardware::boot::V1_0::Slot;
using ::android::hardware::fastboot::V1_1::IFastboot;
-using ::android::hardware::health::V2_0::get_health_service;
namespace sph = std::placeholders;
+std::shared_ptr<aidl::android::hardware::health::IHealth> get_health_service() {
+ using aidl::android::hardware::health::IHealth;
+ using HidlHealth = android::hardware::health::V2_0::IHealth;
+ using aidl::android::hardware::health::HealthShim;
+ auto service_name = IHealth::descriptor + "/default"s;
+ if (AServiceManager_isDeclared(service_name.c_str())) {
+ ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+ std::shared_ptr<IHealth> health = IHealth::fromBinder(binder);
+ if (health != nullptr) return health;
+ LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved.";
+ }
+ LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+ android::sp<HidlHealth> hidl_health = android::hardware::health::V2_0::get_health_service();
+ if (hidl_health != nullptr) {
+ return ndk::SharedRefBase::make<HealthShim>(hidl_health);
+ }
+ LOG(WARNING) << "No health implementation is found.";
+ return nullptr;
+}
+
FastbootDevice::FastbootDevice()
: kCommandMap({
{FB_CMD_SET_ACTIVE, SetActiveHandler},
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 3536136..91ffce3 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -22,10 +22,10 @@
#include <utility>
#include <vector>
+#include <aidl/android/hardware/health/IHealth.h>
#include <android/hardware/boot/1.0/IBootControl.h>
#include <android/hardware/boot/1.1/IBootControl.h>
#include <android/hardware/fastboot/1.1/IFastboot.h>
-#include <android/hardware/health/2.0/IHealth.h>
#include "commands.h"
#include "transport.h"
@@ -57,7 +57,7 @@
android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal() {
return fastboot_hal_;
}
- android::sp<android::hardware::health::V2_0::IHealth> health_hal() { return health_hal_; }
+ std::shared_ptr<aidl::android::hardware::health::IHealth> health_hal() { return health_hal_; }
void set_active_slot(const std::string& active_slot) { active_slot_ = active_slot; }
@@ -67,7 +67,7 @@
std::unique_ptr<Transport> transport_;
android::sp<android::hardware::boot::V1_0::IBootControl> boot_control_hal_;
android::sp<android::hardware::boot::V1_1::IBootControl> boot1_1_;
- android::sp<android::hardware::health::V2_0::IHealth> health_hal_;
+ std::shared_ptr<aidl::android::hardware::health::IHealth> health_hal_;
android::sp<android::hardware::fastboot::V1_1::IFastboot> fastboot_hal_;
std::vector<char> download_data_;
std::string active_slot_;
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index ee1eed8..76e9889 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -26,7 +26,6 @@
#include <android/hardware/boot/1.1/IBootControl.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
-#include <healthhalutils/HealthHalUtils.h>
#include <liblp/liblp.h>
#include "fastboot_device.h"
@@ -120,23 +119,17 @@
}
bool GetBatteryVoltageHelper(FastbootDevice* device, int32_t* battery_voltage) {
- using android::hardware::health::V2_0::HealthInfo;
- using android::hardware::health::V2_0::Result;
+ using aidl::android::hardware::health::HealthInfo;
auto health_hal = device->health_hal();
if (!health_hal) {
return false;
}
- Result ret;
- auto ret_val = health_hal->getHealthInfo([&](Result result, HealthInfo info) {
- *battery_voltage = info.legacy.batteryVoltage;
- ret = result;
- });
- if (!ret_val.isOk() || (ret != Result::SUCCESS)) {
- return false;
- }
-
+ HealthInfo health_info;
+ auto res = health_hal->getHealthInfo(&health_info);
+ if (!res.isOk()) return false;
+ *battery_voltage = health_info.batteryVoltageMillivolts;
return true;
}
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 6b0293a..8b269cd 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -236,52 +236,7 @@
"libbrotli",
"libc++fs",
"libfs_mgr_binder",
- "libgsi",
- "libgmock",
- "liblp",
- "libsnapshot",
- "libsnapshot_cow",
- "libsnapshot_test_helpers",
- "libsparse",
- ],
- header_libs: [
- "libstorage_literals_headers",
- ],
- test_suites: [
- "vts",
- "device-tests"
- ],
- test_options: {
- min_shipping_api_level: 29,
- },
- auto_gen_config: true,
- require_root: true,
-}
-
-cc_defaults {
- name: "userspace_snapshot_test_defaults",
- defaults: ["libsnapshot_defaults"],
- srcs: [
- "partition_cow_creator_test.cpp",
- "snapshot_metadata_updater_test.cpp",
- "snapshot_reader_test.cpp",
- "userspace_snapshot_test.cpp",
- "snapshot_writer_test.cpp",
- ],
- shared_libs: [
- "libbinder",
- "libcrypto",
- "libhidlbase",
- "libprotobuf-cpp-lite",
- "libutils",
- "libz",
- ],
- static_libs: [
- "android.hardware.boot@1.0",
- "android.hardware.boot@1.1",
- "libbrotli",
- "libc++fs",
- "libfs_mgr_binder",
+ "libgflags",
"libgsi",
"libgmock",
"liblp",
@@ -309,9 +264,15 @@
defaults: ["libsnapshot_test_defaults"],
}
-cc_test {
- name: "vts_userspace_snapshot_test",
- defaults: ["userspace_snapshot_test_defaults"],
+sh_test {
+ name: "run_snapshot_tests",
+ src: "run_snapshot_tests.sh",
+ test_suites: [
+ "device-tests",
+ ],
+ required: [
+ "vts_libsnapshot_test",
+ ],
}
cc_binary {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 08c3920..41c6ef5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -793,7 +793,8 @@
// Helper of UpdateUsesCompression
bool UpdateUsesCompression(LockedFile* lock);
- // Helper of UpdateUsesUsersnapshots
+ // Locked and unlocked functions to test whether the current update uses
+ // userspace snapshots.
bool UpdateUsesUserSnapshots(LockedFile* lock);
// Wrapper around libdm, with diagnostics.
diff --git a/fs_mgr/libsnapshot/run_snapshot_tests.sh b/fs_mgr/libsnapshot/run_snapshot_tests.sh
new file mode 100644
index 0000000..b03a4e0
--- /dev/null
+++ b/fs_mgr/libsnapshot/run_snapshot_tests.sh
@@ -0,0 +1,35 @@
+#!/system/bin/sh
+
+# Detect host or AOSP.
+getprop ro.build.version.sdk > /dev/null 2>&1
+if [ $? -eq 0 ]; then
+ cmd_prefix=""
+ local_root=""
+else
+ cmd_prefix="adb shell"
+ local_root="${ANDROID_PRODUCT_OUT}"
+ set -e
+ set -x
+ adb root
+ adb sync data
+ set +x
+ set +e
+fi
+
+testpath64="/data/nativetest64/vts_libsnapshot_test/vts_libsnapshot_test"
+testpath32="/data/nativetest/vts_libsnapshot_test/vts_libsnapshot_test"
+if [ -f "${local_root}/${testpath64}" ]; then
+ testpath="${testpath64}"
+elif [ -f "${local_root}/${testpath32}" ]; then
+ testpath="${testpath32}"
+else
+ echo "ERROR: vts_libsnapshot_test not found." 1>&2
+ echo "Make sure to build vts_libsnapshot_test or snapshot_tests first." 1>&2
+ exit 1
+fi
+
+# Verbose, error on failure.
+set -x
+set -e
+
+time ${cmd_prefix} ${testpath}
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index f4584d2..e6e17bd 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -668,9 +668,15 @@
bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
CHECK(lock);
- if (!DeleteDeviceIfExists(name)) {
- LOG(ERROR) << "Could not delete snapshot device: " << name;
- return false;
+ if (UpdateUsesUserSnapshots(lock)) {
+ if (!UnmapUserspaceSnapshotDevice(lock, name)) {
+ return false;
+ }
+ } else {
+ if (!DeleteDeviceIfExists(name)) {
+ LOG(ERROR) << "Could not delete snapshot device: " << name;
+ return false;
+ }
}
return true;
}
@@ -2429,10 +2435,8 @@
const std::string& target_partition_name) {
CHECK(lock);
- if (!UpdateUsesUserSnapshots(lock)) {
- if (!UnmapSnapshot(lock, target_partition_name)) {
- return false;
- }
+ if (!UnmapSnapshot(lock, target_partition_name)) {
+ return false;
}
if (!UnmapCowDevices(lock, target_partition_name)) {
@@ -2530,16 +2534,10 @@
CHECK(lock);
if (!EnsureImageManager()) return false;
- if (UpdateUsesCompression(lock)) {
- if (UpdateUsesUserSnapshots(lock)) {
- if (!UnmapUserspaceSnapshotDevice(lock, name)) {
- return false;
- }
- } else {
- auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
- if (!UnmapDmUserDevice(dm_user_name)) {
- return false;
- }
+ if (UpdateUsesCompression(lock) && !UpdateUsesUserSnapshots(lock)) {
+ auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+ if (!UnmapDmUserDevice(dm_user_name)) {
+ return false;
}
}
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 078f16e..9adc655 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -155,8 +155,8 @@
}
std::string MakeXorBlockString() {
- std::string data(100, -1);
- data.resize(kBlockSize, 0);
+ std::string data(kBlockSize, 0);
+ memset(data.data(), 0xff, 100);
return data;
}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index f1d76e7..14f2d45 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -34,6 +34,7 @@
#include <fs_mgr/file_wait.h>
#include <fs_mgr/roots.h>
#include <fs_mgr_dm_linear.h>
+#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
@@ -52,6 +53,8 @@
#include <libsnapshot/mock_device_info.h>
#include <libsnapshot/mock_snapshot.h>
+DEFINE_string(force_config, "", "Force testing mode (dmsnap, vab, vabc) ignoring device config.");
+
namespace android {
namespace snapshot {
@@ -87,6 +90,8 @@
std::string fake_super;
void MountMetadata();
+bool ShouldUseCompression();
+bool ShouldUseUserspaceSnapshots();
class SnapshotTest : public ::testing::Test {
public:
@@ -139,11 +144,7 @@
std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
"test_partition_b"};
for (const auto& snapshot : snapshots) {
- ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
- DeleteBackingImage(image_manager_, snapshot + "-cow-img");
-
- auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
- android::base::RemoveFileIfExists(status_file);
+ CleanupSnapshotArtifacts(snapshot);
}
// Remove stale partitions in fake super.
@@ -151,7 +152,7 @@
"base-device",
"test_partition_b",
"test_partition_b-base",
- "test_partition_b-base",
+ "test_partition_b-cow",
};
for (const auto& partition : partitions) {
DeleteDevice(partition);
@@ -163,6 +164,32 @@
}
}
+ void CleanupSnapshotArtifacts(const std::string& snapshot) {
+ // The device-mapper stack may have been collapsed to dm-linear, so it's
+ // necessary to check what state it's in before attempting a cleanup.
+ // SnapshotManager has no path like this because we'd never remove a
+ // merged snapshot (a live partition).
+ bool is_dm_user = false;
+ DeviceMapper::TargetInfo target;
+ if (sm->IsSnapshotDevice(snapshot, &target)) {
+ is_dm_user = (DeviceMapper::GetTargetType(target.spec) == "user");
+ }
+
+ if (is_dm_user) {
+ ASSERT_TRUE(sm->EnsureSnapuserdConnected());
+ ASSERT_TRUE(AcquireLock());
+
+ auto local_lock = std::move(lock_);
+ ASSERT_TRUE(sm->UnmapUserspaceSnapshotDevice(local_lock.get(), snapshot));
+ }
+
+ ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
+ DeleteBackingImage(image_manager_, snapshot + "-cow-img");
+
+ auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
+ android::base::RemoveFileIfExists(status_file);
+ }
+
bool AcquireLock() {
lock_ = sm->LockExclusive();
return !!lock_;
@@ -428,7 +455,7 @@
ASSERT_TRUE(AcquireLock());
PartitionCowCreator cow_creator;
- cow_creator.compression_enabled = IsCompressionEnabled();
+ cow_creator.compression_enabled = ShouldUseCompression();
if (cow_creator.compression_enabled) {
cow_creator.compression_algorithm = "gz";
} else {
@@ -469,7 +496,7 @@
ASSERT_TRUE(AcquireLock());
PartitionCowCreator cow_creator;
- cow_creator.compression_enabled = IsCompressionEnabled();
+ cow_creator.compression_enabled = ShouldUseCompression();
static const uint64_t kDeviceSize = 1024 * 1024;
SnapshotStatus status;
@@ -527,6 +554,8 @@
std::unique_ptr<ISnapshotWriter> writer;
ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
+ bool userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
+
// Release the lock.
lock_ = nullptr;
@@ -548,7 +577,11 @@
// The device should have been switched to a snapshot-merge target.
DeviceMapper::TargetInfo target;
ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+ if (userspace_snapshots) {
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+ } else {
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+ }
// We should not be able to cancel an update now.
ASSERT_FALSE(sm->CancelUpdate());
@@ -584,11 +617,13 @@
ASSERT_TRUE(AcquireLock());
+ bool userspace_snapshots = init->UpdateUsesUserSnapshots(lock_.get());
+
// Validate that we have a snapshot device.
SnapshotStatus status;
ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
ASSERT_EQ(status.state(), SnapshotState::CREATED);
- if (IsCompressionEnabled()) {
+ if (ShouldUseCompression()) {
ASSERT_EQ(status.compression_algorithm(), "gz");
} else {
ASSERT_EQ(status.compression_algorithm(), "none");
@@ -596,7 +631,11 @@
DeviceMapper::TargetInfo target;
ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+ if (userspace_snapshots) {
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+ } else {
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+ }
}
TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
@@ -858,7 +897,7 @@
opener_ = std::make_unique<TestPartitionOpener>(fake_super);
auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
- dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+ dynamic_partition_metadata->set_vabc_enabled(ShouldUseCompression());
dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
// Create a fake update package metadata.
@@ -897,9 +936,9 @@
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
- // Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
+ // Map source partitions.
std::string path;
- for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
+ for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
ASSERT_TRUE(CreateLogicalPartition(
CreateLogicalPartitionParams{
.block_device = fake_super,
@@ -991,7 +1030,7 @@
}
AssertionResult MapOneUpdateSnapshot(const std::string& name) {
- if (IsCompressionEnabled()) {
+ if (ShouldUseCompression()) {
std::unique_ptr<ISnapshotWriter> writer;
return MapUpdateSnapshot(name, &writer);
} else {
@@ -1001,7 +1040,7 @@
}
AssertionResult WriteSnapshotAndHash(const std::string& name) {
- if (IsCompressionEnabled()) {
+ if (ShouldUseCompression()) {
std::unique_ptr<ISnapshotWriter> writer;
auto res = MapUpdateSnapshot(name, &writer);
if (!res) {
@@ -1169,7 +1208,7 @@
// Initiate the merge and wait for it to be completed.
ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+ ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
{
// We should have started in SECOND_PHASE since nothing shrinks.
ASSERT_TRUE(AcquireLock());
@@ -1196,7 +1235,7 @@
}
TEST_F(SnapshotUpdateTest, DuplicateOps) {
- if (!IsCompressionEnabled()) {
+ if (!ShouldUseCompression()) {
GTEST_SKIP() << "Compression-only test";
}
@@ -1240,7 +1279,7 @@
// Test that shrinking and growing partitions at the same time is handled
// correctly in VABC.
TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
- if (!IsCompressionEnabled()) {
+ if (!ShouldUseCompression()) {
// b/179111359
GTEST_SKIP() << "Skipping Virtual A/B Compression test";
}
@@ -1303,7 +1342,7 @@
// Initiate the merge and wait for it to be completed.
ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+ ASSERT_EQ(init->IsSnapuserdRequired(), ShouldUseUserspaceSnapshots());
{
// Check that the merge phase is FIRST_PHASE until at least one call
// to ProcessUpdateState() occurs.
@@ -1320,11 +1359,21 @@
// Check that we used the correct types after rebooting mid-merge.
DeviceMapper::TargetInfo target;
ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
- ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
- ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+ bool userspace_snapshots = init->UpdateUsesUserSnapshots();
+ if (userspace_snapshots) {
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+ ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+ ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
+ } else {
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+ ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+ ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+ ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+ }
// Complete the merge.
ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
@@ -1802,6 +1851,8 @@
ASSERT_TRUE(new_sm->FinishMergeInRecovery());
+ ASSERT_TRUE(UnmapAll());
+
auto mount = new_sm->EnsureMetadataMounted();
ASSERT_TRUE(mount && mount->HasDevice());
ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
@@ -1894,6 +1945,8 @@
ASSERT_FALSE(test_device->IsSlotUnbootable(1));
ASSERT_FALSE(test_device->IsSlotUnbootable(0));
+ ASSERT_TRUE(UnmapAll());
+
// Now reboot into new slot.
test_device = new TestDeviceInfo(fake_super, "_b");
auto init = NewManagerForFirstStageMount(test_device);
@@ -1922,8 +1975,8 @@
ASSERT_TRUE(AcquireLock());
PartitionCowCreator cow_creator = {
- .compression_enabled = IsCompressionEnabled(),
- .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+ .compression_enabled = ShouldUseCompression(),
+ .compression_algorithm = ShouldUseCompression() ? "gz" : "none",
};
SnapshotStatus status;
status.set_name("sys_a");
@@ -1955,6 +2008,8 @@
ASSERT_FALSE(test_device->IsSlotUnbootable(1));
ASSERT_FALSE(test_device->IsSlotUnbootable(0));
+ ASSERT_TRUE(UnmapAll());
+
// Now reboot into new slot.
test_device = new TestDeviceInfo(fake_super, "_b");
auto init = NewManagerForFirstStageMount(test_device);
@@ -2017,7 +2072,7 @@
// Test for overflow bit after update
TEST_F(SnapshotUpdateTest, Overflow) {
- if (IsCompressionEnabled()) {
+ if (ShouldUseCompression()) {
GTEST_SKIP() << "No overflow bit set for userspace COWs";
}
@@ -2152,7 +2207,7 @@
};
TEST_F(SnapshotUpdateTest, DaemonTransition) {
- if (!IsCompressionEnabled()) {
+ if (!ShouldUseCompression()) {
GTEST_SKIP() << "Skipping Virtual A/B Compression test";
}
@@ -2178,21 +2233,38 @@
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);
+ bool userspace_snapshots = init->UpdateUsesUserSnapshots();
+
+ if (userspace_snapshots) {
+ ASSERT_EQ(access("/dev/dm-user/sys_b-init", F_OK), 0);
+ ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), -1);
+ } else {
+ 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->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
// :TODO: this is a workaround to ensure the handler list stays empty. We
// should make this test more like actual init, and spawn two copies of
// snapuserd, given how many other tests we now have for normal snapuserd.
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+ if (userspace_snapshots) {
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-init"));
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-init"));
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-init"));
- // 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);
+ // The control device should have been renamed.
+ ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-init", 10s));
+ ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), 0);
+ } else {
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
+ ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+
+ // 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) {
@@ -2215,6 +2287,8 @@
TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
AddOperationForPartitions();
+ ASSERT_TRUE(UnmapAll());
+
// Execute the update from B->A.
test_device->set_slot_suffix("_b");
ASSERT_TRUE(sm->BeginUpdate());
@@ -2231,8 +2305,13 @@
},
&path));
- // Hold sys_a open so it can't be unmapped.
- unique_fd fd(open(path.c_str(), O_RDONLY));
+ bool userspace_snapshots = sm->UpdateUsesUserSnapshots();
+
+ unique_fd fd;
+ if (!userspace_snapshots) {
+ // Hold sys_a open so it can't be unmapped.
+ fd.reset(open(path.c_str(), O_RDONLY));
+ }
// Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
// we should simply delete the old snapshots.
@@ -2251,6 +2330,11 @@
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ if (sm->UpdateUsesUserSnapshots()) {
+ GTEST_SKIP() << "Test does not apply to userspace snapshots";
+ }
+
ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
ASSERT_TRUE(UnmapAll());
@@ -2555,21 +2639,53 @@
}
}
+bool ShouldUseUserspaceSnapshots() {
+ if (FLAGS_force_config == "dmsnap") {
+ return false;
+ }
+ if (!FLAGS_force_config.empty()) {
+ return true;
+ }
+ return IsUserspaceSnapshotsEnabled();
+}
+
+bool ShouldUseCompression() {
+ if (FLAGS_force_config == "vab" || FLAGS_force_config == "dmsnap") {
+ return false;
+ }
+ if (FLAGS_force_config == "vabc") {
+ return true;
+ }
+ return IsCompressionEnabled();
+}
+
} // namespace snapshot
} // namespace android
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
+ gflags::ParseCommandLineFlags(&argc, &argv, false);
android::base::SetProperty("ctl.stop", "snapuserd");
- if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
- return testing::AssertionFailure()
- << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
+ std::unordered_set<std::string> configs = {"", "dmsnap", "vab", "vabc"};
+ if (configs.count(FLAGS_force_config) == 0) {
+ std::cerr << "Unexpected force_config argument\n";
+ return 1;
+ }
+
+ if (FLAGS_force_config == "dmsnap") {
+ if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
+ return testing::AssertionFailure()
+ << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
+ }
}
int ret = RUN_ALL_TESTS();
- android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+
+ if (FLAGS_force_config == "dmsnap") {
+ android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+ }
return ret;
}
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 93b0f7c..84bcb94 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -61,12 +61,12 @@
"dm-snapshot-merge/snapuserd_worker.cpp",
"dm-snapshot-merge/snapuserd_readahead.cpp",
"snapuserd_daemon.cpp",
- "snapuserd_buffer.cpp",
- "user-space-merge/snapuserd_core.cpp",
- "user-space-merge/snapuserd_dm_user.cpp",
- "user-space-merge/snapuserd_merge.cpp",
- "user-space-merge/snapuserd_readahead.cpp",
- "user-space-merge/snapuserd_transitions.cpp",
+ "snapuserd_buffer.cpp",
+ "user-space-merge/snapuserd_core.cpp",
+ "user-space-merge/snapuserd_dm_user.cpp",
+ "user-space-merge/snapuserd_merge.cpp",
+ "user-space-merge/snapuserd_readahead.cpp",
+ "user-space-merge/snapuserd_transitions.cpp",
"user-space-merge/snapuserd_server.cpp",
],
@@ -95,11 +95,24 @@
init_rc: [
"snapuserd.rc",
],
+
+ // snapuserd is started during early boot by first-stage init. At that
+ // point, /system is mounted using the "dm-user" device-mapper kernel
+ // module. dm-user routes all I/O to userspace to be handled by
+ // snapuserd, which would lead to deadlock if we had to handle page
+ // faults for its code pages.
static_executable: true,
+
system_shared_libs: [],
ramdisk_available: true,
vendor_ramdisk_available: true,
recovery_available: true,
+
+ // Snapuserd segfaults with ThinLTO
+ // http://b/208565717
+ lto: {
+ never: true,
+ }
}
cc_test {
@@ -111,7 +124,7 @@
"dm-snapshot-merge/cow_snapuserd_test.cpp",
"dm-snapshot-merge/snapuserd.cpp",
"dm-snapshot-merge/snapuserd_worker.cpp",
- "snapuserd_buffer.cpp",
+ "snapuserd_buffer.cpp",
],
cflags: [
"-Wall",
@@ -174,7 +187,9 @@
"libstorage_literals_headers",
"libfiemap_headers",
],
- test_min_api_level: 30,
+ test_options: {
+ min_shipping_api_level: 30,
+ },
auto_gen_config: true,
require_root: false,
}
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index 3bb7a0a..c201b23 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -246,9 +246,15 @@
int num_ops = 0;
int total_blocks_merged = 0;
+ // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
+ // on 32-bit systems
+ std::unique_ptr<uint8_t[]> metadata_buffer =
+ std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+ memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
+
while (true) {
struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
- (char*)metadata_buffer_ + metadata_offset);
+ (char*)metadata_buffer.get() + metadata_offset);
// Done reading metadata
if (bm->new_block == 0 && bm->file_offset == 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 40e7242..9e8ccfb 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -121,9 +121,15 @@
int num_ops = 0;
int total_blocks_merged = 0;
+ // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault
+ // on 32-bit systems
+ std::unique_ptr<uint8_t[]> metadata_buffer =
+ std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
+ memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize());
+
while (true) {
struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
- (char*)metadata_buffer_ + metadata_offset);
+ (char*)metadata_buffer.get() + metadata_offset);
// Done reading metadata
if (bm->new_block == 0 && bm->file_offset == 0) {
diff --git a/fs_mgr/libsnapshot/userspace_snapshot_test.cpp b/fs_mgr/libsnapshot/userspace_snapshot_test.cpp
deleted file mode 100644
index abe67f6..0000000
--- a/fs_mgr/libsnapshot/userspace_snapshot_test.cpp
+++ /dev/null
@@ -1,2519 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <libsnapshot/cow_format.h>
-#include <libsnapshot/snapshot.h>
-
-#include <fcntl.h>
-#include <signal.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <chrono>
-#include <deque>
-#include <future>
-#include <iostream>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#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>
-#include <libdm/dm.h>
-#include <libfiemap/image_manager.h>
-#include <liblp/builder.h>
-#include <storage_literals/storage_literals.h>
-
-#include <android/snapshot/snapshot.pb.h>
-#include <libsnapshot/test_helpers.h>
-#include "partition_cow_creator.h"
-#include "utility.h"
-
-#include <android-base/properties.h>
-
-// Mock classes are not used. Header included to ensure mocked class definition aligns with the
-// class itself.
-#include <libsnapshot/mock_device_info.h>
-#include <libsnapshot/mock_snapshot.h>
-
-namespace android {
-namespace snapshot {
-
-using android::base::unique_fd;
-using android::dm::DeviceMapper;
-using android::dm::DmDeviceState;
-using android::dm::IDeviceMapper;
-using android::fiemap::FiemapStatus;
-using android::fiemap::IImageManager;
-using android::fs_mgr::BlockDeviceInfo;
-using android::fs_mgr::CreateLogicalPartitionParams;
-using android::fs_mgr::DestroyLogicalPartition;
-using android::fs_mgr::EnsurePathMounted;
-using android::fs_mgr::EnsurePathUnmounted;
-using android::fs_mgr::Extent;
-using android::fs_mgr::Fstab;
-using android::fs_mgr::GetPartitionGroupName;
-using android::fs_mgr::GetPartitionName;
-using android::fs_mgr::Interval;
-using android::fs_mgr::MetadataBuilder;
-using android::fs_mgr::SlotSuffixForSlotNumber;
-using chromeos_update_engine::DeltaArchiveManifest;
-using chromeos_update_engine::DynamicPartitionGroup;
-using chromeos_update_engine::PartitionUpdate;
-using namespace ::testing;
-using namespace android::storage_literals;
-using namespace std::chrono_literals;
-using namespace std::string_literals;
-
-// Global states. See test_helpers.h.
-std::unique_ptr<SnapshotManager> sm;
-TestDeviceInfo* test_device = nullptr;
-std::string fake_super;
-
-void MountMetadata();
-
-class SnapshotTest : public ::testing::Test {
- public:
- SnapshotTest() : dm_(DeviceMapper::Instance()) {}
-
- // This is exposed for main.
- void Cleanup() {
- InitializeState();
- CleanupTestArtifacts();
- }
-
- protected:
- void SetUp() override {
- SKIP_IF_NON_VIRTUAL_AB();
-
- SnapshotTestPropertyFetcher::SetUp();
- InitializeState();
- CleanupTestArtifacts();
- FormatFakeSuper();
- MountMetadata();
- ASSERT_TRUE(sm->BeginUpdate());
- }
-
- void TearDown() override {
- RETURN_IF_NON_VIRTUAL_AB();
-
- lock_ = nullptr;
-
- CleanupTestArtifacts();
- SnapshotTestPropertyFetcher::TearDown();
- }
-
- void InitializeState() {
- ASSERT_TRUE(sm->EnsureImageManager());
- image_manager_ = sm->image_manager();
-
- test_device->set_slot_suffix("_a");
-
- sm->set_use_first_stage_snapuserd(false);
- }
-
- void CleanupTestArtifacts() {
- // Normally cancelling inside a merge is not allowed. Since these
- // are tests, we don't care, destroy everything that might exist.
- // Note we hardcode this list because of an annoying quirk: when
- // completing a merge, the snapshot stops existing, so we can't
- // get an accurate list to remove.
- lock_ = nullptr;
-
- std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
- "test_partition_b"};
- for (const auto& snapshot : snapshots) {
- ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
- DeleteBackingImage(image_manager_, snapshot + "-cow-img");
-
- auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
- android::base::RemoveFileIfExists(status_file);
- }
-
- // Remove stale partitions in fake super.
- std::vector<std::string> partitions = {
- "base-device",
- "test_partition_b",
- "test_partition_b-base",
- "test_partition_b-base",
- };
- for (const auto& partition : partitions) {
- DeleteDevice(partition);
- }
-
- if (sm->GetUpdateState() != UpdateState::None) {
- auto state_file = sm->GetStateFilePath();
- unlink(state_file.c_str());
- }
- }
-
- bool AcquireLock() {
- lock_ = sm->LockExclusive();
- return !!lock_;
- }
-
- // This is so main() can instantiate this to invoke Cleanup.
- virtual void TestBody() override {}
-
- void FormatFakeSuper() {
- BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4096);
- std::vector<BlockDeviceInfo> devices = {super_device};
-
- auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
- ASSERT_NE(builder, nullptr);
-
- auto metadata = builder->Export();
- ASSERT_NE(metadata, nullptr);
-
- TestPartitionOpener opener(fake_super);
- ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *metadata.get()));
- }
-
- // If |path| is non-null, the partition will be mapped after creation.
- bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr,
- const std::optional<std::string> group = {}) {
- TestPartitionOpener opener(fake_super);
- auto builder = MetadataBuilder::New(opener, "super", 0);
- if (!builder) return false;
-
- std::string partition_group = std::string(android::fs_mgr::kDefaultGroup);
- if (group) {
- partition_group = *group;
- }
- return CreatePartition(builder.get(), name, size, path, partition_group);
- }
-
- bool CreatePartition(MetadataBuilder* builder, const std::string& name, uint64_t size,
- std::string* path, const std::string& group) {
- auto partition = builder->AddPartition(name, group, 0);
- if (!partition) return false;
- if (!builder->ResizePartition(partition, size)) {
- return false;
- }
-
- // Update the source slot.
- auto metadata = builder->Export();
- if (!metadata) return false;
-
- TestPartitionOpener opener(fake_super);
- if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) {
- return false;
- }
-
- if (!path) return true;
-
- CreateLogicalPartitionParams params = {
- .block_device = fake_super,
- .metadata = metadata.get(),
- .partition_name = name,
- .force_writable = true,
- .timeout_ms = 10s,
- };
- return CreateLogicalPartition(params, path);
- }
-
- AssertionResult MapUpdateSnapshot(const std::string& name,
- std::unique_ptr<ISnapshotWriter>* writer) {
- TestPartitionOpener opener(fake_super);
- CreateLogicalPartitionParams params{
- .block_device = fake_super,
- .metadata_slot = 1,
- .partition_name = name,
- .timeout_ms = 10s,
- .partition_opener = &opener,
- };
-
- auto old_partition = "/dev/block/mapper/" + GetOtherPartitionName(name);
- auto result = sm->OpenSnapshotWriter(params, {old_partition});
- if (!result) {
- return AssertionFailure() << "Cannot open snapshot for writing: " << name;
- }
- if (!result->Initialize()) {
- return AssertionFailure() << "Cannot initialize snapshot for writing: " << name;
- }
-
- if (writer) {
- *writer = std::move(result);
- }
- return AssertionSuccess();
- }
-
- AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path) {
- TestPartitionOpener opener(fake_super);
- CreateLogicalPartitionParams params{
- .block_device = fake_super,
- .metadata_slot = 1,
- .partition_name = name,
- .timeout_ms = 10s,
- .partition_opener = &opener,
- };
-
- auto result = sm->MapUpdateSnapshot(params, path);
- if (!result) {
- return AssertionFailure() << "Cannot open snapshot for writing: " << name;
- }
- return AssertionSuccess();
- }
-
- AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
- AssertionResult res = AssertionSuccess();
- if (!(res = DeleteDevice(snapshot))) return res;
- if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) {
- return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
- }
- if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
- if (!(res = DeleteDevice(snapshot + "-cow"))) return res;
- if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) {
- return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
- }
- if (!(res = DeleteDevice(snapshot + "-base"))) return res;
- if (!(res = DeleteDevice(snapshot + "-src"))) return res;
- return AssertionSuccess();
- }
-
- AssertionResult DeleteDevice(const std::string& device) {
- if (!dm_.DeleteDeviceIfExists(device)) {
- return AssertionFailure() << "Can't delete " << device;
- }
- return AssertionSuccess();
- }
-
- AssertionResult CreateCowImage(const std::string& name) {
- if (!sm->CreateCowImage(lock_.get(), name)) {
- return AssertionFailure() << "Cannot create COW image " << name;
- }
- std::string cow_device;
- auto map_res = MapCowImage(name, 10s, &cow_device);
- if (!map_res) {
- return map_res;
- }
- if (!InitializeKernelCow(cow_device)) {
- return AssertionFailure() << "Cannot zero fill " << cow_device;
- }
- if (!sm->UnmapCowImage(name)) {
- return AssertionFailure() << "Cannot unmap " << name << " after zero filling it";
- }
- return AssertionSuccess();
- }
-
- AssertionResult MapCowImage(const std::string& name,
- const std::chrono::milliseconds& timeout_ms, std::string* path) {
- auto cow_image_path = sm->MapCowImage(name, timeout_ms);
- if (!cow_image_path.has_value()) {
- return AssertionFailure() << "Cannot map cow image " << name;
- }
- *path = *cow_image_path;
- return AssertionSuccess();
- }
-
- // Prepare A/B slot for a partition named "test_partition".
- AssertionResult PrepareOneSnapshot(uint64_t device_size,
- std::unique_ptr<ISnapshotWriter>* writer = nullptr) {
- lock_ = nullptr;
-
- DeltaArchiveManifest manifest;
-
- auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
- dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
- dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
-
- auto group = dynamic_partition_metadata->add_groups();
- group->set_name("group");
- group->set_size(device_size * 2);
- group->add_partition_names("test_partition");
-
- auto pu = manifest.add_partitions();
- pu->set_partition_name("test_partition");
- pu->set_estimate_cow_size(device_size);
- SetSize(pu, device_size);
-
- auto extent = pu->add_operations()->add_dst_extents();
- extent->set_start_block(0);
- if (device_size) {
- extent->set_num_blocks(device_size / manifest.block_size());
- }
-
- TestPartitionOpener opener(fake_super);
- auto builder = MetadataBuilder::New(opener, "super", 0);
- if (!builder) {
- return AssertionFailure() << "Failed to open MetadataBuilder";
- }
- builder->AddGroup("group_a", 16_GiB);
- builder->AddGroup("group_b", 16_GiB);
- if (!CreatePartition(builder.get(), "test_partition_a", device_size, nullptr, "group_a")) {
- return AssertionFailure() << "Failed create test_partition_a";
- }
-
- if (!sm->CreateUpdateSnapshots(manifest)) {
- return AssertionFailure() << "Failed to create update snapshots";
- }
-
- if (writer) {
- auto res = MapUpdateSnapshot("test_partition_b", writer);
- if (!res) {
- return res;
- }
- } else if (!IsCompressionEnabled()) {
- std::string ignore;
- if (!MapUpdateSnapshot("test_partition_b", &ignore)) {
- return AssertionFailure() << "Failed to map test_partition_b";
- }
- }
- if (!AcquireLock()) {
- return AssertionFailure() << "Failed to acquire lock";
- }
- return AssertionSuccess();
- }
-
- // Simulate a reboot into the new slot.
- AssertionResult SimulateReboot() {
- lock_ = nullptr;
- if (!sm->FinishedSnapshotWrites(false)) {
- return AssertionFailure() << "Failed to finish snapshot writes";
- }
- if (!sm->UnmapUpdateSnapshot("test_partition_b")) {
- return AssertionFailure() << "Failed to unmap COW for test_partition_b";
- }
- if (!dm_.DeleteDeviceIfExists("test_partition_b")) {
- return AssertionFailure() << "Failed to delete test_partition_b";
- }
- if (!dm_.DeleteDeviceIfExists("test_partition_b-base")) {
- return AssertionFailure() << "Failed to destroy test_partition_b-base";
- }
- return AssertionSuccess();
- }
-
- std::unique_ptr<SnapshotManager> NewManagerForFirstStageMount(
- const std::string& slot_suffix = "_a") {
- auto info = new TestDeviceInfo(fake_super, slot_suffix);
- return NewManagerForFirstStageMount(info);
- }
-
- std::unique_ptr<SnapshotManager> NewManagerForFirstStageMount(TestDeviceInfo* info) {
- info->set_first_stage_init(true);
- auto init = SnapshotManager::NewForFirstStageMount(info);
- if (!init) {
- return nullptr;
- }
- init->SetUeventRegenCallback([](const std::string& device) -> bool {
- return android::fs_mgr::WaitForFile(device, snapshot_timeout_);
- });
- return init;
- }
-
- static constexpr std::chrono::milliseconds snapshot_timeout_ = 5s;
- DeviceMapper& dm_;
- std::unique_ptr<SnapshotManager::LockedFile> lock_;
- android::fiemap::IImageManager* image_manager_ = nullptr;
- std::string fake_super_;
-};
-
-TEST_F(SnapshotTest, CreateSnapshot) {
- ASSERT_TRUE(AcquireLock());
-
- PartitionCowCreator cow_creator;
- cow_creator.compression_enabled = IsCompressionEnabled();
- if (cow_creator.compression_enabled) {
- cow_creator.compression_algorithm = "gz";
- } else {
- cow_creator.compression_algorithm = "none";
- }
-
- static const uint64_t kDeviceSize = 1024 * 1024;
- SnapshotStatus status;
- status.set_name("test-snapshot");
- status.set_device_size(kDeviceSize);
- status.set_snapshot_size(kDeviceSize);
- status.set_cow_file_size(kDeviceSize);
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
- ASSERT_TRUE(CreateCowImage("test-snapshot"));
-
- std::vector<std::string> snapshots;
- ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
- ASSERT_EQ(snapshots.size(), 1);
- ASSERT_EQ(snapshots[0], "test-snapshot");
-
- // Scope so delete can re-acquire the snapshot file lock.
- {
- SnapshotStatus status;
- ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
- ASSERT_EQ(status.state(), SnapshotState::CREATED);
- ASSERT_EQ(status.device_size(), kDeviceSize);
- ASSERT_EQ(status.snapshot_size(), kDeviceSize);
- ASSERT_EQ(status.compression_enabled(), cow_creator.compression_enabled);
- ASSERT_EQ(status.compression_algorithm(), cow_creator.compression_algorithm);
- }
-
- ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
- ASSERT_TRUE(sm->UnmapCowImage("test-snapshot"));
- ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot"));
-}
-
-TEST_F(SnapshotTest, MapSnapshot) {
- ASSERT_TRUE(AcquireLock());
-
- PartitionCowCreator cow_creator;
- cow_creator.compression_enabled = IsCompressionEnabled();
-
- static const uint64_t kDeviceSize = 1024 * 1024;
- SnapshotStatus status;
- status.set_name("test-snapshot");
- status.set_device_size(kDeviceSize);
- status.set_snapshot_size(kDeviceSize);
- status.set_cow_file_size(kDeviceSize);
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
- ASSERT_TRUE(CreateCowImage("test-snapshot"));
-
- std::string base_device;
- ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
-
- std::string cow_device;
- ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
-
- std::string snap_device;
- ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
- &snap_device));
- ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
-}
-
-TEST_F(SnapshotTest, NoMergeBeforeReboot) {
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Merge should fail, since the slot hasn't changed.
- ASSERT_FALSE(sm->InitiateMerge());
-}
-
-TEST_F(SnapshotTest, CleanFirstStageMount) {
- // If there's no update in progress, there should be no first-stage mount
- // needed.
- auto sm = NewManagerForFirstStageMount();
- ASSERT_NE(sm, nullptr);
- ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount());
-}
-
-TEST_F(SnapshotTest, FirstStageMountAfterRollback) {
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // We didn't change the slot, so we shouldn't need snapshots.
- auto sm = NewManagerForFirstStageMount();
- ASSERT_NE(sm, nullptr);
- ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount());
-
- auto indicator = sm->GetRollbackIndicatorPath();
- ASSERT_EQ(access(indicator.c_str(), R_OK), 0);
-}
-
-TEST_F(SnapshotTest, Merge) {
- ASSERT_TRUE(AcquireLock());
-
- static const uint64_t kDeviceSize = 1024 * 1024;
-
- std::unique_ptr<ISnapshotWriter> writer;
- ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
-
- // Release the lock.
- lock_ = nullptr;
-
- std::string test_string = "This is a test string.";
- test_string.resize(writer->options().block_size);
- ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size()));
- ASSERT_TRUE(writer->Finalize());
- writer = nullptr;
-
- // Done updating.
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- ASSERT_TRUE(sm->UnmapUpdateSnapshot("test_partition_b"));
-
- test_device->set_slot_suffix("_b");
- ASSERT_TRUE(sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- ASSERT_TRUE(sm->InitiateMerge());
-
- // The device should have been switched to a snapshot-merge target.
- DeviceMapper::TargetInfo target;
- ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
-
- // We should not be able to cancel an update now.
- ASSERT_FALSE(sm->CancelUpdate());
-
- ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted);
- ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
-
- // The device should no longer be a snapshot or snapshot-merge.
- ASSERT_FALSE(sm->IsSnapshotDevice("test_partition_b"));
-
- // Test that we can read back the string we wrote to the snapshot. Note
- // that the base device is gone now. |snap_device| contains the correct
- // partition.
- unique_fd fd(open("/dev/block/mapper/test_partition_b", O_RDONLY | O_CLOEXEC));
- ASSERT_GE(fd, 0);
-
- std::string buffer(test_string.size(), '\0');
- ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size()));
- ASSERT_EQ(test_string, buffer);
-}
-
-TEST_F(SnapshotTest, FirstStageMountAndMerge) {
- ASSERT_TRUE(AcquireLock());
-
- static const uint64_t kDeviceSize = 1024 * 1024;
- ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
- ASSERT_TRUE(SimulateReboot());
-
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- ASSERT_TRUE(AcquireLock());
-
- // Validate that we have a snapshot device.
- SnapshotStatus status;
- ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
- ASSERT_EQ(status.state(), SnapshotState::CREATED);
- if (IsCompressionEnabled()) {
- ASSERT_EQ(status.compression_algorithm(), "gz");
- } else {
- ASSERT_EQ(status.compression_algorithm(), "none");
- }
-
- DeviceMapper::TargetInfo target;
- ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
-}
-
-TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
- ASSERT_TRUE(AcquireLock());
-
- static const uint64_t kDeviceSize = 1024 * 1024;
- ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
- ASSERT_TRUE(SimulateReboot());
-
- // Reflash the super partition.
- FormatFakeSuper();
- ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
-
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- ASSERT_TRUE(AcquireLock());
-
- SnapshotStatus status;
- ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
-
- // We should not get a snapshot device now.
- DeviceMapper::TargetInfo target;
- ASSERT_FALSE(init->IsSnapshotDevice("test_partition_b", &target));
-
- // We should see a cancelled update as well.
- lock_ = nullptr;
- ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled);
-}
-
-TEST_F(SnapshotTest, FlashSuperDuringMerge) {
- ASSERT_TRUE(AcquireLock());
-
- static const uint64_t kDeviceSize = 1024 * 1024;
- ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
- ASSERT_TRUE(SimulateReboot());
-
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- ASSERT_TRUE(init->InitiateMerge());
-
- // Now, reflash super. Note that we haven't called ProcessUpdateState, so the
- // status is still Merging.
- ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b"));
- ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img"));
- FormatFakeSuper();
- ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Because the status is Merging, we must call ProcessUpdateState, which should
- // detect a cancelled update.
- ASSERT_EQ(init->ProcessUpdateState(), UpdateState::Cancelled);
- ASSERT_EQ(init->GetUpdateState(), UpdateState::None);
-}
-
-TEST_F(SnapshotTest, UpdateBootControlHal) {
- ASSERT_TRUE(AcquireLock());
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE);
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Initiated));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE);
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::SNAPSHOTTED);
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Merging));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeNeedsReboot));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE);
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeCompleted));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE);
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
-}
-
-TEST_F(SnapshotTest, MergeFailureCode) {
- ASSERT_TRUE(AcquireLock());
-
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed,
- MergeFailureCode::ListSnapshots));
- ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
-
- SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get());
- ASSERT_EQ(status.state(), UpdateState::MergeFailed);
- ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots);
-}
-
-enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT };
-std::ostream& operator<<(std::ostream& os, Request request) {
- switch (request) {
- case Request::LOCK_SHARED:
- return os << "Shared";
- case Request::LOCK_EXCLUSIVE:
- return os << "Exclusive";
- case Request::UNLOCK:
- return os << "Unlock";
- case Request::EXIT:
- return os << "Exit";
- case Request::UNKNOWN:
- [[fallthrough]];
- default:
- return os << "Unknown";
- }
-}
-
-class LockTestConsumer {
- public:
- AssertionResult MakeRequest(Request new_request) {
- {
- std::unique_lock<std::mutex> ulock(mutex_);
- requests_.push_back(new_request);
- }
- cv_.notify_all();
- return AssertionSuccess() << "Request " << new_request << " successful";
- }
-
- template <typename R, typename P>
- AssertionResult WaitFulfill(std::chrono::duration<R, P> timeout) {
- std::unique_lock<std::mutex> ulock(mutex_);
- if (cv_.wait_for(ulock, timeout, [this] { return requests_.empty(); })) {
- return AssertionSuccess() << "All requests_ fulfilled.";
- }
- return AssertionFailure() << "Timeout waiting for fulfilling " << requests_.size()
- << " request(s), first one is "
- << (requests_.empty() ? Request::UNKNOWN : requests_.front());
- }
-
- void StartHandleRequestsInBackground() {
- future_ = std::async(std::launch::async, &LockTestConsumer::HandleRequests, this);
- }
-
- private:
- void HandleRequests() {
- static constexpr auto consumer_timeout = 3s;
-
- auto next_request = Request::UNKNOWN;
- do {
- // Peek next request.
- {
- std::unique_lock<std::mutex> ulock(mutex_);
- if (cv_.wait_for(ulock, consumer_timeout, [this] { return !requests_.empty(); })) {
- next_request = requests_.front();
- } else {
- next_request = Request::EXIT;
- }
- }
-
- // Handle next request.
- switch (next_request) {
- case Request::LOCK_SHARED: {
- lock_ = sm->LockShared();
- } break;
- case Request::LOCK_EXCLUSIVE: {
- lock_ = sm->LockExclusive();
- } break;
- case Request::EXIT:
- [[fallthrough]];
- case Request::UNLOCK: {
- lock_.reset();
- } break;
- case Request::UNKNOWN:
- [[fallthrough]];
- default:
- break;
- }
-
- // Pop next request. This thread is the only thread that
- // pops from the front of the requests_ deque.
- {
- std::unique_lock<std::mutex> ulock(mutex_);
- if (next_request == Request::EXIT) {
- requests_.clear();
- } else {
- requests_.pop_front();
- }
- }
- cv_.notify_all();
- } while (next_request != Request::EXIT);
- }
-
- std::mutex mutex_;
- std::condition_variable cv_;
- std::deque<Request> requests_;
- std::unique_ptr<SnapshotManager::LockedFile> lock_;
- std::future<void> future_;
-};
-
-class LockTest : public ::testing::Test {
- public:
- void SetUp() {
- SKIP_IF_NON_VIRTUAL_AB();
- first_consumer.StartHandleRequestsInBackground();
- second_consumer.StartHandleRequestsInBackground();
- }
-
- void TearDown() {
- RETURN_IF_NON_VIRTUAL_AB();
- EXPECT_TRUE(first_consumer.MakeRequest(Request::EXIT));
- EXPECT_TRUE(second_consumer.MakeRequest(Request::EXIT));
- }
-
- static constexpr auto request_timeout = 500ms;
- LockTestConsumer first_consumer;
- LockTestConsumer second_consumer;
-};
-
-TEST_F(LockTest, SharedShared) {
- ASSERT_TRUE(first_consumer.MakeRequest(Request::LOCK_SHARED));
- ASSERT_TRUE(first_consumer.WaitFulfill(request_timeout));
- ASSERT_TRUE(second_consumer.MakeRequest(Request::LOCK_SHARED));
- ASSERT_TRUE(second_consumer.WaitFulfill(request_timeout));
-}
-
-using LockTestParam = std::pair<Request, Request>;
-class LockTestP : public LockTest, public ::testing::WithParamInterface<LockTestParam> {};
-TEST_P(LockTestP, Test) {
- ASSERT_TRUE(first_consumer.MakeRequest(GetParam().first));
- ASSERT_TRUE(first_consumer.WaitFulfill(request_timeout));
- ASSERT_TRUE(second_consumer.MakeRequest(GetParam().second));
- ASSERT_FALSE(second_consumer.WaitFulfill(request_timeout))
- << "Should not be able to " << GetParam().second << " while separate thread "
- << GetParam().first;
- ASSERT_TRUE(first_consumer.MakeRequest(Request::UNLOCK));
- ASSERT_TRUE(second_consumer.WaitFulfill(request_timeout))
- << "Should be able to hold lock that is released by separate thread";
-}
-INSTANTIATE_TEST_SUITE_P(
- LockTest, LockTestP,
- testing::Values(LockTestParam{Request::LOCK_EXCLUSIVE, Request::LOCK_EXCLUSIVE},
- LockTestParam{Request::LOCK_EXCLUSIVE, Request::LOCK_SHARED},
- LockTestParam{Request::LOCK_SHARED, Request::LOCK_EXCLUSIVE}),
- [](const testing::TestParamInfo<LockTestP::ParamType>& info) {
- std::stringstream ss;
- ss << info.param.first << info.param.second;
- return ss.str();
- });
-
-class SnapshotUpdateTest : public SnapshotTest {
- public:
- void SetUp() override {
- SKIP_IF_NON_VIRTUAL_AB();
-
- SnapshotTest::SetUp();
- Cleanup();
-
- // Cleanup() changes slot suffix, so initialize it again.
- test_device->set_slot_suffix("_a");
-
- opener_ = std::make_unique<TestPartitionOpener>(fake_super);
-
- auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
- dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
- dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
-
- // Create a fake update package metadata.
- // Not using full name "system", "vendor", "product" because these names collide with the
- // mapped partitions on the running device.
- // Each test modifies manifest_ slightly to indicate changes to the partition layout.
- group_ = dynamic_partition_metadata->add_groups();
- group_->set_name("group");
- group_->set_size(kGroupSize);
- group_->add_partition_names("sys");
- group_->add_partition_names("vnd");
- group_->add_partition_names("prd");
- sys_ = manifest_.add_partitions();
- sys_->set_partition_name("sys");
- sys_->set_estimate_cow_size(2_MiB);
- SetSize(sys_, 3_MiB);
- vnd_ = manifest_.add_partitions();
- vnd_->set_partition_name("vnd");
- vnd_->set_estimate_cow_size(2_MiB);
- SetSize(vnd_, 3_MiB);
- prd_ = manifest_.add_partitions();
- prd_->set_partition_name("prd");
- prd_->set_estimate_cow_size(2_MiB);
- SetSize(prd_, 3_MiB);
-
- // Initialize source partition metadata using |manifest_|.
- src_ = MetadataBuilder::New(*opener_, "super", 0);
- ASSERT_NE(src_, nullptr);
- ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a"));
- // Add sys_b which is like system_other.
- ASSERT_TRUE(src_->AddGroup("group_b", kGroupSize));
- auto partition = src_->AddPartition("sys_b", "group_b", 0);
- ASSERT_NE(nullptr, partition);
- ASSERT_TRUE(src_->ResizePartition(partition, 1_MiB));
- auto metadata = src_->Export();
- ASSERT_NE(nullptr, metadata);
- ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
-
- // Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
- std::string path;
- for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
- ASSERT_TRUE(CreateLogicalPartition(
- CreateLogicalPartitionParams{
- .block_device = fake_super,
- .metadata_slot = 0,
- .partition_name = name,
- .timeout_ms = 1s,
- .partition_opener = opener_.get(),
- },
- &path));
- ASSERT_TRUE(WriteRandomData(path));
- auto hash = GetHash(path);
- ASSERT_TRUE(hash.has_value());
- hashes_[name] = *hash;
- }
-
- // OTA client blindly unmaps all partitions that are possibly mapped.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
- }
- }
- void TearDown() override {
- RETURN_IF_NON_VIRTUAL_AB();
-
- Cleanup();
- SnapshotTest::TearDown();
- }
- void Cleanup() {
- if (!image_manager_) {
- InitializeState();
- }
- MountMetadata();
- for (const auto& suffix : {"_a", "_b"}) {
- test_device->set_slot_suffix(suffix);
-
- // Cheat our way out of merge failed states.
- if (sm->ProcessUpdateState() == UpdateState::MergeFailed) {
- ASSERT_TRUE(AcquireLock());
- ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None));
- lock_ = {};
- }
-
- EXPECT_TRUE(sm->CancelUpdate()) << suffix;
- }
- EXPECT_TRUE(UnmapAll());
- }
-
- AssertionResult IsPartitionUnchanged(const std::string& name) {
- std::string path;
- if (!dm_.GetDmDevicePathByName(name, &path)) {
- return AssertionFailure() << "Path of " << name << " cannot be determined";
- }
- auto hash = GetHash(path);
- if (!hash.has_value()) {
- return AssertionFailure() << "Cannot read partition " << name << ": " << path;
- }
- auto it = hashes_.find(name);
- if (it == hashes_.end()) {
- return AssertionFailure() << "No existing hash for " << name << ". Bad test code?";
- }
- if (it->second != *hash) {
- return AssertionFailure() << "Content of " << name << " has changed";
- }
- return AssertionSuccess();
- }
-
- std::optional<uint64_t> GetSnapshotSize(const std::string& name) {
- if (!AcquireLock()) {
- return std::nullopt;
- }
- auto local_lock = std::move(lock_);
-
- SnapshotStatus status;
- if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
- return std::nullopt;
- }
- return status.snapshot_size();
- }
-
- AssertionResult UnmapAll() {
- for (const auto& name : {"sys", "vnd", "prd", "dlkm"}) {
- if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
- return AssertionFailure() << "Cannot unmap " << name << "_a";
- }
- if (!DeleteSnapshotDevice(name + "_b"s)) {
- return AssertionFailure() << "Cannot delete snapshot " << name << "_b";
- }
- }
- return AssertionSuccess();
- }
-
- AssertionResult MapOneUpdateSnapshot(const std::string& name) {
- if (IsCompressionEnabled()) {
- std::unique_ptr<ISnapshotWriter> writer;
- return MapUpdateSnapshot(name, &writer);
- } else {
- std::string path;
- return MapUpdateSnapshot(name, &path);
- }
- }
-
- AssertionResult WriteSnapshotAndHash(const std::string& name) {
- if (IsCompressionEnabled()) {
- std::unique_ptr<ISnapshotWriter> writer;
- auto res = MapUpdateSnapshot(name, &writer);
- if (!res) {
- return res;
- }
- if (!WriteRandomData(writer.get(), &hashes_[name])) {
- return AssertionFailure() << "Unable to write random data to snapshot " << name;
- }
- if (!writer->Finalize()) {
- return AssertionFailure() << "Unable to finalize COW for " << name;
- }
- } else {
- std::string path;
- auto res = MapUpdateSnapshot(name, &path);
- if (!res) {
- return res;
- }
- if (!WriteRandomData(path, std::nullopt, &hashes_[name])) {
- return AssertionFailure() << "Unable to write random data to snapshot " << name;
- }
- }
-
- // Make sure updates to one device are seen by all devices.
- sync();
-
- return AssertionSuccess() << "Written random data to snapshot " << name
- << ", hash: " << hashes_[name];
- }
-
- // Generate a snapshot that moves all the upper blocks down to the start.
- // It doesn't really matter the order, we just want copies that reference
- // blocks that won't exist if the partition shrinks.
- AssertionResult ShiftAllSnapshotBlocks(const std::string& name, uint64_t old_size) {
- std::unique_ptr<ISnapshotWriter> writer;
- if (auto res = MapUpdateSnapshot(name, &writer); !res) {
- return res;
- }
- if (!writer->options().max_blocks || !*writer->options().max_blocks) {
- return AssertionFailure() << "No max blocks set for " << name << " writer";
- }
-
- uint64_t src_block = (old_size / writer->options().block_size) - 1;
- uint64_t dst_block = 0;
- uint64_t max_blocks = *writer->options().max_blocks;
- while (dst_block < max_blocks && dst_block < src_block) {
- if (!writer->AddCopy(dst_block, src_block)) {
- return AssertionFailure() << "Unable to add copy for " << name << " for blocks "
- << src_block << ", " << dst_block;
- }
- dst_block++;
- src_block--;
- }
- if (!writer->Finalize()) {
- return AssertionFailure() << "Unable to finalize writer for " << name;
- }
-
- auto hash = HashSnapshot(writer.get());
- if (hash.empty()) {
- return AssertionFailure() << "Unable to hash snapshot writer for " << name;
- }
- hashes_[name] = hash;
-
- return AssertionSuccess();
- }
-
- AssertionResult MapUpdateSnapshots(const std::vector<std::string>& names = {"sys_b", "vnd_b",
- "prd_b"}) {
- for (const auto& name : names) {
- auto res = MapOneUpdateSnapshot(name);
- if (!res) {
- return res;
- }
- }
- return AssertionSuccess();
- }
-
- // Create fake install operations to grow the COW device size.
- void AddOperation(PartitionUpdate* partition_update, uint64_t size_bytes = 0) {
- auto e = partition_update->add_operations()->add_dst_extents();
- e->set_start_block(0);
- if (size_bytes == 0) {
- size_bytes = GetSize(partition_update);
- }
- e->set_num_blocks(size_bytes / manifest_.block_size());
- }
-
- void AddOperationForPartitions(std::vector<PartitionUpdate*> partitions = {}) {
- if (partitions.empty()) {
- partitions = {sys_, vnd_, prd_};
- }
- for (auto* partition : partitions) {
- AddOperation(partition);
- }
- }
-
- std::unique_ptr<TestPartitionOpener> opener_;
- DeltaArchiveManifest manifest_;
- std::unique_ptr<MetadataBuilder> src_;
- std::map<std::string, std::string> hashes_;
-
- PartitionUpdate* sys_ = nullptr;
- PartitionUpdate* vnd_ = nullptr;
- PartitionUpdate* prd_ = nullptr;
- DynamicPartitionGroup* group_ = nullptr;
-};
-
-// Test full update flow executed by update_engine. Some partitions uses super empty space,
-// some uses images, and some uses both.
-// Also test UnmapUpdateSnapshot unmaps everything.
-// Also test first stage mount and merge after this.
-TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
- // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
- // fit in super, but not |prd|.
- constexpr uint64_t partition_size = 3788_KiB;
- SetSize(sys_, partition_size);
- SetSize(vnd_, partition_size);
- SetSize(prd_, 18_MiB);
-
- // Make sure |prd| does not fit in super at all. On VABC, this means we
- // fake an extra large COW for |vnd| to fill up super.
- vnd_->set_estimate_cow_size(30_MiB);
- prd_->set_estimate_cow_size(30_MiB);
-
- AddOperationForPartitions();
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Test that partitions prioritize using space in super.
- 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"));
- ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name));
- }
-
- // Assert that source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- auto indicator = sm->GetRollbackIndicatorPath();
- ASSERT_NE(access(indicator.c_str(), R_OK), 0);
-
- // Check that the target partitions have the same content.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- // Initiate the merge and wait for it to be completed.
- ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
- {
- // We should have started in SECOND_PHASE since nothing shrinks.
- ASSERT_TRUE(AcquireLock());
- auto local_lock = std::move(lock_);
- auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
- ASSERT_EQ(status.merge_phase(), MergePhase::SECOND_PHASE);
- }
- ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
-
- // Make sure the second phase ran and deleted snapshots.
- {
- ASSERT_TRUE(AcquireLock());
- auto local_lock = std::move(lock_);
- std::vector<std::string> snapshots;
- ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots));
- ASSERT_TRUE(snapshots.empty());
- }
-
- // Check that the target partitions have the same content after the merge.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name))
- << "Content of " << name << " changes after the merge";
- }
-}
-
-TEST_F(SnapshotUpdateTest, DuplicateOps) {
- if (!IsCompressionEnabled()) {
- GTEST_SKIP() << "Compression-only test";
- }
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name));
- }
-
- std::vector<PartitionUpdate*> partitions = {sys_, vnd_, prd_};
- for (auto* partition : partitions) {
- AddOperation(partition);
-
- std::unique_ptr<ISnapshotWriter> writer;
- auto res = MapUpdateSnapshot(partition->partition_name() + "_b", &writer);
- ASSERT_TRUE(res);
- ASSERT_TRUE(writer->AddZeroBlocks(0, 1));
- ASSERT_TRUE(writer->AddZeroBlocks(0, 1));
- ASSERT_TRUE(writer->Finalize());
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Initiate the merge and wait for it to be completed.
- ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
-}
-
-// Test that shrinking and growing partitions at the same time is handled
-// correctly in VABC.
-TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
- if (!IsCompressionEnabled()) {
- // b/179111359
- GTEST_SKIP() << "Skipping Virtual A/B Compression test";
- }
-
- auto old_sys_size = GetSize(sys_);
- auto old_prd_size = GetSize(prd_);
-
- // Grow |sys| but shrink |prd|.
- SetSize(sys_, old_sys_size * 2);
- sys_->set_estimate_cow_size(8_MiB);
- SetSize(prd_, old_prd_size / 2);
- prd_->set_estimate_cow_size(1_MiB);
-
- AddOperationForPartitions();
-
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Check that the old partition sizes were saved correctly.
- {
- ASSERT_TRUE(AcquireLock());
- auto local_lock = std::move(lock_);
-
- SnapshotStatus status;
- ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "prd_b", &status));
- ASSERT_EQ(status.old_partition_size(), 3145728);
- ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "sys_b", &status));
- ASSERT_EQ(status.old_partition_size(), 3145728);
- }
-
- ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
- ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
- ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
-
- sync();
-
- // Assert that source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- auto indicator = sm->GetRollbackIndicatorPath();
- ASSERT_NE(access(indicator.c_str(), R_OK), 0);
-
- // Check that the target partitions have the same content.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- // Initiate the merge and wait for it to be completed.
- ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
- {
- // Check that the merge phase is FIRST_PHASE until at least one call
- // to ProcessUpdateState() occurs.
- ASSERT_TRUE(AcquireLock());
- auto local_lock = std::move(lock_);
- auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
- ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE);
- }
-
- // Simulate shutting down the device and creating partitions again.
- ASSERT_TRUE(UnmapAll());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Check that we used the correct types after rebooting mid-merge.
- DeviceMapper::TargetInfo target;
- ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
- ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
- ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
- ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user");
-
- // Complete the merge.
- ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
-
- // Make sure the second phase ran and deleted snapshots.
- {
- ASSERT_TRUE(AcquireLock());
- auto local_lock = std::move(lock_);
- std::vector<std::string> snapshots;
- ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots));
- ASSERT_TRUE(snapshots.empty());
- }
-
- // Check that the target partitions have the same content after the merge.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name))
- << "Content of " << name << " changes after the merge";
- }
-}
-
-// Test that if new system partitions uses empty space in super, that region is not snapshotted.
-TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) {
- GTEST_SKIP() << "b/141889746";
- SetSize(sys_, 4_MiB);
- // vnd_b and prd_b are unchanged.
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0));
-}
-
-// Test that if new system partitions uses space of old vendor partition, that region is
-// snapshotted.
-TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) {
- SetSize(sys_, 4_MiB); // grows
- SetSize(vnd_, 2_MiB); // shrinks
- // prd_b is unchanged
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0));
-}
-
-// Test that even if there seem to be empty space in target metadata, COW partition won't take
-// it because they are used by old partitions.
-TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) {
- SetSize(sys_, 2_MiB); // shrinks
- // vnd_b and prd_b are unchanged.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- auto tgt = MetadataBuilder::New(*opener_, "super", 1);
- ASSERT_NE(nullptr, tgt);
- auto metadata = tgt->Export();
- ASSERT_NE(nullptr, metadata);
- std::vector<std::string> written;
- // Write random data to all COW partitions in super
- for (auto p : metadata->partitions) {
- if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) {
- continue;
- }
- std::string path;
- ASSERT_TRUE(CreateLogicalPartition(
- CreateLogicalPartitionParams{
- .block_device = fake_super,
- .metadata = metadata.get(),
- .partition = &p,
- .timeout_ms = 1s,
- .partition_opener = opener_.get(),
- },
- &path));
- ASSERT_TRUE(WriteRandomData(path));
- written.push_back(GetPartitionName(p));
- }
- ASSERT_FALSE(written.empty())
- << "No COW partitions are created even if there are empty space in super partition";
-
- // Make sure source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-}
-
-// Test that it crashes after creating snapshot status file but before creating COW image, then
-// calling CreateUpdateSnapshots again works.
-TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) {
- // Write some trash snapshot files to simulate leftovers from previous runs.
- {
- ASSERT_TRUE(AcquireLock());
- auto local_lock = std::move(lock_);
- SnapshotStatus status;
- status.set_name("sys_b");
- ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), status));
- ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
- IImageManager::CREATE_IMAGE_DEFAULT));
- }
-
- // Redo the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
-
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Check that target partitions can be mapped.
- EXPECT_TRUE(MapUpdateSnapshots());
-}
-
-// Test that the old partitions are not modified.
-TEST_F(SnapshotUpdateTest, TestRollback) {
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
-
- AddOperationForPartitions();
-
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name));
- }
-
- // Assert that source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Check that the target partitions have the same content.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- // Simulate shutting down the device again.
- ASSERT_TRUE(UnmapAll());
- init = NewManagerForFirstStageMount("_a");
- ASSERT_NE(init, nullptr);
- ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Assert that the source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-}
-
-// Test that if an update is applied but not booted into, it can be canceled.
-TEST_F(SnapshotUpdateTest, CancelAfterApply) {
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
- ASSERT_TRUE(sm->CancelUpdate());
-}
-
-static std::vector<Interval> ToIntervals(const std::vector<std::unique_ptr<Extent>>& extents) {
- std::vector<Interval> ret;
- std::transform(extents.begin(), extents.end(), std::back_inserter(ret),
- [](const auto& extent) { return extent->AsLinearExtent()->AsInterval(); });
- return ret;
-}
-
-// Test that at the second update, old COW partition spaces are reclaimed.
-TEST_F(SnapshotUpdateTest, ReclaimCow) {
- // Make sure VABC cows are small enough that they fit in fake_super.
- sys_->set_estimate_cow_size(64_KiB);
- vnd_->set_estimate_cow_size(64_KiB);
- prd_->set_estimate_cow_size(64_KiB);
-
- // Execute the first update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- init = nullptr;
-
- // Initiate the merge and wait for it to be completed.
- auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
- ASSERT_TRUE(new_sm->InitiateMerge());
- ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState());
-
- // Execute the second update.
- ASSERT_TRUE(new_sm->BeginUpdate());
- ASSERT_TRUE(new_sm->CreateUpdateSnapshots(manifest_));
-
- // Check that the old COW space is reclaimed and does not occupy space of mapped partitions.
- auto src = MetadataBuilder::New(*opener_, "super", 1);
- ASSERT_NE(src, nullptr);
- auto tgt = MetadataBuilder::New(*opener_, "super", 0);
- ASSERT_NE(tgt, nullptr);
- for (const auto& cow_part_name : {"sys_a-cow", "vnd_a-cow", "prd_a-cow"}) {
- auto* cow_part = tgt->FindPartition(cow_part_name);
- ASSERT_NE(nullptr, cow_part) << cow_part_name << " does not exist in target metadata";
- auto cow_intervals = ToIntervals(cow_part->extents());
- for (const auto& old_part_name : {"sys_b", "vnd_b", "prd_b"}) {
- auto* old_part = src->FindPartition(old_part_name);
- ASSERT_NE(nullptr, old_part) << old_part_name << " does not exist in source metadata";
- auto old_intervals = ToIntervals(old_part->extents());
-
- auto intersect = Interval::Intersect(cow_intervals, old_intervals);
- ASSERT_TRUE(intersect.empty()) << "COW uses space of source partitions";
- }
- }
-}
-
-TEST_F(SnapshotUpdateTest, RetrofitAfterRegularAb) {
- constexpr auto kRetrofitGroupSize = kGroupSize / 2;
-
- // Initialize device-mapper / disk
- ASSERT_TRUE(UnmapAll());
- FormatFakeSuper();
-
- // Setup source partition metadata to have both _a and _b partitions.
- src_ = MetadataBuilder::New(*opener_, "super", 0);
- ASSERT_NE(nullptr, src_);
- for (const auto& suffix : {"_a"s, "_b"s}) {
- ASSERT_TRUE(src_->AddGroup(group_->name() + suffix, kRetrofitGroupSize));
- for (const auto& name : {"sys"s, "vnd"s, "prd"s}) {
- auto partition = src_->AddPartition(name + suffix, group_->name() + suffix, 0);
- ASSERT_NE(nullptr, partition);
- ASSERT_TRUE(src_->ResizePartition(partition, 2_MiB));
- }
- }
- auto metadata = src_->Export();
- ASSERT_NE(nullptr, metadata);
- ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
-
- // Flash source partitions
- std::string path;
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(CreateLogicalPartition(
- CreateLogicalPartitionParams{
- .block_device = fake_super,
- .metadata_slot = 0,
- .partition_name = name,
- .timeout_ms = 1s,
- .partition_opener = opener_.get(),
- },
- &path));
- ASSERT_TRUE(WriteRandomData(path));
- auto hash = GetHash(path);
- ASSERT_TRUE(hash.has_value());
- hashes_[name] = *hash;
- }
-
- // Setup manifest.
- group_->set_size(kRetrofitGroupSize);
- for (auto* partition : {sys_, vnd_, prd_}) {
- SetSize(partition, 2_MiB);
- }
- AddOperationForPartitions();
-
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Test that COW image should not be created for retrofit devices; super
- // should be big enough.
- ASSERT_FALSE(image_manager_->BackingImageExists("sys_b-cow-img"));
- ASSERT_FALSE(image_manager_->BackingImageExists("vnd_b-cow-img"));
- ASSERT_FALSE(image_manager_->BackingImageExists("prd_b-cow-img"));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name));
- }
-
- // Assert that source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-}
-
-TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) {
- // Make source partitions as big as possible to force COW image to be created.
- SetSize(sys_, 10_MiB);
- SetSize(vnd_, 10_MiB);
- SetSize(prd_, 10_MiB);
- sys_->set_estimate_cow_size(12_MiB);
- vnd_->set_estimate_cow_size(12_MiB);
- prd_->set_estimate_cow_size(12_MiB);
-
- src_ = MetadataBuilder::New(*opener_, "super", 0);
- ASSERT_NE(src_, nullptr);
- src_->RemoveGroupAndPartitions(group_->name() + "_a");
- src_->RemoveGroupAndPartitions(group_->name() + "_b");
- ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a"));
- auto metadata = src_->Export();
- ASSERT_NE(nullptr, metadata);
- ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0));
-
- // Add operations for sys. The whole device is written.
- AddOperation(sys_);
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- // Normally we should use NewManagerForFirstStageMount, but if so,
- // "gsid.mapped_image.sys_b-cow-img" won't be set.
- auto init = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Keep an open handle to the cow device. This should cause the merge to
- // be incomplete.
- auto cow_path = android::base::GetProperty("gsid.mapped_image.sys_b-cow-img", "");
- unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC));
- ASSERT_GE(fd, 0);
-
- // COW cannot be removed due to open fd, so expect a soft failure.
- ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(UpdateState::MergeNeedsReboot, init->ProcessUpdateState());
-
- // Simulate shutting down the device.
- fd.reset();
- ASSERT_TRUE(UnmapAll());
-
- // init does first stage mount again.
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // sys_b should be mapped as a dm-linear device directly.
- ASSERT_FALSE(sm->IsSnapshotDevice("sys_b", nullptr));
-
- // Merge should be able to complete now.
- ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
-}
-
-class MetadataMountedTest : public ::testing::Test {
- public:
- // This is so main() can instantiate this to invoke Cleanup.
- virtual void TestBody() override {}
- void SetUp() override {
- SKIP_IF_NON_VIRTUAL_AB();
- metadata_dir_ = test_device->GetMetadataDir();
- ASSERT_TRUE(ReadDefaultFstab(&fstab_));
- }
- void TearDown() override {
- RETURN_IF_NON_VIRTUAL_AB();
- SetUp();
- // Remount /metadata
- test_device->set_recovery(false);
- EXPECT_TRUE(android::fs_mgr::EnsurePathMounted(&fstab_, metadata_dir_));
- }
- AssertionResult IsMetadataMounted() {
- Fstab mounted_fstab;
- if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) {
- ADD_FAILURE() << "Failed to scan mounted volumes";
- return AssertionFailure() << "Failed to scan mounted volumes";
- }
-
- auto entry = GetEntryForPath(&fstab_, metadata_dir_);
- if (entry == nullptr) {
- return AssertionFailure() << "No mount point found in fstab for path " << metadata_dir_;
- }
-
- auto mv = GetEntryForMountPoint(&mounted_fstab, entry->mount_point);
- if (mv == nullptr) {
- return AssertionFailure() << metadata_dir_ << " is not mounted";
- }
- return AssertionSuccess() << metadata_dir_ << " is mounted";
- }
- std::string metadata_dir_;
- Fstab fstab_;
-};
-
-void MountMetadata() {
- MetadataMountedTest().TearDown();
-}
-
-TEST_F(MetadataMountedTest, Android) {
- auto device = sm->EnsureMetadataMounted();
- EXPECT_NE(nullptr, device);
- device.reset();
-
- EXPECT_TRUE(IsMetadataMounted());
- EXPECT_TRUE(sm->CancelUpdate()) << "Metadata dir should never be unmounted in Android mode";
-}
-
-TEST_F(MetadataMountedTest, Recovery) {
- test_device->set_recovery(true);
- metadata_dir_ = test_device->GetMetadataDir();
-
- EXPECT_TRUE(android::fs_mgr::EnsurePathUnmounted(&fstab_, metadata_dir_));
- EXPECT_FALSE(IsMetadataMounted());
-
- auto device = sm->EnsureMetadataMounted();
- EXPECT_NE(nullptr, device);
- EXPECT_TRUE(IsMetadataMounted());
-
- device.reset();
- EXPECT_FALSE(IsMetadataMounted());
-}
-
-// Test that during a merge, we can wipe data in recovery.
-TEST_F(SnapshotUpdateTest, MergeInRecovery) {
- // Execute the first update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- init = nullptr;
-
- // Initiate the merge and then immediately stop it to simulate a reboot.
- auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
- ASSERT_TRUE(new_sm->InitiateMerge());
- ASSERT_TRUE(UnmapAll());
-
- // Simulate a reboot into recovery.
- auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
- test_device->set_recovery(true);
- new_sm = NewManagerForFirstStageMount(test_device.release());
-
- ASSERT_TRUE(new_sm->HandleImminentDataWipe());
- ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None);
-}
-
-// Test that a merge does not clear the snapshot state in fastboot.
-TEST_F(SnapshotUpdateTest, MergeInFastboot) {
- // Execute the first update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- init = nullptr;
-
- // Initiate the merge and then immediately stop it to simulate a reboot.
- auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
- ASSERT_TRUE(new_sm->InitiateMerge());
- ASSERT_TRUE(UnmapAll());
-
- // Simulate a reboot into recovery.
- auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
- test_device->set_recovery(true);
- new_sm = NewManagerForFirstStageMount(test_device.release());
-
- ASSERT_TRUE(new_sm->FinishMergeInRecovery());
-
- ASSERT_TRUE(UnmapAll());
-
- auto mount = new_sm->EnsureMetadataMounted();
- ASSERT_TRUE(mount && mount->HasDevice());
- ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
-
- // Finish the merge in a normal boot.
- test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
- init = NewManagerForFirstStageMount(test_device.release());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- init = nullptr;
-
- test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
- new_sm = NewManagerForFirstStageMount(test_device.release());
- ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
- ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None);
-}
-
-// Test that after an OTA, before a merge, we can wipe data in recovery.
-TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) {
- // Execute the first update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // Simulate a reboot into recovery.
- auto test_device = new TestDeviceInfo(fake_super, "_b");
- test_device->set_recovery(true);
- auto new_sm = NewManagerForFirstStageMount(test_device);
-
- ASSERT_TRUE(new_sm->HandleImminentDataWipe());
- // Manually mount metadata so that we can call GetUpdateState() below.
- MountMetadata();
- EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
- EXPECT_TRUE(test_device->IsSlotUnbootable(1));
- EXPECT_FALSE(test_device->IsSlotUnbootable(0));
-}
-
-// Test that after an OTA and a bootloader rollback with no merge, we can wipe
-// data in recovery.
-TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) {
- // Execute the first update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // Simulate a rollback, with reboot into recovery.
- auto test_device = new TestDeviceInfo(fake_super, "_a");
- test_device->set_recovery(true);
- auto new_sm = NewManagerForFirstStageMount(test_device);
-
- ASSERT_TRUE(new_sm->HandleImminentDataWipe());
- EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
- EXPECT_FALSE(test_device->IsSlotUnbootable(0));
- EXPECT_FALSE(test_device->IsSlotUnbootable(1));
-}
-
-// Test update package that requests data wipe.
-TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) {
- AddOperationForPartitions();
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // Simulate a reboot into recovery.
- auto test_device = new TestDeviceInfo(fake_super, "_b");
- test_device->set_recovery(true);
- auto new_sm = NewManagerForFirstStageMount(test_device);
-
- ASSERT_TRUE(new_sm->HandleImminentDataWipe());
- // Manually mount metadata so that we can call GetUpdateState() below.
- MountMetadata();
- EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
- ASSERT_FALSE(test_device->IsSlotUnbootable(1));
- ASSERT_FALSE(test_device->IsSlotUnbootable(0));
-
- ASSERT_TRUE(UnmapAll());
-
- // Now reboot into new slot.
- test_device = new TestDeviceInfo(fake_super, "_b");
- auto init = NewManagerForFirstStageMount(test_device);
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- // Verify that we are on the downgraded build.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name)) << name;
- }
-}
-
-// Test update package that requests data wipe.
-TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
- AddOperationForPartitions();
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
- }
-
- // Create a stale snapshot that should not exist.
- {
- ASSERT_TRUE(AcquireLock());
-
- PartitionCowCreator cow_creator = {
- .compression_enabled = IsCompressionEnabled(),
- .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
- };
- SnapshotStatus status;
- status.set_name("sys_a");
- status.set_device_size(1_MiB);
- status.set_snapshot_size(2_MiB);
- status.set_cow_partition_size(2_MiB);
-
- ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
- lock_ = nullptr;
-
- ASSERT_TRUE(sm->EnsureImageManager());
- ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0));
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // Simulate a reboot into recovery.
- auto test_device = new TestDeviceInfo(fake_super, "_b");
- test_device->set_recovery(true);
- auto new_sm = NewManagerForFirstStageMount(test_device);
-
- ASSERT_TRUE(new_sm->HandleImminentDataWipe());
- // Manually mount metadata so that we can call GetUpdateState() below.
- MountMetadata();
- EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
- ASSERT_FALSE(test_device->IsSlotUnbootable(1));
- ASSERT_FALSE(test_device->IsSlotUnbootable(0));
-
- ASSERT_TRUE(UnmapAll());
-
- // Now reboot into new slot.
- test_device = new TestDeviceInfo(fake_super, "_b");
- auto init = NewManagerForFirstStageMount(test_device);
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
- // Verify that we are on the downgraded build.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name)) << name;
- }
-}
-
-TEST_F(SnapshotUpdateTest, Hashtree) {
- constexpr auto partition_size = 4_MiB;
- constexpr auto data_size = 3_MiB;
- constexpr auto hashtree_size = 512_KiB;
- constexpr auto fec_size = partition_size - data_size - hashtree_size;
-
- const auto block_size = manifest_.block_size();
- SetSize(sys_, partition_size);
- AddOperation(sys_, data_size);
-
- sys_->set_estimate_cow_size(partition_size + data_size);
-
- // Set hastree extents.
- sys_->mutable_hash_tree_data_extent()->set_start_block(0);
- sys_->mutable_hash_tree_data_extent()->set_num_blocks(data_size / block_size);
-
- sys_->mutable_hash_tree_extent()->set_start_block(data_size / block_size);
- sys_->mutable_hash_tree_extent()->set_num_blocks(hashtree_size / block_size);
-
- // Set FEC extents.
- sys_->mutable_fec_data_extent()->set_start_block(0);
- sys_->mutable_fec_data_extent()->set_num_blocks((data_size + hashtree_size) / block_size);
-
- sys_->mutable_fec_extent()->set_start_block((data_size + hashtree_size) / block_size);
- sys_->mutable_fec_extent()->set_num_blocks(fec_size / block_size);
-
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Map and write some data to target partition.
- ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
- ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
-
- // Finish update.
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_b");
- ASSERT_NE(init, nullptr);
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Check that the target partition have the same content. Hashtree and FEC extents
- // should be accounted for.
- ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
-}
-
-// Test for overflow bit after update
-TEST_F(SnapshotUpdateTest, Overflow) {
- if (IsCompressionEnabled()) {
- GTEST_SKIP() << "No overflow bit set for userspace COWs";
- }
-
- const auto actual_write_size = GetSize(sys_);
- const auto declared_write_size = actual_write_size - 1_MiB;
-
- AddOperation(sys_, declared_write_size);
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Map and write some data to target partitions.
- ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
- ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
-
- std::vector<android::dm::DeviceMapper::TargetInfo> table;
- ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table));
- ASSERT_EQ(1u, table.size());
- EXPECT_TRUE(table[0].IsOverflowSnapshot());
-
- ASSERT_FALSE(sm->FinishedSnapshotWrites(false))
- << "FinishedSnapshotWrites should detect overflow of CoW device.";
-}
-
-TEST_F(SnapshotUpdateTest, LowSpace) {
- static constexpr auto kMaxFree = 10_MiB;
- auto userdata = std::make_unique<LowSpaceUserdata>();
- ASSERT_TRUE(userdata->Init(kMaxFree));
-
- // Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After
- // using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space.
- constexpr uint64_t partition_size = 10_MiB;
- SetSize(sys_, partition_size);
- SetSize(vnd_, partition_size);
- SetSize(prd_, partition_size);
- sys_->set_estimate_cow_size(partition_size);
- vnd_->set_estimate_cow_size(partition_size);
- prd_->set_estimate_cow_size(partition_size);
-
- AddOperationForPartitions();
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- auto res = sm->CreateUpdateSnapshots(manifest_);
- ASSERT_FALSE(res);
- ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());
- ASSERT_GE(res.required_size(), 14_MiB);
- ASSERT_LT(res.required_size(), 40_MiB);
-}
-
-TEST_F(SnapshotUpdateTest, AddPartition) {
- group_->add_partition_names("dlkm");
-
- auto dlkm = manifest_.add_partitions();
- dlkm->set_partition_name("dlkm");
- dlkm->set_estimate_cow_size(2_MiB);
- SetSize(dlkm, 3_MiB);
-
- // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
- // fit in super, but not |prd|.
- constexpr uint64_t partition_size = 3788_KiB;
- SetSize(sys_, partition_size);
- SetSize(vnd_, partition_size);
- SetSize(prd_, partition_size);
- SetSize(dlkm, partition_size);
-
- AddOperationForPartitions({sys_, vnd_, prd_, dlkm});
-
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- // Write some data to target partitions.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
- ASSERT_TRUE(WriteSnapshotAndHash(name));
- }
-
- // Assert that source partitions aren't affected.
- for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- // After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount("_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_));
-
- // Check that the target partitions have the same content.
- std::vector<std::string> partitions = {"sys_b", "vnd_b", "prd_b", "dlkm_b"};
- for (const auto& name : partitions) {
- ASSERT_TRUE(IsPartitionUnchanged(name));
- }
-
- ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
- for (const auto& name : partitions) {
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
- }
-
- // Initiate the merge and wait for it to be completed.
- ASSERT_TRUE(init->InitiateMerge());
- ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
-
- // Check that the target partitions have the same content after the merge.
- for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name))
- << "Content of " << name << " changes after the merge";
- }
-}
-
-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";
- }
-
- // 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 = NewManagerForFirstStageMount("_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-init", F_OK), 0);
- ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), -1);
-
- ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
-
- // :TODO: this is a workaround to ensure the handler list stays empty. We
- // should make this test more like actual init, and spawn two copies of
- // snapuserd, given how many other tests we now have for normal snapuserd.
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-init"));
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-init"));
- ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-init"));
-
- // The control device should have been renamed.
- ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-init", 10s));
- ASSERT_EQ(access("/dev/dm-user/sys_b", 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"));
-
- ASSERT_TRUE(sm->UnmapAllSnapshots());
-}
-
-TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
- AddOperationForPartitions();
-
- // Execute the update from B->A.
- test_device->set_slot_suffix("_b");
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-
- ASSERT_TRUE(UnmapAll());
- std::string path;
- ASSERT_TRUE(CreateLogicalPartition(
- CreateLogicalPartitionParams{
- .block_device = fake_super,
- .metadata_slot = 0,
- .partition_name = "sys_a",
- .timeout_ms = 1s,
- .partition_opener = opener_.get(),
- },
- &path));
-
- // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
- // we should simply delete the old snapshots.
- test_device->set_slot_suffix("_a");
- ASSERT_TRUE(sm->BeginUpdate());
-}
-
-class FlashAfterUpdateTest : public SnapshotUpdateTest,
- public WithParamInterface<std::tuple<uint32_t, bool>> {
- public:
- AssertionResult InitiateMerge(const std::string& slot_suffix) {
- auto sm = SnapshotManager::New(new TestDeviceInfo(fake_super, slot_suffix));
- if (!sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)) {
- return AssertionFailure() << "Cannot CreateLogicalAndSnapshotPartitions";
- }
- if (!sm->InitiateMerge()) {
- return AssertionFailure() << "Cannot initiate merge";
- }
- return AssertionSuccess();
- }
-};
-
-TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) {
- // Execute the update.
- ASSERT_TRUE(sm->BeginUpdate());
- ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
- ASSERT_TRUE(MapUpdateSnapshots());
- ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
-
- // Simulate shutting down the device.
- ASSERT_TRUE(UnmapAll());
-
- bool after_merge = std::get<1>(GetParam());
- if (after_merge) {
- ASSERT_TRUE(InitiateMerge("_b"));
- // Simulate shutting down the device after merge has initiated.
- ASSERT_TRUE(UnmapAll());
- }
-
- auto flashed_slot = std::get<0>(GetParam());
- auto flashed_slot_suffix = SlotSuffixForSlotNumber(flashed_slot);
-
- // Simulate flashing |flashed_slot|. This clears the UPDATED flag.
- auto flashed_builder = MetadataBuilder::New(*opener_, "super", flashed_slot);
- ASSERT_NE(flashed_builder, nullptr);
- flashed_builder->RemoveGroupAndPartitions(group_->name() + flashed_slot_suffix);
- flashed_builder->RemoveGroupAndPartitions(kCowGroupName);
- ASSERT_TRUE(FillFakeMetadata(flashed_builder.get(), manifest_, flashed_slot_suffix));
-
- // Deliberately remove a partition from this build so that
- // InitiateMerge do not switch state to "merging". This is possible in
- // practice because the list of dynamic partitions may change.
- ASSERT_NE(nullptr, flashed_builder->FindPartition("prd" + flashed_slot_suffix));
- flashed_builder->RemovePartition("prd" + flashed_slot_suffix);
-
- // Note that fastbootd always updates the partition table of both slots.
- auto flashed_metadata = flashed_builder->Export();
- ASSERT_NE(nullptr, flashed_metadata);
- ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, 0));
- ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, 1));
-
- std::string path;
- for (const auto& name : {"sys", "vnd"}) {
- ASSERT_TRUE(CreateLogicalPartition(
- CreateLogicalPartitionParams{
- .block_device = fake_super,
- .metadata_slot = flashed_slot,
- .partition_name = name + flashed_slot_suffix,
- .timeout_ms = 1s,
- .partition_opener = opener_.get(),
- },
- &path));
- ASSERT_TRUE(WriteRandomData(path));
- auto hash = GetHash(path);
- ASSERT_TRUE(hash.has_value());
- hashes_[name + flashed_slot_suffix] = *hash;
- }
-
- // Simulate shutting down the device after flash.
- ASSERT_TRUE(UnmapAll());
-
- // Simulate reboot. After reboot, init does first stage mount.
- auto init = NewManagerForFirstStageMount(flashed_slot_suffix);
- ASSERT_NE(init, nullptr);
-
- if (flashed_slot && after_merge) {
- ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
- }
- ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
-
- // Check that the target partitions have the same content.
- for (const auto& name : {"sys", "vnd"}) {
- ASSERT_TRUE(IsPartitionUnchanged(name + flashed_slot_suffix));
- }
-
- // There should be no snapshot to merge.
- auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, flashed_slot_suffix));
- if (flashed_slot == 0 && after_merge) {
- ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState());
- } else {
- // update_engine calls ProcessUpdateState first -- should see Cancelled.
- ASSERT_EQ(UpdateState::Cancelled, new_sm->ProcessUpdateState());
- }
-
- // Next OTA calls CancelUpdate no matter what.
- ASSERT_TRUE(new_sm->CancelUpdate());
-}
-
-INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), Bool()),
- [](const TestParamInfo<FlashAfterUpdateTest::ParamType>& info) {
- return "Flash"s + (std::get<0>(info.param) ? "New"s : "Old"s) +
- "Slot"s + (std::get<1>(info.param) ? "After"s : "Before"s) +
- "Merge"s;
- });
-
-// Test behavior of ImageManager::Create on low space scenario. These tests assumes image manager
-// uses /data as backup device.
-class ImageManagerTest : public SnapshotTest, public WithParamInterface<uint64_t> {
- protected:
- void SetUp() override {
- SKIP_IF_NON_VIRTUAL_AB();
- SnapshotTest::SetUp();
- userdata_ = std::make_unique<LowSpaceUserdata>();
- ASSERT_TRUE(userdata_->Init(GetParam()));
- }
- void TearDown() override {
- RETURN_IF_NON_VIRTUAL_AB();
- return; // BUG(149738928)
-
- EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
- image_manager_->DeleteBackingImage(kImageName));
- }
- static constexpr const char* kImageName = "my_image";
- std::unique_ptr<LowSpaceUserdata> userdata_;
-};
-
-TEST_P(ImageManagerTest, CreateImageEnoughAvailSpace) {
- if (userdata_->available_space() == 0) {
- GTEST_SKIP() << "/data is full (" << userdata_->available_space()
- << " bytes available), skipping";
- }
- ASSERT_TRUE(image_manager_->CreateBackingImage(kImageName, userdata_->available_space(),
- IImageManager::CREATE_IMAGE_DEFAULT))
- << "Should be able to create image with size = " << userdata_->available_space()
- << " bytes";
- ASSERT_TRUE(image_manager_->DeleteBackingImage(kImageName))
- << "Should be able to delete created image";
-}
-
-TEST_P(ImageManagerTest, CreateImageNoSpace) {
- uint64_t to_allocate = userdata_->free_space() + userdata_->bsize();
- auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
- IImageManager::CREATE_IMAGE_DEFAULT);
- ASSERT_FALSE(res) << "Should not be able to create image with size = " << to_allocate
- << " bytes because only " << userdata_->free_space() << " bytes are free";
- ASSERT_EQ(FiemapStatus::ErrorCode::NO_SPACE, res.error_code()) << res.string();
-}
-
-std::vector<uint64_t> ImageManagerTestParams() {
- std::vector<uint64_t> ret;
- for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
- ret.push_back(size);
- }
- return ret;
-}
-
-INSTANTIATE_TEST_SUITE_P(ImageManagerTest, ImageManagerTest, ValuesIn(ImageManagerTestParams()));
-
-bool Mkdir(const std::string& path) {
- if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
- std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
- return false;
- }
- return true;
-}
-
-class SnapshotTestEnvironment : public ::testing::Environment {
- public:
- ~SnapshotTestEnvironment() override {}
- void SetUp() override;
- void TearDown() override;
-
- private:
- bool CreateFakeSuper();
-
- std::unique_ptr<IImageManager> super_images_;
-};
-
-bool SnapshotTestEnvironment::CreateFakeSuper() {
- // Create and map the fake super partition.
- static constexpr int kImageFlags =
- IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
- if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
- LOG(ERROR) << "Could not create fake super partition";
- return false;
- }
- if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) {
- LOG(ERROR) << "Could not map fake super partition";
- return false;
- }
- test_device->set_fake_super(fake_super);
- return true;
-}
-
-void SnapshotTestEnvironment::SetUp() {
- // b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until
- // that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test
- // suites.
- RETURN_IF_NON_VIRTUAL_AB_MSG("Virtual A/B is not enabled, skipping global setup.\n");
-
- std::vector<std::string> paths = {
- // clang-format off
- "/data/gsi/ota/test",
- "/data/gsi/ota/test/super",
- "/metadata/gsi/ota/test",
- "/metadata/gsi/ota/test/super",
- "/metadata/ota/test",
- "/metadata/ota/test/snapshots",
- // clang-format on
- };
- for (const auto& path : paths) {
- ASSERT_TRUE(Mkdir(path));
- }
-
- // Create this once, otherwise, gsid will start/stop between each test.
- test_device = new TestDeviceInfo();
- sm = SnapshotManager::New(test_device);
- ASSERT_NE(nullptr, sm) << "Could not create snapshot manager";
-
- // Use a separate image manager for our fake super partition.
- super_images_ = IImageManager::Open("ota/test/super", 10s);
- ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
-
- // Map the old image if one exists so we can safely unmap everything that
- // depends on it.
- bool recreate_fake_super;
- if (super_images_->BackingImageExists("fake-super")) {
- if (super_images_->IsImageMapped("fake-super")) {
- ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super));
- } else {
- ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super));
- }
- test_device->set_fake_super(fake_super);
- recreate_fake_super = true;
- } else {
- ASSERT_TRUE(CreateFakeSuper());
- recreate_fake_super = false;
- }
-
- // Clean up previous run.
- MetadataMountedTest().TearDown();
- SnapshotUpdateTest().Cleanup();
- SnapshotTest().Cleanup();
-
- if (recreate_fake_super) {
- // Clean up any old copy.
- DeleteBackingImage(super_images_.get(), "fake-super");
- ASSERT_TRUE(CreateFakeSuper());
- }
-}
-
-void SnapshotTestEnvironment::TearDown() {
- RETURN_IF_NON_VIRTUAL_AB();
- if (super_images_ != nullptr) {
- DeleteBackingImage(super_images_.get(), "fake-super");
- }
-}
-
-} // namespace snapshot
-} // namespace android
-
-int main(int argc, char** argv) {
- ::testing::InitGoogleTest(&argc, argv);
- ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
-
- android::base::SetProperty("ctl.stop", "snapuserd");
- android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
-
- return RUN_ALL_TESTS();
-}
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index a7f0c0e..0aedc58 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -29,7 +29,9 @@
srcs: [
"gatekeeperd.cpp",
],
-
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ ],
shared_libs: [
"libbinder",
"libbinder_ndk",
@@ -43,7 +45,6 @@
"libhidlbase",
"android.hardware.gatekeeper@1.0",
"libgatekeeper_aidl",
- "android.hardware.security.keymint-V1-ndk",
"android.security.authorization-ndk",
],
diff --git a/healthd/Android.bp b/healthd/Android.bp
index eaa8e5b..24777c8 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -216,8 +216,6 @@
shared_libs: [
// common
- "android.hardware.health@2.0",
- "android.hardware.health@2.1",
"libbase",
"libcutils",
"libhidlbase",
@@ -255,6 +253,10 @@
"charger.cpp",
"charger_utils.cpp",
],
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "android.hardware.health@2.1",
+ ],
target: {
recovery: {
@@ -280,6 +282,11 @@
name: "charger_test",
defaults: ["charger_defaults"],
srcs: ["charger_test.cpp"],
+ static_libs: [
+ "android.hardware.health@1.0",
+ "android.hardware.health@2.0",
+ "android.hardware.health@2.1",
+ ],
}
cc_test {
@@ -290,6 +297,9 @@
"healthd_mode_charger_test.cpp"
],
static_libs: [
+ "android.hardware.health@1.0",
+ "android.hardware.health@2.0",
+ "android.hardware.health@2.1",
"libgmock",
],
test_suites: [
diff --git a/init/README.md b/init/README.md
index ebab073..c102b1f 100644
--- a/init/README.md
+++ b/init/README.md
@@ -641,9 +641,10 @@
configurations. Intended to be used only once when apexd notifies the mount
event by setting `apexd.status` to ready.
-`restart <service>`
+`restart [--only-if-running] <service>`
> Stops and restarts a running service, does nothing if the service is currently
- restarting, otherwise, it just starts the service.
+ restarting, otherwise, it just starts the service. If "--only-if-running" is
+ specified, the service is only restarted if it is already running.
`restorecon <path> [ <path>\* ]`
> Restore the file named by _path_ to the security context specified
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 98831e1..8045c71 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -774,8 +774,21 @@
}
static Result<void> do_restart(const BuiltinArguments& args) {
- Service* svc = ServiceList::GetInstance().FindService(args[1]);
- if (!svc) return Error() << "service " << args[1] << " not found";
+ bool only_if_running = false;
+ if (args.size() == 3) {
+ if (args[1] == "--only-if-running") {
+ only_if_running = true;
+ } else {
+ return Error() << "Unknown argument to restart: " << args[1];
+ }
+ }
+
+ const auto& classname = args[args.size() - 1];
+ Service* svc = ServiceList::GetInstance().FindService(classname);
+ if (!svc) return Error() << "service " << classname << " not found";
+ if (only_if_running && !svc->IsRunning()) {
+ return {};
+ }
svc->Restart();
return {};
}
@@ -1453,7 +1466,7 @@
{"update_linker_config", {0, 0, {false, do_update_linker_config}}},
{"readahead", {1, 2, {true, do_readahead}}},
{"remount_userdata", {0, 0, {false, do_remount_userdata}}},
- {"restart", {1, 1, {false, do_restart}}},
+ {"restart", {1, 2, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
{"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}},
{"rm", {1, 1, {true, do_rm}}},
diff --git a/init/ueventd_test.cpp b/init/ueventd_test.cpp
index fc3cdfb..1ac6d8e 100644
--- a/init/ueventd_test.cpp
+++ b/init/ueventd_test.cpp
@@ -99,7 +99,7 @@
const char* const contexts[] = {
"u:object_r:audio_device:s0",
"u:object_r:sensors_device:s0",
- "u:object_r:video_device:s0"
+ "u:object_r:video_device:s0",
"u:object_r:zero_device:s0",
};
diff --git a/libasyncio/Android.bp b/libasyncio/Android.bp
index 692e223..296e207 100644
--- a/libasyncio/Android.bp
+++ b/libasyncio/Android.bp
@@ -32,6 +32,7 @@
defaults: ["libasyncio_defaults"],
vendor_available: true,
recovery_available: true,
+ min_sdk_version: "apex_inherit",
apex_available: [
"//apex_available:platform",
"com.android.adbd",
diff --git a/libcrypto_utils/Android.bp b/libcrypto_utils/Android.bp
index a9f6958..2559137 100644
--- a/libcrypto_utils/Android.bp
+++ b/libcrypto_utils/Android.bp
@@ -44,6 +44,7 @@
enabled: true,
},
},
+ min_sdk_version: "apex_inherit",
apex_available: [
"//apex_available:platform",
"com.android.adbd",
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0d9f2c7..c8bfb01 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -156,7 +156,6 @@
},
srcs: [
"config_utils.cpp",
- "canned_fs_config.cpp",
"iosched_policy.cpp",
"load_file.cpp",
"native_handle.cpp",
@@ -173,6 +172,7 @@
not_windows: {
srcs: libcutils_nonwindows_sources + [
"ashmem-host.cpp",
+ "canned_fs_config.cpp",
"fs_config.cpp",
"trace-host.cpp",
],
@@ -193,6 +193,7 @@
srcs: libcutils_nonwindows_sources + [
"android_reboot.cpp",
"ashmem-dev.cpp",
+ "canned_fs_config.cpp",
"fs_config.cpp",
"klog.cpp",
"partition_utils.cpp",
diff --git a/libcutils/canned_fs_config.cpp b/libcutils/canned_fs_config.cpp
index 2772ef0..b677949 100644
--- a/libcutils/canned_fs_config.cpp
+++ b/libcutils/canned_fs_config.cpp
@@ -18,6 +18,8 @@
#include <private/canned_fs_config.h>
#include <private/fs_config.h>
+#include <android-base/strings.h>
+
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
@@ -25,104 +27,107 @@
#include <stdlib.h>
#include <string.h>
-typedef struct {
- const char* path;
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+using android::base::ConsumePrefix;
+using android::base::StartsWith;
+using android::base::Tokenize;
+
+struct Entry {
+ std::string path;
unsigned uid;
unsigned gid;
unsigned mode;
uint64_t capabilities;
-} Path;
+};
-static Path* canned_data = NULL;
-static int canned_alloc = 0;
-static int canned_used = 0;
-
-static int path_compare(const void* a, const void* b) {
- return strcmp(((Path*)a)->path, ((Path*)b)->path);
-}
+static std::vector<Entry> canned_data;
int load_canned_fs_config(const char* fn) {
- char buf[PATH_MAX + 200];
- FILE* f;
-
- f = fopen(fn, "r");
- if (f == NULL) {
- fprintf(stderr, "failed to open %s: %s\n", fn, strerror(errno));
- return -1;
- }
-
- while (fgets(buf, sizeof(buf), f)) {
- Path* p;
- char* token;
- char* line = buf;
- bool rootdir;
-
- while (canned_used >= canned_alloc) {
- canned_alloc = (canned_alloc+1) * 2;
- canned_data = (Path*) realloc(canned_data, canned_alloc * sizeof(Path));
+ std::ifstream input(fn);
+ for (std::string line; std::getline(input, line);) {
+ // Historical: the root dir can be represented as a space character.
+ // e.g. " 1000 1000 0755" is parsed as
+ // path = " ", uid = 1000, gid = 1000, mode = 0755.
+ // But at the same time, we also have accepted
+ // "/ 1000 1000 0755".
+ if (StartsWith(line, " ")) {
+ line.insert(line.begin(), '/');
}
- p = canned_data + canned_used;
- if (line[0] == '/') line++;
- rootdir = line[0] == ' ';
- p->path = strdup(rootdir ? "" : strtok(line, " "));
- p->uid = atoi(strtok(rootdir ? line : NULL, " "));
- p->gid = atoi(strtok(NULL, " "));
- p->mode = strtol(strtok(NULL, " "), NULL, 8); // mode is in octal
- p->capabilities = 0;
- do {
- token = strtok(NULL, " ");
- if (token && strncmp(token, "capabilities=", 13) == 0) {
- p->capabilities = strtoll(token+13, NULL, 0);
+ std::vector<std::string> tokens = Tokenize(line, " ");
+ if (tokens.size() < 4) {
+ std::cerr << "Ill-formed line: " << line << " in " << fn << std::endl;
+ return -1;
+ }
+
+ // Historical: remove the leading '/' if exists.
+ std::string path(tokens[0].front() == '/' ? std::string(tokens[0], 1) : tokens[0]);
+
+ Entry e{
+ .path = std::move(path),
+ .uid = static_cast<unsigned int>(atoi(tokens[1].c_str())),
+ .gid = static_cast<unsigned int>(atoi(tokens[2].c_str())),
+ // mode is in octal
+ .mode = static_cast<unsigned int>(strtol(tokens[3].c_str(), nullptr, 8)),
+ .capabilities = 0,
+ };
+
+ for (size_t i = 4; i < tokens.size(); i++) {
+ std::string_view sv = tokens[i];
+ if (ConsumePrefix(&sv, "capabilities=")) {
+ e.capabilities = strtoll(std::string(sv).c_str(), nullptr, 0);
break;
}
- } while (token);
+ // Historical: there can be tokens like "selabel=..." here. They have been ignored.
+ // It's not an error because selabels are applied separately in e2fsdroid using the
+ // file_contexts files set via -S option.
+ std::cerr << "info: ignored token \"" << sv << "\" in " << fn << std::endl;
+ }
- canned_used++;
+ canned_data.emplace_back(std::move(e));
}
- fclose(f);
+ // Note: we used to sort the entries by path names. This was to improve the lookup performance
+ // by doing binary search. However, this is no longer the case. The lookup performance is not
+ // critical because this tool runs on the host, not on the device. Now, there can be multiple
+ // entries for the same path. Then the one that comes the last wins. This is to allow overriding
+ // platform provided fs_config with a user provided fs_config by appending the latter to the
+ // former.
+ //
+ // To implement the strategy, reverse the entries order, and search from the top.
+ std::reverse(canned_data.begin(), canned_data.end());
- qsort(canned_data, canned_used, sizeof(Path), path_compare);
- printf("loaded %d fs_config entries\n", canned_used);
-
+ std::cout << "loaded " << canned_data.size() << " fs_config entries" << std::endl;
return 0;
}
-static const int kDebugCannedFsConfig = 0;
+void canned_fs_config(const char* path, [[maybe_unused]] int dir,
+ [[maybe_unused]] const char* target_out_path, unsigned* uid, unsigned* gid,
+ unsigned* mode, uint64_t* capabilities) {
+ if (path != nullptr && path[0] == '/') path++; // canned paths lack the leading '/'
-void canned_fs_config(const char* path, int dir, const char* target_out_path,
- unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) {
- Path key, *p;
+ const Entry* found = nullptr;
+ // canned_data is already reversed. First match wins.
+ for (const auto& entry : canned_data) {
+ if (path == entry.path) {
+ found = &entry;
+ break;
+ }
+ continue;
+ }
- key.path = path;
- if (path[0] == '/') key.path++; // canned paths lack the leading '/'
- p = (Path*) bsearch(&key, canned_data, canned_used, sizeof(Path), path_compare);
- if (p == NULL) {
- fprintf(stderr, "failed to find [%s] in canned fs_config\n", path);
+ if (found == nullptr) {
+ std::cerr << "failed to find " << path << " in canned fs_config" << std::endl;
exit(1);
}
- *uid = p->uid;
- *gid = p->gid;
- *mode = p->mode;
- *capabilities = p->capabilities;
- if (kDebugCannedFsConfig) {
- // for debugging, run the built-in fs_config and compare the results.
-
- unsigned c_uid, c_gid, c_mode;
- uint64_t c_capabilities;
-
- fs_config(path, dir, target_out_path, &c_uid, &c_gid, &c_mode, &c_capabilities);
-
- if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid);
- if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid);
- if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode);
- if (c_capabilities != *capabilities) {
- printf("%s capabilities %" PRIx64 " %" PRIx64 "\n",
- path,
- *capabilities,
- c_capabilities);
- }
- }
+ *uid = found->uid;
+ *gid = found->gid;
+ *mode = found->mode;
+ *capabilities = found->capabilities;
}
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 24c6ae6..97e93f8 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -208,6 +208,39 @@
}
/**
+ * Trace an instantaneous context. name is used to identify the context.
+ *
+ * An "instant" is an event with no defined duration. Visually is displayed like a single marker
+ * in the timeline (rather than a span, in the case of begin/end events).
+ *
+ * By default, instant events are added into a dedicated track that has the same name of the event.
+ * Use atrace_instant_for_track to put different instant events into the same timeline track/row.
+ */
+#define ATRACE_INSTANT(name) atrace_instant(ATRACE_TAG, name)
+static inline void atrace_instant(uint64_t tag, const char* name) {
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ void atrace_instant_body(const char*);
+ atrace_instant_body(name);
+ }
+}
+
+/**
+ * Trace an instantaneous context. name is used to identify the context.
+ * trackName is the name of the row where the event should be recorded.
+ *
+ * An "instant" is an event with no defined duration. Visually is displayed like a single marker
+ * in the timeline (rather than a span, in the case of begin/end events).
+ */
+#define ATRACE_INSTANT_FOR_TRACK(trackName, name) \
+ atrace_instant_for_track(ATRACE_TAG, trackName, name)
+static inline void atrace_instant_for_track(uint64_t tag, const char* trackName, const char* name) {
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ void atrace_instant_for_track_body(const char*, const char*);
+ atrace_instant_for_track_body(trackName, name);
+ }
+}
+
+/**
* Traces an integer counter value. name is used to identify the counter.
* This can be used to track how a value changes over time.
*/
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index e65fe92..23d1415 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -131,6 +131,7 @@
#define AID_ARTD 1082 /* ART Service daemon */
#define AID_UWB 1083 /* UWB subsystem */
#define AID_THREAD_NETWORK 1084 /* Thread Network subsystem */
+#define AID_DICED 1085 /* Android's DICE daemon */
/* Changes to this file must be made in AOSP, *not* in internal branches. */
#define AID_SHELL 2000 /* adb and debug shell user */
@@ -158,6 +159,7 @@
#define AID_READPROC 3009 /* Allow /proc read access */
#define AID_WAKELOCK 3010 /* Allow system wakelock read/write access */
#define AID_UHID 3011 /* Allow read/write to /dev/uhid node */
+#define AID_READTRACEFS 3012 /* Allow tracefs read */
/* The range 5000-5999 is also reserved for vendor partition. */
#define AID_OEM_RESERVED_2_START 5000
diff --git a/libcutils/trace-container.cpp b/libcutils/trace-container.cpp
index f7eed48..ef7c72d 100644
--- a/libcutils/trace-container.cpp
+++ b/libcutils/trace-container.cpp
@@ -217,6 +217,28 @@
WRITE_MSG("F|%d|", "|%" PRId32, name, cookie);
}
+void atrace_instant_body(const char* name) {
+ if (CC_LIKELY(atrace_use_container_sock)) {
+ WRITE_MSG_IN_CONTAINER("I", "|", "%s", name, "");
+ return;
+ }
+
+ if (atrace_marker_fd < 0) return;
+
+ WRITE_MSG("I|%d|", "%s", name, "");
+}
+
+void atrace_instant_for_track_body(const char* trackName, const char* name) {
+ if (CC_LIKELY(atrace_use_container_sock)) {
+ WRITE_MSG_IN_CONTAINER("N", "|", "|%s", trackName, name);
+ return;
+ }
+
+ if (atrace_marker_fd < 0) return;
+
+ WRITE_MSG("N|%d|", "|%s", name, trackName);
+}
+
void atrace_int_body(const char* name, int32_t value)
{
if (CC_LIKELY(atrace_use_container_sock)) {
diff --git a/libcutils/trace-dev.cpp b/libcutils/trace-dev.cpp
index 1ab63dc..25c86f4 100644
--- a/libcutils/trace-dev.cpp
+++ b/libcutils/trace-dev.cpp
@@ -89,6 +89,14 @@
WRITE_MSG("F|%d|", "|%" PRId32, name, cookie);
}
+void atrace_instant_body(const char* name) {
+ WRITE_MSG("I|%d|", "%s", name, "");
+}
+
+void atrace_instant_for_track_body(const char* trackName, const char* name) {
+ WRITE_MSG("N|%d|", "|%s", trackName, name);
+}
+
void atrace_int_body(const char* name, int32_t value)
{
WRITE_MSG("C|%d|", "|%" PRId32, name, value);
diff --git a/libcutils/trace-dev_test.cpp b/libcutils/trace-dev_test.cpp
index 832b36a..ff6d202 100644
--- a/libcutils/trace-dev_test.cpp
+++ b/libcutils/trace-dev_test.cpp
@@ -195,6 +195,106 @@
ASSERT_STREQ(expected.c_str(), actual.c_str());
}
+TEST_F(TraceDevTest, atrace_instant_body_normal) {
+ atrace_instant_body("fake_name");
+
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ std::string expected = android::base::StringPrintf("I|%d|fake_name", getpid());
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_body_exact) {
+ std::string expected = android::base::StringPrintf("I|%d|", getpid());
+ std::string name = MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1);
+ atrace_instant_body(name.c_str());
+
+ ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ expected += name;
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+ // Add a single character and verify we get the exact same value as before.
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+ name += '*';
+ atrace_instant_body(name.c_str());
+ EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_body_truncated) {
+ std::string expected = android::base::StringPrintf("I|%d|", getpid());
+ std::string name = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+ atrace_instant_body(name.c_str());
+
+ ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1;
+ expected += android::base::StringPrintf("%.*s", expected_len, name.c_str());
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_normal) {
+ atrace_instant_for_track_body("fake_track", "fake_name");
+
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ std::string expected = android::base::StringPrintf("N|%d|fake_track|fake_name", getpid());
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_exact) {
+ const int nameSize = 5;
+ std::string expected = android::base::StringPrintf("N|%d|", getpid());
+ std::string trackName = MakeName(ATRACE_MESSAGE_LENGTH - expected.length() - 1 - nameSize);
+ atrace_instant_for_track_body(trackName.c_str(), "name");
+
+ ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ expected += trackName + "|name";
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+ // Add a single character and verify we get the exact same value as before.
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+ trackName += '*';
+ atrace_instant_for_track_body(trackName.c_str(), "name");
+ EXPECT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST_F(TraceDevTest, atrace_instant_for_track_body_truncated) {
+ const int nameSize = 5;
+ std::string expected = android::base::StringPrintf("N|%d|", getpid());
+ std::string trackName = MakeName(2 * ATRACE_MESSAGE_LENGTH);
+ atrace_instant_for_track_body(trackName.c_str(), "name");
+
+ ASSERT_EQ(ATRACE_MESSAGE_LENGTH - 1, lseek(atrace_marker_fd, 0, SEEK_CUR));
+ ASSERT_EQ(0, lseek(atrace_marker_fd, 0, SEEK_SET));
+
+ std::string actual;
+ ASSERT_TRUE(android::base::ReadFdToString(atrace_marker_fd, &actual));
+ int expected_len = ATRACE_MESSAGE_LENGTH - expected.length() - 1 - nameSize;
+ expected += android::base::StringPrintf("%.*s|name", expected_len, trackName.c_str());
+ ASSERT_STREQ(expected.c_str(), actual.c_str());
+}
+
TEST_F(TraceDevTest, atrace_int_body_normal) {
atrace_int_body("fake_name", 12345);
diff --git a/libcutils/trace-host.cpp b/libcutils/trace-host.cpp
index 9781ad3..b01a0ec 100644
--- a/libcutils/trace-host.cpp
+++ b/libcutils/trace-host.cpp
@@ -28,6 +28,9 @@
void atrace_end_body() { }
void atrace_async_begin_body(const char* /*name*/, int32_t /*cookie*/) {}
void atrace_async_end_body(const char* /*name*/, int32_t /*cookie*/) {}
+void atrace_instant_body(const char* /*name*/) {}
+void atrace_instant_for_track_body(const char* /*trackName*/,
+ const char* /*name*/) {}
void atrace_int_body(const char* /*name*/, int32_t /*value*/) {}
void atrace_int64_body(const char* /*name*/, int64_t /*value*/) {}
void atrace_init() {}
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 45d3c7c..b668dcb 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -651,6 +651,10 @@
{
"Name": "Dex2OatBootComplete",
"Profiles": [ "Dex2oatPerformance", "LowIoPriority", "TimerSlackHigh" ]
+ },
+ {
+ "Name": "OtaProfiles",
+ "Profiles": [ "ServiceCapacityLow", "LowIoPriority", "HighEnergySaving" ]
}
]
}
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index e935f99..3834f91 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -144,30 +144,13 @@
return true;
}
-bool SetCgroupAction::IsAppDependentPath(const std::string& path) {
- return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
-}
-
-SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
- : controller_(c), path_(p) {
- // file descriptors for app-dependent paths can't be cached
- if (IsAppDependentPath(path_)) {
- // file descriptor is not cached
- fd_.reset(FDS_APP_DEPENDENT);
- return;
- }
-
- // file descriptor can be cached later on request
- fd_.reset(FDS_NOT_CACHED);
-}
-
-void SetCgroupAction::EnableResourceCaching() {
+void CachedFdProfileAction::EnableResourceCaching() {
std::lock_guard<std::mutex> lock(fd_mutex_);
if (fd_ != FDS_NOT_CACHED) {
return;
}
- std::string tasks_path = controller_.GetTasksFilePath(path_);
+ std::string tasks_path = GetPath();
if (access(tasks_path.c_str(), W_OK) != 0) {
// file is not accessible
@@ -185,7 +168,7 @@
fd_ = std::move(fd);
}
-void SetCgroupAction::DropResourceCaching() {
+void CachedFdProfileAction::DropResourceCaching() {
std::lock_guard<std::mutex> lock(fd_mutex_);
if (fd_ == FDS_NOT_CACHED) {
return;
@@ -194,6 +177,26 @@
fd_.reset(FDS_NOT_CACHED);
}
+bool CachedFdProfileAction::IsAppDependentPath(const std::string& path) {
+ return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
+}
+
+void CachedFdProfileAction::InitFd(const std::string& path) {
+ // file descriptors for app-dependent paths can't be cached
+ if (IsAppDependentPath(path)) {
+ // file descriptor is not cached
+ fd_.reset(FDS_APP_DEPENDENT);
+ return;
+ }
+ // file descriptor can be cached later on request
+ fd_.reset(FDS_NOT_CACHED);
+}
+
+SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
+ : controller_(c), path_(p) {
+ InitFd(controller_.GetTasksFilePath(path_));
+}
+
bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
if (tid <= 0) {
return true;
@@ -270,7 +273,7 @@
std::string tasks_path = controller()->GetTasksFilePath(path_);
unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
if (tmp_fd < 0) {
- PLOG(WARNING) << "Failed to open " << tasks_path << ": " << strerror(errno);
+ PLOG(WARNING) << "Failed to open " << tasks_path;
return false;
}
if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
@@ -281,37 +284,73 @@
return true;
}
-bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
- std::string filepath(filepath_), value(value_);
+WriteFileAction::WriteFileAction(const std::string& path, const std::string& value,
+ bool logfailures)
+ : path_(path), value_(value), logfailures_(logfailures) {
+ InitFd(path_);
+}
- 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);
+bool WriteFileAction::WriteValueToFile(const std::string& value, const std::string& path,
+ bool logfailures) {
+ // Use WriteStringToFd instead of WriteStringToFile because the latter will open file with
+ // O_TRUNC which causes kernfs_mutex contention
+ unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_WRONLY | O_CLOEXEC)));
- if (!WriteStringToFile(value, filepath)) {
- if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+ if (tmp_fd < 0) {
+ if (logfailures) PLOG(WARNING) << "Failed to open " << path;
+ return false;
+ }
+
+ if (!WriteStringToFd(value, tmp_fd)) {
+ if (logfailures) PLOG(ERROR) << "Failed to write '" << value << "' to " << path;
return false;
}
return true;
}
+bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+ std::lock_guard<std::mutex> lock(fd_mutex_);
+ std::string value(value_);
+ std::string path(path_);
+
+ value = StringReplace(value, "<uid>", std::to_string(uid), true);
+ value = StringReplace(value, "<pid>", std::to_string(pid), true);
+ path = StringReplace(path, "<uid>", std::to_string(uid), true);
+ path = StringReplace(path, "<pid>", std::to_string(pid), true);
+
+ return WriteValueToFile(value, path, logfailures_);
+}
+
bool WriteFileAction::ExecuteForTask(int tid) const {
- std::string filepath(filepath_), value(value_);
+ std::lock_guard<std::mutex> lock(fd_mutex_);
+ std::string 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)) {
- if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+ if (IsFdValid()) {
+ // fd is cached, reuse it
+ if (!WriteStringToFd(value, fd_)) {
+ if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << path_;
+ return false;
+ }
+ return true;
+ }
+
+ if (fd_ == FDS_INACCESSIBLE) {
+ // no permissions to access the file, ignore
+ return true;
+ }
+
+ if (fd_ == FDS_APP_DEPENDENT) {
+ // application-dependent path can't be used with tid
+ PLOG(ERROR) << "Application profile can't be applied to a thread";
return false;
}
- return true;
+ return WriteValueToFile(value, path_, logfailures_);
}
bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 97c38f4..278892d 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -108,50 +108,67 @@
std::string value_;
};
-// Set cgroup profile element
-class SetCgroupAction : public ProfileAction {
+// Abstract profile element for cached fd
+class CachedFdProfileAction : public ProfileAction {
public:
- SetCgroupAction(const CgroupController& c, const std::string& p);
-
- virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
- virtual bool ExecuteForTask(int tid) const;
virtual void EnableResourceCaching();
virtual void DropResourceCaching();
- const CgroupController* controller() const { return &controller_; }
- std::string path() const { return path_; }
-
- private:
+ protected:
enum FdState {
FDS_INACCESSIBLE = -1,
FDS_APP_DEPENDENT = -2,
FDS_NOT_CACHED = -3,
};
- CgroupController controller_;
- std::string path_;
android::base::unique_fd fd_;
mutable std::mutex fd_mutex_;
static bool IsAppDependentPath(const std::string& path);
- static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+ void InitFd(const std::string& path);
bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
+
+ virtual const std::string GetPath() const = 0;
};
-// Write to file action
-class WriteFileAction : public ProfileAction {
+// Set cgroup profile element
+class SetCgroupAction : public CachedFdProfileAction {
public:
- WriteFileAction(const std::string& filepath, const std::string& value,
- bool logfailures) noexcept
- : filepath_(filepath), value_(value), logfailures_(logfailures) {}
+ SetCgroupAction(const CgroupController& c, const std::string& p);
virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
virtual bool ExecuteForTask(int tid) const;
+ const CgroupController* controller() const { return &controller_; }
+
+ protected:
+ const std::string GetPath() const override { return controller_.GetTasksFilePath(path_); }
+
private:
- std::string filepath_, value_;
+ CgroupController controller_;
+ std::string path_;
+
+ static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+};
+
+// Write to file action
+class WriteFileAction : public CachedFdProfileAction {
+ public:
+ WriteFileAction(const std::string& path, const std::string& value, bool logfailures);
+
+ virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+ virtual bool ExecuteForTask(int tid) const;
+
+ protected:
+ const std::string GetPath() const override { return path_; }
+
+ private:
+ std::string path_, value_;
bool logfailures_;
+
+ static bool WriteValueToFile(const std::string& value, const std::string& path,
+ bool logfailures);
};
class TaskProfile {
diff --git a/libstats/bootstrap/Android.bp b/libstats/bootstrap/Android.bp
new file mode 100644
index 0000000..332d9c8
--- /dev/null
+++ b/libstats/bootstrap/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// =========================================================================
+// Native library that provide a client to StatsBootstrapAtomService.
+// This library should only be used by processes that start in the bootstrap namespace.
+// All other clients should use libstatssocket, provided by the statsd apex.
+// =========================================================================
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "libstatsbootstrap_defaults",
+ srcs: [
+ "BootstrapClientInternal.cpp",
+ "StatsBootstrapAtomClient.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ "android.os.statsbootstrap_aidl-cpp",
+ ],
+}
+
+cc_library {
+ name: "libstatsbootstrap",
+ defaults: ["libstatsbootstrap_defaults"],
+ export_include_dirs: ["include"],
+}
+
+
diff --git a/libstats/bootstrap/BootstrapClientInternal.cpp b/libstats/bootstrap/BootstrapClientInternal.cpp
new file mode 100644
index 0000000..b02e116
--- /dev/null
+++ b/libstats/bootstrap/BootstrapClientInternal.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BootstrapClientInternal.h"
+
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+sp<BootstrapClientInternal> BootstrapClientInternal::getInstance() {
+ static sp<BootstrapClientInternal> client = new BootstrapClientInternal();
+ return client;
+}
+
+sp<IStatsBootstrapAtomService> BootstrapClientInternal::getServiceNonBlocking() {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mService != nullptr) {
+ return mService;
+ }
+ connectNonBlockingLocked();
+ return mService;
+}
+
+void BootstrapClientInternal::binderDied(const wp<IBinder>&) {
+ std::lock_guard<std::mutex> lock(mLock);
+ mService = nullptr;
+ connectNonBlockingLocked();
+}
+
+void BootstrapClientInternal::connectNonBlockingLocked() {
+ const String16 name("statsbootstrap");
+ mService =
+ interface_cast<IStatsBootstrapAtomService>(defaultServiceManager()->checkService(name));
+ if (mService != nullptr) {
+ // Set up binder death.
+ IInterface::asBinder(mService)->linkToDeath(this);
+ }
+}
+
+} // namespace stats
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/libstats/bootstrap/BootstrapClientInternal.h b/libstats/bootstrap/BootstrapClientInternal.h
new file mode 100644
index 0000000..96238da
--- /dev/null
+++ b/libstats/bootstrap/BootstrapClientInternal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/os/IStatsBootstrapAtomService.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+class BootstrapClientInternal : public IBinder::DeathRecipient {
+ public:
+ static sp<BootstrapClientInternal> getInstance();
+ void binderDied(const wp<IBinder>& who) override;
+ sp<IStatsBootstrapAtomService> getServiceNonBlocking();
+
+ private:
+ BootstrapClientInternal() {}
+ void connectNonBlockingLocked();
+
+ mutable std::mutex mLock;
+ sp<IStatsBootstrapAtomService> mService;
+};
+
+} // namespace stats
+} // namespace os
+} // namespace android
diff --git a/libstats/bootstrap/StatsBootstrapAtomClient.cpp b/libstats/bootstrap/StatsBootstrapAtomClient.cpp
new file mode 100644
index 0000000..348b7fa
--- /dev/null
+++ b/libstats/bootstrap/StatsBootstrapAtomClient.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "include/StatsBootstrapAtomClient.h"
+
+#include <android/os/IStatsBootstrapAtomService.h>
+
+#include "BootstrapClientInternal.h"
+
+namespace android {
+namespace os {
+namespace stats {
+
+bool StatsBootstrapAtomClient::reportBootstrapAtom(const StatsBootstrapAtom& atom) {
+ sp<IStatsBootstrapAtomService> service =
+ BootstrapClientInternal::getInstance()->getServiceNonBlocking();
+ if (service == nullptr) {
+ return false;
+ }
+ return service->reportBootstrapAtom(atom).isOk();
+}
+
+} // namespace stats
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/libstats/bootstrap/include/StatsBootstrapAtomClient.h b/libstats/bootstrap/include/StatsBootstrapAtomClient.h
new file mode 100644
index 0000000..87930fd
--- /dev/null
+++ b/libstats/bootstrap/include/StatsBootstrapAtomClient.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/os/StatsBootstrapAtom.h>
+
+namespace android {
+namespace os {
+namespace stats {
+
+class StatsBootstrapAtomClient {
+ public:
+ static bool reportBootstrapAtom(const StatsBootstrapAtom& atom);
+};
+
+} // namespace stats
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 6e293c7..3bf5779 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -317,10 +317,7 @@
if (pri >= ANDROID_PRIORITY_BACKGROUND) {
rc = SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
} else if (curr_pri >= ANDROID_PRIORITY_BACKGROUND) {
- SchedPolicy policy = SP_FOREGROUND;
- // Change to the sched policy group of the process.
- get_sched_policy(getpid(), &policy);
- rc = SetTaskProfiles(tid, {get_sched_policy_profile_name(policy)}, true) ? 0 : -1;
+ rc = SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
}
if (rc) {
diff --git a/rootdir/init.rc b/rootdir/init.rc
index b09c2f1..42545d9 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -78,8 +78,8 @@
mkdir /dev/boringssl 0755 root root
mkdir /dev/boringssl/selftest 0755 root root
- # Mount tracefs
- mount tracefs tracefs /sys/kernel/tracing
+ # Mount tracefs (with GID=AID_READTRACEFS)
+ mount tracefs tracefs /sys/kernel/tracing gid=3012
# create sys dirctory
mkdir /dev/sys 0755 system system
diff --git a/storaged/Android.bp b/storaged/Android.bp
index b557dee..7960af3 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -24,8 +24,10 @@
shared_libs: [
"android.hardware.health@1.0",
"android.hardware.health@2.0",
+ "android.hardware.health-V1-ndk",
"libbase",
"libbinder",
+ "libbinder_ndk",
"libcutils",
"libhidlbase",
"liblog",
@@ -35,6 +37,12 @@
"packagemanager_aidl-cpp",
],
+ static_libs: [
+ "android.hardware.health-translate-ndk",
+ "libhealthhalutils",
+ "libhealthshim",
+ ],
+
cflags: [
"-Wall",
"-Werror",
@@ -67,7 +75,6 @@
":storaged_aidl_private",
],
- static_libs: ["libhealthhalutils"],
header_libs: ["libbatteryservice_headers"],
logtags: ["EventLogTags.logtags"],
@@ -90,7 +97,6 @@
srcs: ["main.cpp"],
static_libs: [
- "libhealthhalutils",
"libstoraged",
],
}
@@ -107,9 +113,11 @@
srcs: ["tests/storaged_test.cpp"],
static_libs: [
- "libhealthhalutils",
"libstoraged",
],
+ test_suites: [
+ "general-tests",
+ ],
}
// AIDL interface between storaged and framework.jar
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
index 79b5d41..e120271 100644
--- a/storaged/include/storaged.h
+++ b/storaged/include/storaged.h
@@ -28,6 +28,7 @@
#include <utils/Mutex.h>
+#include <aidl/android/hardware/health/IHealth.h>
#include <android/hardware/health/2.0/IHealth.h>
#define FRIEND_TEST(test_case_name, test_name) \
@@ -67,6 +68,8 @@
// UID IO threshold in bytes
#define DEFAULT_PERIODIC_CHORES_UID_IO_THRESHOLD ( 1024 * 1024 * 1024ULL )
+class storaged_t;
+
struct storaged_config {
int periodic_chores_interval_unit;
int periodic_chores_interval_disk_stats_publish;
@@ -75,15 +78,33 @@
int event_time_check_usec; // check how much cputime spent in event loop
};
-class storaged_t : public android::hardware::health::V2_0::IHealthInfoCallback,
- public android::hardware::hidl_death_recipient {
+struct HealthServicePair {
+ std::shared_ptr<aidl::android::hardware::health::IHealth> aidl_health;
+ android::sp<android::hardware::health::V2_0::IHealth> hidl_health;
+ static HealthServicePair get();
+};
+
+class hidl_health_death_recipient : public android::hardware::hidl_death_recipient {
+ public:
+ hidl_health_death_recipient(const android::sp<android::hardware::health::V2_0::IHealth>& health)
+ : mHealth(health) {}
+ void serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who);
+
+ private:
+ android::sp<android::hardware::health::V2_0::IHealth> mHealth;
+};
+
+class storaged_t : public RefBase {
private:
time_t mTimer;
storaged_config mConfig;
unique_ptr<disk_stats_monitor> mDsm;
uid_monitor mUidm;
time_t mStarttime;
- sp<android::hardware::health::V2_0::IHealth> health;
+ std::shared_ptr<aidl::android::hardware::health::IHealth> health;
+ sp<android::hardware::hidl_death_recipient> hidl_death_recp;
+ ndk::ScopedAIBinder_DeathRecipient aidl_death_recp;
+ shared_ptr<aidl::android::hardware::health::IHealthInfoCallback> aidl_health_callback;
unique_ptr<storage_info_t> storage_info;
static const uint32_t current_version;
Mutex proto_lock;
@@ -135,10 +156,6 @@
void add_user_ce(userid_t user_id);
void remove_user_ce(userid_t user_id);
- virtual ::android::hardware::Return<void> healthInfoChanged(
- const ::android::hardware::health::V2_0::HealthInfo& info);
- void serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who);
-
void report_storage_info();
void flush_protos(unordered_map<int, StoragedProto>* protos);
diff --git a/storaged/include/storaged_diskstats.h b/storaged/include/storaged_diskstats.h
index 0b93ba6..3996ef6 100644
--- a/storaged/include/storaged_diskstats.h
+++ b/storaged/include/storaged_diskstats.h
@@ -19,7 +19,7 @@
#include <stdint.h>
-#include <android/hardware/health/2.0/IHealth.h>
+#include <aidl/android/hardware/health/IHealth.h>
// number of attributes diskstats has
#define DISK_STATS_SIZE ( 11 )
@@ -162,7 +162,7 @@
const double mSigma;
struct disk_perf mMean;
struct disk_perf mStd;
- android::sp<android::hardware::health::V2_0::IHealth> mHealth;
+ std::shared_ptr<aidl::android::hardware::health::IHealth> mHealth;
void update_mean();
void update_std();
@@ -173,14 +173,15 @@
void update(struct disk_stats* stats);
public:
- disk_stats_monitor(const android::sp<android::hardware::health::V2_0::IHealth>& healthService,
+ disk_stats_monitor(const std::shared_ptr<aidl::android::hardware::health::IHealth>& healthService,
uint32_t window_size = 5, double sigma = 1.0)
: DISK_STATS_PATH(
- healthService != nullptr
- ? nullptr
- : (access(MMC_DISK_STATS_PATH, R_OK) == 0
- ? MMC_DISK_STATS_PATH
- : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH : nullptr))),
+ healthService != nullptr
+ ? nullptr
+ : (access(MMC_DISK_STATS_PATH, R_OK) == 0
+ ? MMC_DISK_STATS_PATH
+ : (access(SDA_DISK_STATS_PATH, R_OK) == 0 ? SDA_DISK_STATS_PATH
+ : nullptr))),
mPrevious(),
mAccumulate(),
mAccumulate_pub(),
diff --git a/storaged/include/storaged_info.h b/storaged/include/storaged_info.h
index 9c3d0e7..83c97ad 100644
--- a/storaged/include/storaged_info.h
+++ b/storaged/include/storaged_info.h
@@ -21,7 +21,7 @@
#include <chrono>
-#include <android/hardware/health/2.0/IHealth.h>
+#include <aidl/android/hardware/health/IHealth.h>
#include <utils/Mutex.h>
#include "storaged.h"
@@ -71,8 +71,8 @@
public:
static storage_info_t* get_storage_info(
- const sp<android::hardware::health::V2_0::IHealth>& healthService);
- virtual ~storage_info_t() {};
+ const shared_ptr<aidl::android::hardware::health::IHealth>& healthService);
+ virtual ~storage_info_t(){};
virtual void report() {};
void load_perf_history_proto(const IOPerfHistory& perf_history);
void refresh(IOPerfHistory* perf_history);
@@ -105,14 +105,14 @@
class health_storage_info_t : public storage_info_t {
private:
- using IHealth = hardware::health::V2_0::IHealth;
- using StorageInfo = hardware::health::V2_0::StorageInfo;
+ using IHealth = aidl::android::hardware::health::IHealth;
+ using StorageInfo = aidl::android::hardware::health::StorageInfo;
- sp<IHealth> mHealth;
+ shared_ptr<IHealth> mHealth;
void set_values_from_hal_storage_info(const StorageInfo& halInfo);
public:
- health_storage_info_t(const sp<IHealth>& service) : mHealth(service){};
+ health_storage_info_t(const shared_ptr<IHealth>& service) : mHealth(service){};
virtual ~health_storage_info_t() {}
virtual void report();
};
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index b7aa89f..fb855f7 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -28,12 +28,16 @@
#include <sstream>
#include <string>
+#include <aidl/android/hardware/health/BnHealthInfoCallback.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <batteryservice/BatteryServiceConstants.h>
#include <cutils/properties.h>
+#include <health-shim/shim.h>
#include <healthhalutils/HealthHalUtils.h>
#include <hidl/HidlTransportSupport.h>
#include <hwbinder/IPCThreadState.h>
@@ -64,26 +68,59 @@
const uint32_t storaged_t::current_version = 4;
+using aidl::android::hardware::health::BatteryStatus;
+using aidl::android::hardware::health::BnHealthInfoCallback;
+using aidl::android::hardware::health::HealthInfo;
+using aidl::android::hardware::health::IHealth;
+using aidl::android::hardware::health::IHealthInfoCallback;
using android::hardware::interfacesEqual;
-using android::hardware::Return;
-using android::hardware::health::V1_0::BatteryStatus;
-using android::hardware::health::V1_0::toString;
using android::hardware::health::V2_0::get_health_service;
-using android::hardware::health::V2_0::HealthInfo;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
using android::hidl::manager::V1_0::IServiceManager;
+using HidlHealth = android::hardware::health::V2_0::IHealth;
+using aidl::android::hardware::health::HealthShim;
+using ndk::ScopedAIBinder_DeathRecipient;
+using ndk::ScopedAStatus;
+HealthServicePair HealthServicePair::get() {
+ HealthServicePair ret;
+ auto service_name = IHealth::descriptor + "/default"s;
+ if (AServiceManager_isDeclared(service_name.c_str())) {
+ ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str()));
+ ret.aidl_health = IHealth::fromBinder(binder);
+ if (ret.aidl_health == nullptr) {
+ LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved.";
+ }
+ }
+ if (ret.aidl_health == nullptr) {
+ LOG(INFO) << "Unable to get AIDL health service, trying HIDL...";
+ ret.hidl_health = get_health_service();
+ if (ret.hidl_health != nullptr) {
+ ret.aidl_health = ndk::SharedRefBase::make<HealthShim>(ret.hidl_health);
+ }
+ }
+ if (ret.aidl_health == nullptr) {
+ LOG(WARNING) << "health: failed to find IHealth service";
+ return {};
+ }
+ return ret;
+}
inline charger_stat_t is_charger_on(BatteryStatus prop) {
return (prop == BatteryStatus::CHARGING || prop == BatteryStatus::FULL) ?
CHARGER_ON : CHARGER_OFF;
}
-Return<void> storaged_t::healthInfoChanged(const HealthInfo& props) {
- mUidm.set_charger_state(is_charger_on(props.legacy.batteryStatus));
- return android::hardware::Void();
-}
+class HealthInfoCallback : public BnHealthInfoCallback {
+ public:
+ HealthInfoCallback(uid_monitor* uidm) : mUidm(uidm) {}
+ ScopedAStatus healthInfoChanged(const HealthInfo& info) override {
+ mUidm->set_charger_state(is_charger_on(info.batteryStatus));
+ return ScopedAStatus::ok();
+ }
+
+ private:
+ uid_monitor* mUidm;
+};
void storaged_t::init() {
init_health_service();
@@ -91,42 +128,59 @@
storage_info.reset(storage_info_t::get_storage_info(health));
}
+static void onHealthBinderDied(void*) {
+ LOG(ERROR) << "health service died, exiting";
+ android::hardware::IPCThreadState::self()->stopProcess();
+ exit(1);
+}
+
void storaged_t::init_health_service() {
if (!mUidm.enabled())
return;
- health = get_health_service();
- if (health == NULL) {
- LOG(WARNING) << "health: failed to find IHealth service";
- return;
- }
+ auto [aidlHealth, hidlHealth] = HealthServicePair::get();
+ health = aidlHealth;
+ if (health == nullptr) return;
BatteryStatus status = BatteryStatus::UNKNOWN;
- auto ret = health->getChargeStatus([&](Result r, BatteryStatus v) {
- if (r != Result::SUCCESS) {
- LOG(WARNING) << "health: cannot get battery status " << toString(r);
- return;
- }
- if (v == BatteryStatus::UNKNOWN) {
- LOG(WARNING) << "health: invalid battery status";
- }
- status = v;
- });
+ auto ret = health->getChargeStatus(&status);
if (!ret.isOk()) {
- LOG(WARNING) << "health: get charge status transaction error " << ret.description();
+ LOG(WARNING) << "health: cannot get battery status: " << ret.getDescription();
+ }
+ if (status == BatteryStatus::UNKNOWN) {
+ LOG(WARNING) << "health: invalid battery status";
}
mUidm.init(is_charger_on(status));
// register listener after init uid_monitor
- health->registerCallback(this);
- health->linkToDeath(this, 0 /* cookie */);
+ aidl_health_callback = std::make_shared<HealthInfoCallback>(&mUidm);
+ ret = health->registerCallback(aidl_health_callback);
+ if (!ret.isOk()) {
+ LOG(WARNING) << "health: failed to register callback: " << ret.getDescription();
+ }
+
+ if (hidlHealth != nullptr) {
+ hidl_death_recp = new hidl_health_death_recipient(hidlHealth);
+ auto ret = hidlHealth->linkToDeath(hidl_death_recp, 0 /* cookie */);
+ if (!ret.isOk()) {
+ LOG(WARNING) << "Failed to link to death (HIDL): " << ret.description();
+ }
+ } else {
+ aidl_death_recp =
+ ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(onHealthBinderDied));
+ auto ret = AIBinder_linkToDeath(health->asBinder().get(), aidl_death_recp.get(),
+ nullptr /* cookie */);
+ if (ret != STATUS_OK) {
+ LOG(WARNING) << "Failed to link to death (AIDL): "
+ << ScopedAStatus(AStatus_fromStatus(ret)).getDescription();
+ }
+ }
}
-void storaged_t::serviceDied(uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& who) {
- if (health != NULL && interfacesEqual(health, who.promote())) {
- LOG(ERROR) << "health service died, exiting";
- android::hardware::IPCThreadState::self()->stopProcess();
- exit(1);
+void hidl_health_death_recipient::serviceDied(uint64_t cookie,
+ const wp<::android::hidl::base::V1_0::IBase>& who) {
+ if (mHealth != nullptr && interfacesEqual(mHealth, who.promote())) {
+ onHealthBinderDied(reinterpret_cast<void*>(cookie));
} else {
LOG(ERROR) << "unknown service died";
}
diff --git a/storaged/storaged_diskstats.cpp b/storaged/storaged_diskstats.cpp
index 52bd4e0..1eae5a1 100644
--- a/storaged/storaged_diskstats.cpp
+++ b/storaged/storaged_diskstats.cpp
@@ -30,11 +30,8 @@
namespace {
-using android::sp;
-using android::hardware::health::V2_0::DiskStats;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_0::toString;
+using aidl::android::hardware::health::DiskStats;
+using aidl::android::hardware::health::IHealth;
#ifdef DEBUG
void log_debug_disk_perf(struct disk_perf* perf, const char* type) {
@@ -121,39 +118,30 @@
dst->io_in_queue = src.ioInQueue;
}
-bool get_disk_stats_from_health_hal(const sp<IHealth>& service, struct disk_stats* stats) {
+bool get_disk_stats_from_health_hal(const std::shared_ptr<IHealth>& service,
+ struct disk_stats* stats) {
struct timespec ts;
if (!get_time(&ts)) {
return false;
}
- bool success = false;
- auto ret = service->getDiskStats([&success, stats](auto result, const auto& halStats) {
- if (result == Result::NOT_SUPPORTED) {
- LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
- return;
+ std::vector<DiskStats> halStats;
+ auto ret = service->getDiskStats(&halStats);
+ if (ret.isOk()) {
+ if (halStats.size() > 0) {
+ convert_hal_disk_stats(stats, halStats[0]);
+ init_disk_stats_other(ts, stats);
+ return true;
}
- if (result != Result::SUCCESS || halStats.size() == 0) {
- LOG(ERROR) << "getDiskStats failed with result " << toString(result) << " and size "
- << halStats.size();
- return;
- }
-
- convert_hal_disk_stats(stats, halStats[0]);
- success = true;
- });
-
- if (!ret.isOk()) {
- LOG(ERROR) << "getDiskStats failed with " << ret.description();
+ LOG(ERROR) << "getDiskStats succeeded but size is 0";
return false;
}
-
- if (!success) {
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ LOG(DEBUG) << "getDiskStats is not supported on health HAL.";
return false;
}
-
- init_disk_stats_other(ts, stats);
- return true;
+ LOG(ERROR) << "getDiskStats failed with " << ret.getDescription();
+ return false;
}
struct disk_perf get_disk_perf(struct disk_stats* stats)
diff --git a/storaged/storaged_info.cpp b/storaged/storaged_info.cpp
index bb21829..3e646e0 100644
--- a/storaged/storaged_info.cpp
+++ b/storaged/storaged_info.cpp
@@ -36,9 +36,8 @@
using namespace android::base;
using namespace storaged_proto;
-using android::hardware::health::V2_0::IHealth;
-using android::hardware::health::V2_0::Result;
-using android::hardware::health::V2_0::StorageInfo;
+using aidl::android::hardware::health::IHealth;
+using aidl::android::hardware::health::StorageInfo;
const string emmc_info_t::emmc_sysfs = "/sys/bus/mmc/devices/mmc0:0001/";
const char* emmc_info_t::emmc_ver_str[9] = {
@@ -57,7 +56,7 @@
} // namespace
-storage_info_t* storage_info_t::get_storage_info(const sp<IHealth>& healthService) {
+storage_info_t* storage_info_t::get_storage_info(const shared_ptr<IHealth>& healthService) {
if (healthService != nullptr) {
return new health_storage_info_t(healthService);
}
@@ -326,23 +325,22 @@
}
void health_storage_info_t::report() {
- auto ret = mHealth->getStorageInfo([this](auto result, const auto& halInfos) {
- if (result == Result::NOT_SUPPORTED) {
- LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
+ vector<StorageInfo> halInfos;
+ auto ret = mHealth->getStorageInfo(&halInfos);
+ if (ret.isOk()) {
+ if (halInfos.size() != 0) {
+ set_values_from_hal_storage_info(halInfos[0]);
+ publish();
return;
}
- if (result != Result::SUCCESS || halInfos.size() == 0) {
- LOG(ERROR) << "getStorageInfo failed with result " << toString(result) << " and size "
- << halInfos.size();
- return;
- }
- set_values_from_hal_storage_info(halInfos[0]);
- publish();
- });
-
- if (!ret.isOk()) {
- LOG(ERROR) << "getStorageInfo failed with " << ret.description();
+ LOG(ERROR) << "getStorageInfo succeeded but size is 0";
+ return;
}
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ LOG(DEBUG) << "getStorageInfo is not supported on health HAL.";
+ return;
+ }
+ LOG(ERROR) << "getStorageInfo failed with " << ret.getDescription();
}
void health_storage_info_t::set_values_from_hal_storage_info(const StorageInfo& halInfo) {
diff --git a/storaged/tests/storaged_test.cpp b/storaged/tests/storaged_test.cpp
index 64009c2..bb71bf3 100644
--- a/storaged/tests/storaged_test.cpp
+++ b/storaged/tests/storaged_test.cpp
@@ -25,6 +25,7 @@
#include <gtest/gtest.h>
+#include <aidl/android/hardware/health/IHealth.h>
#include <healthhalutils/HealthHalUtils.h>
#include <storaged.h> // data structures
#include <storaged_utils.h> // functions to test
@@ -64,20 +65,23 @@
} // namespace
// the return values of the tested functions should be the expected ones
-const char* DISK_STATS_PATH;
+const char* get_disk_stats_path() {
+ if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
+ return MMC_DISK_STATS_PATH;
+ } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
+ return SDA_DISK_STATS_PATH;
+ } else {
+ return nullptr;
+ }
+}
TEST(storaged_test, retvals) {
struct disk_stats stats;
memset(&stats, 0, sizeof(struct disk_stats));
- if (access(MMC_DISK_STATS_PATH, R_OK) >= 0) {
- DISK_STATS_PATH = MMC_DISK_STATS_PATH;
- } else if (access(SDA_DISK_STATS_PATH, R_OK) >= 0) {
- DISK_STATS_PATH = SDA_DISK_STATS_PATH;
- } else {
- return;
- }
+ auto disk_stats_path = get_disk_stats_path();
+ if (disk_stats_path == nullptr) GTEST_SKIP();
- EXPECT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+ EXPECT_TRUE(parse_disk_stats(disk_stats_path, &stats));
struct disk_stats old_stats;
memset(&old_stats, 0, sizeof(struct disk_stats));
@@ -92,7 +96,9 @@
TEST(storaged_test, disk_stats) {
struct disk_stats stats = {};
- ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &stats));
+ auto disk_stats_path = get_disk_stats_path();
+ if (disk_stats_path == nullptr) GTEST_SKIP();
+ ASSERT_TRUE(parse_disk_stats(disk_stats_path, &stats));
// every entry of stats (except io_in_flight) should all be greater than 0
for (uint i = 0; i < DISK_STATS_SIZE; ++i) {
@@ -103,7 +109,7 @@
// accumulation of the increments should be the same with the overall increment
struct disk_stats base = {}, tmp = {}, curr, acc = {}, inc[5];
for (uint i = 0; i < 5; ++i) {
- ASSERT_TRUE(parse_disk_stats(DISK_STATS_PATH, &curr));
+ ASSERT_TRUE(parse_disk_stats(disk_stats_path, &curr));
if (i == 0) {
base = curr;
tmp = curr;
@@ -235,9 +241,7 @@
}
TEST(storaged_test, disk_stats_monitor) {
- using android::hardware::health::V2_0::get_health_service;
-
- auto healthService = get_health_service();
+ auto [healthService, hidlHealth] = HealthServicePair::get();
// asserting that there is one file for diskstats
ASSERT_TRUE(healthService != nullptr || access(MMC_DISK_STATS_PATH, R_OK) >= 0 ||
@@ -246,6 +250,13 @@
// testing if detect() will return the right value
disk_stats_monitor dsm_detect{healthService};
ASSERT_TRUE(dsm_detect.enabled());
+
+ // Even if enabled(), healthService may not support disk stats. Check if it is supported.
+ std::vector<aidl::android::hardware::health::DiskStats> halStats;
+ if (healthService->getDiskStats(&halStats).getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ GTEST_SKIP();
+ }
+
// feed monitor with constant perf data for io perf baseline
// using constant perf is reasonable since the functionality of stream_stats
// has already been tested
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 99d9e56..0e916ef 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -105,8 +105,10 @@
"keymint/TrustySharedSecret.cpp",
"keymint/service.cpp",
],
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ ],
shared_libs: [
- "android.hardware.security.keymint-V1-ndk",
"android.hardware.security.secureclock-V1-ndk",
"android.hardware.security.sharedsecret-V1-ndk",
"lib_android_keymaster_keymint_utils",