Merge "Remove salyzyn@ from OWNERS."
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 8fb9697..2f1ca70 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -91,12 +91,19 @@
     name: "libfs_avb_test",
     defaults: ["libfs_avb_host_test_defaults"],
     test_suites: ["general-tests"],
+    test_options: {
+        unit_test: true,
+    },
     static_libs: [
         "libfs_avb_test_util",
     ],
     shared_libs: [
         "libcrypto",
     ],
+    data: [
+        ":avbtool",
+        ":fec",
+    ],
     srcs: [
         "tests/basic_test.cpp",
         "tests/fs_avb_test.cpp",
@@ -108,9 +115,17 @@
     name: "libfs_avb_internal_test",
     defaults: ["libfs_avb_host_test_defaults"],
     test_suites: ["general-tests"],
+    test_options: {
+        unit_test: true,
+    },
     static_libs: [
         "libfs_avb_test_util",
     ],
+    compile_multilib: "first",
+    data: [
+        ":avbtool",
+        ":fec",
+    ],
     srcs: [
         "avb_util.cpp",
         "util.cpp",
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 0addba3..7fa23db 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -98,6 +98,7 @@
     void ValidateMerge();
     void ReadSnapshotDeviceAndValidate();
     void Shutdown();
+    void MergeInterrupt();
 
     std::string snapshot_dev() const { return snapshot_dev_->path(); }
 
@@ -105,7 +106,11 @@
 
   private:
     void SetupImpl();
+
     void MergeImpl();
+    void SimulateDaemonRestart();
+    void StartMerge();
+
     void CreateCowDevice();
     void CreateBaseDevice();
     void InitCowDevice();
@@ -130,7 +135,7 @@
     std::unique_ptr<uint8_t[]> merged_buffer_;
     bool setup_ok_ = false;
     bool merge_ok_ = false;
-    size_t size_ = 1_MiB;
+    size_t size_ = 50_MiB;
     int cow_num_sectors_;
     int total_base_size_;
 };
@@ -154,9 +159,13 @@
 }
 
 void CowSnapuserdTest::Shutdown() {
-    ASSERT_TRUE(client_->StopSnapuserd());
     ASSERT_TRUE(snapshot_dev_->Destroy());
     ASSERT_TRUE(dmuser_dev_->Destroy());
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
+    ASSERT_TRUE(client_->DetachSnapuserd());
 }
 
 bool CowSnapuserdTest::Setup() {
@@ -367,7 +376,7 @@
     return merge_ok_;
 }
 
-void CowSnapuserdTest::MergeImpl() {
+void CowSnapuserdTest::StartMerge() {
     DmTable merge_table;
     ASSERT_TRUE(merge_table.AddTarget(std::make_unique<DmTargetSnapshot>(
             0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
@@ -377,6 +386,11 @@
 
     DeviceMapper& dm = DeviceMapper::Instance();
     ASSERT_TRUE(dm.LoadTableAndActivate("cowsnapuserd-test-dm-snapshot", merge_table));
+}
+
+void CowSnapuserdTest::MergeImpl() {
+    StartMerge();
+    DeviceMapper& dm = DeviceMapper::Instance();
 
     while (true) {
         vector<DeviceMapper::TargetInfo> status;
@@ -405,6 +419,44 @@
     ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
 }
 
+void CowSnapuserdTest::SimulateDaemonRestart() {
+    Shutdown();
+    SetDeviceControlName();
+    StartSnapuserdDaemon();
+    InitCowDevice();
+    CreateDmUserDevice();
+    InitDaemon();
+    CreateSnapshotDevice();
+}
+
+void CowSnapuserdTest::MergeInterrupt() {
+    StartMerge();
+    std::this_thread::sleep_for(4s);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(3s);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(3s);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(1s);
+    SimulateDaemonRestart();
+
+    ASSERT_TRUE(Merge());
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
 TEST(Snapuserd_Test, Snapshot) {
     CowSnapuserdTest harness;
     ASSERT_TRUE(harness.Setup());
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 0a8567f..ff7a727 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -379,6 +379,7 @@
     FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
     FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
+    FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
     friend class SnapshotTest;
     friend class SnapshotUpdateTest;
     friend class FlashAfterUpdateTest;
@@ -441,7 +442,7 @@
     //
     // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes
     // must be a multiple of the sector size (512 bytes).
-    bool CreateSnapshot(LockedFile* lock, SnapshotStatus* status);
+    bool CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator, SnapshotStatus* status);
 
     // |name| should be the base partition name (e.g. "system_a"). Create the
     // backing COW image using the size previously passed to CreateSnapshot().
@@ -717,6 +718,8 @@
     bool PerformInitTransition(InitTransition transition,
                                std::vector<std::string>* snapuserd_argv = nullptr);
 
+    SnapuserdClient* snapuserd_client() const { return snapuserd_client_.get(); }
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index ff0047e..b038527 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -146,6 +146,7 @@
 bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
                      std::string* hash = nullptr);
 bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr);
+std::string HashSnapshot(ISnapshotWriter* writer);
 
 std::optional<std::string> GetHash(const std::string& path);
 
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index ebda430..9329725 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -312,7 +312,8 @@
     return WriteUpdateState(lock.get(), UpdateState::Unverified);
 }
 
-bool SnapshotManager::CreateSnapshot(LockedFile* lock, SnapshotStatus* status) {
+bool SnapshotManager::CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator,
+                                     SnapshotStatus* status) {
     CHECK(lock);
     CHECK(lock->lock_mode() == LOCK_EX);
     CHECK(status);
@@ -353,7 +354,7 @@
     status->set_state(SnapshotState::CREATED);
     status->set_sectors_allocated(0);
     status->set_metadata_sectors(0);
-    status->set_compression_enabled(IsCompressionEnabled());
+    status->set_compression_enabled(cow_creator->compression_enabled);
 
     if (!WriteSnapshotStatus(lock, *status)) {
         PLOG(ERROR) << "Could not write snapshot status: " << status->name();
@@ -2129,10 +2130,6 @@
 bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
     auto& dm = DeviceMapper::Instance();
 
-    if (!EnsureSnapuserdConnected()) {
-        return false;
-    }
-
     auto dm_user_name = GetDmUserCowName(snapshot_name);
     if (dm.GetState(dm_user_name) == DmDeviceState::INVALID) {
         return true;
@@ -2143,6 +2140,9 @@
         return false;
     }
 
+    if (!EnsureSnapuserdConnected()) {
+        return false;
+    }
     if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
         LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
         return false;
@@ -2172,12 +2172,44 @@
         return false;
     }
 
-    if (!UnmapAllSnapshots(lock.get())) {
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock.get(), &snapshots)) {
         return false;
     }
 
-    uint32_t slot = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix());
-    return MapAllPartitions(lock.get(), device_->GetSuperDevice(slot), slot, timeout_ms);
+    const auto& opener = device_->GetPartitionOpener();
+    auto slot_suffix = device_->GetOtherSlotSuffix();
+    auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
+    auto super_device = device_->GetSuperDevice(slot_number);
+    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot_number);
+    if (!metadata) {
+        LOG(ERROR) << "MapAllSnapshots could not read dynamic partition metadata for device: "
+                   << super_device;
+        return false;
+    }
+
+    for (const auto& snapshot : snapshots) {
+        if (!UnmapPartitionWithSnapshot(lock.get(), snapshot)) {
+            LOG(ERROR) << "MapAllSnapshots could not unmap snapshot: " << snapshot;
+            return false;
+        }
+
+        CreateLogicalPartitionParams params = {
+                .block_device = super_device,
+                .metadata = metadata.get(),
+                .partition_name = snapshot,
+                .partition_opener = &opener,
+                .timeout_ms = timeout_ms,
+        };
+        if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount,
+                                      nullptr)) {
+            LOG(ERROR) << "MapAllSnapshots failed to map: " << snapshot;
+            return false;
+        }
+    }
+
+    LOG(INFO) << "MapAllSnapshots succeeded.";
+    return true;
 }
 
 bool SnapshotManager::UnmapAllSnapshots() {
@@ -2584,6 +2616,10 @@
     // these devices.
     AutoDeviceList created_devices;
 
+    bool use_compression = IsCompressionEnabled() &&
+                           manifest.dynamic_partition_metadata().vabc_enabled() &&
+                           !device_->IsRecovery();
+
     PartitionCowCreator cow_creator{
             .target_metadata = target_metadata.get(),
             .target_suffix = target_suffix,
@@ -2592,7 +2628,7 @@
             .current_suffix = current_suffix,
             .update = nullptr,
             .extra_extents = {},
-            .compression_enabled = IsCompressionEnabled(),
+            .compression_enabled = use_compression,
     };
 
     auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
@@ -2744,7 +2780,7 @@
         }
 
         // Store these device sizes to snapshot status file.
-        if (!CreateSnapshot(lock, &cow_creator_ret->snapshot_status)) {
+        if (!CreateSnapshot(lock, cow_creator, &cow_creator_ret->snapshot_status)) {
             return Return::Error();
         }
         created_devices->EmplaceBack<AutoDeleteSnapshot>(this, lock, target_partition->name());
@@ -2857,11 +2893,6 @@
 
 bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                                         std::string* snapshot_path) {
-    if (IsCompressionEnabled()) {
-        LOG(ERROR) << "MapUpdateSnapshot cannot be used in compression mode.";
-        return false;
-    }
-
     auto lock = LockShared();
     if (!lock) return false;
     if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) {
@@ -2870,6 +2901,15 @@
         return false;
     }
 
+    SnapshotStatus status;
+    if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) {
+        return false;
+    }
+    if (status.compression_enabled()) {
+        LOG(ERROR) << "Cannot use MapUpdateSnapshot with compressed snapshots";
+        return false;
+    }
+
     SnapshotPaths paths;
     if (!MapPartitionWithSnapshot(lock.get(), params, SnapshotContext::Update, &paths)) {
         return false;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 4c209ec..95e7d89 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -41,6 +41,7 @@
 
 #include <android/snapshot/snapshot.pb.h>
 #include <libsnapshot/test_helpers.h>
+#include "partition_cow_creator.h"
 #include "utility.h"
 
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
@@ -224,7 +225,7 @@
     }
 
     AssertionResult MapUpdateSnapshot(const std::string& name,
-                                      std::unique_ptr<ICowWriter>* writer) {
+                                      std::unique_ptr<ISnapshotWriter>* writer) {
         TestPartitionOpener opener(fake_super);
         CreateLogicalPartitionParams params{
                 .block_device = fake_super,
@@ -278,6 +279,7 @@
             return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
         }
         if (!(res = DeleteDevice(snapshot + "-base"))) return res;
+        if (!(res = DeleteDevice(snapshot + "-src"))) return res;
         return AssertionSuccess();
     }
 
@@ -318,12 +320,15 @@
 
     // Prepare A/B slot for a partition named "test_partition".
     AssertionResult PrepareOneSnapshot(uint64_t device_size,
-                                       std::unique_ptr<ICowWriter>* writer = nullptr) {
+                                       std::unique_ptr<ISnapshotWriter>* writer = nullptr) {
         lock_ = nullptr;
 
         DeltaArchiveManifest manifest;
 
-        auto group = manifest.mutable_dynamic_partition_metadata()->add_groups();
+        auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
+        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+
+        auto group = dynamic_partition_metadata->add_groups();
         group->set_name("group");
         group->set_size(device_size * 2);
         group->add_partition_names("test_partition");
@@ -416,13 +421,16 @@
 TEST_F(SnapshotTest, CreateSnapshot) {
     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(), &status));
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::vector<std::string> snapshots;
@@ -437,6 +445,7 @@
         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_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
@@ -447,13 +456,16 @@
 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(), &status));
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device;
@@ -500,7 +512,7 @@
 
     static const uint64_t kDeviceSize = 1024 * 1024;
 
-    std::unique_ptr<ICowWriter> writer;
+    std::unique_ptr<ISnapshotWriter> writer;
     ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
 
     // Release the lock.
@@ -816,11 +828,14 @@
 
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
+        auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
+        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+
         // 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_ = manifest_.mutable_dynamic_partition_metadata()->add_groups();
+        group_ = dynamic_partition_metadata->add_groups();
         group_->set_name("group");
         group_->set_size(kGroupSize);
         group_->add_partition_names("sys");
@@ -934,7 +949,7 @@
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
         if (IsCompressionEnabled()) {
-            std::unique_ptr<ICowWriter> writer;
+            std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
             std::string path;
@@ -944,7 +959,7 @@
 
     AssertionResult WriteSnapshotAndHash(const std::string& name) {
         if (IsCompressionEnabled()) {
-            std::unique_ptr<ICowWriter> writer;
+            std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
                 return res;
@@ -973,6 +988,42 @@
                                   << ", 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) {
@@ -1081,8 +1132,132 @@
     // 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 that shrinking and growing partitions at the same time is handled
+// correctly in VABC.
+TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
+    // 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));
+    }
+
+    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), "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());
+
+    // 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))
@@ -1803,6 +1978,13 @@
 
     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"));
+
     // 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);
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 9ad4a1a..ceba8ab 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -96,7 +96,7 @@
 // it will be de-compressed.
 bool Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) {
     if (!reader_->ReadData(*cow_op, &bufsink_)) {
-        SNAP_LOG(ERROR) << "ReadData failed for chunk: " << cow_op->new_block;
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
         return false;
     }
 
@@ -113,7 +113,8 @@
     // if the successive blocks are contiguous.
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SIZE,
                                           cow_op->source * BLOCK_SIZE)) {
-        SNAP_LOG(ERROR) << "Copy-op failed. Read from backing store at: " << cow_op->source;
+        SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+                         << "at block :" << cow_op->source;
         return false;
     }
 
@@ -160,7 +161,7 @@
                     << " Aligned sector: " << it->second;
 
     if (!ProcessCowOp(it->second)) {
-        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed";
+        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
         return -1;
     }
 
@@ -377,16 +378,21 @@
             CHECK(cow_de->new_chunk == 0);
             break;
         } else {
-            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata";
-            SNAP_LOG(ERROR) << "merged_de-old-chunk: " << merged_de->old_chunk;
-            SNAP_LOG(ERROR) << "merged_de-new-chunk: " << merged_de->new_chunk;
-            SNAP_LOG(ERROR) << "cow_de-old-chunk: " << cow_de->old_chunk;
-            SNAP_LOG(ERROR) << "cow_de-new-chunk: " << cow_de->new_chunk;
+            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
+                            << " merged_de-old-chunk: " << merged_de->old_chunk
+                            << " merged_de-new-chunk: " << merged_de->new_chunk
+                            << " cow_de-old-chunk: " << cow_de->old_chunk
+                            << " cow_de-new-chunk: " << cow_de->new_chunk
+                            << " unmerged_exceptions: " << unmerged_exceptions
+                            << " merged_ops_cur_iter: " << merged_ops_cur_iter
+                            << " offset: " << offset;
             return -1;
         }
     }
 
     if (*copy_op) {
+        SNAP_LOG(ERROR) << "Invalid batch merge of copy ops: merged_ops_cur_iter: "
+                        << merged_ops_cur_iter;
         CHECK(merged_ops_cur_iter == 1);
     }
     return merged_ops_cur_iter;
@@ -446,7 +452,7 @@
     header.num_merge_ops += merged_ops_cur_iter;
     reader_->UpdateMergeProgress(merged_ops_cur_iter);
     if (!writer_->CommitMerge(merged_ops_cur_iter, copy_op)) {
-        SNAP_LOG(ERROR) << "CommitMerge failed...";
+        SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << merged_ops_cur_iter;
         return false;
     }
 
@@ -661,7 +667,7 @@
 bool Snapuserd::WriteDmUserPayload(size_t size) {
     if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
                                    sizeof(struct dm_user_header) + size)) {
-        SNAP_PLOG(ERROR) << "Write to dm-user failed";
+        SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size;
         return false;
     }
 
@@ -670,7 +676,7 @@
 
 bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
     if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
-        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed";
+        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
         return false;
     }
 
@@ -808,7 +814,8 @@
                 ret = ReadData(sector + num_sectors_read, read_size);
                 if (ret < 0) {
                     SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
-                                    << "Sector: " << header->sector;
+                                    << " Sector: " << (sector + num_sectors_read)
+                                    << " size: " << read_size << " header-len: " << header->len;
                     header->type = DM_USER_RESP_ERROR;
                 } else {
                     SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 6104c82..e3e3af8 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -23,6 +23,7 @@
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 #include <openssl/sha.h>
+#include <payload_consumer/file_descriptor.h>
 
 namespace android {
 namespace snapshot {
@@ -169,6 +170,37 @@
     return true;
 }
 
+std::string HashSnapshot(ISnapshotWriter* writer) {
+    auto reader = writer->OpenReader();
+    if (!reader) {
+        return {};
+    }
+
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    uint64_t remaining = reader->BlockDevSize();
+    char buffer[4096];
+    while (remaining) {
+        size_t to_read =
+                static_cast<size_t>(std::min(remaining, static_cast<uint64_t>(sizeof(buffer))));
+        ssize_t read = reader->Read(&buffer, to_read);
+        if (read <= 0) {
+            if (read < 0) {
+                LOG(ERROR) << "Failed to read from snapshot writer";
+                return {};
+            }
+            break;
+        }
+        SHA256_Update(&ctx, buffer, to_read);
+        remaining -= static_cast<size_t>(read);
+    }
+
+    uint8_t out[32];
+    SHA256_Final(out, &ctx);
+    return ToHexString(out, sizeof(out));
+}
+
 std::optional<std::string> GetHash(const std::string& path) {
     std::string content;
     if (!android::base::ReadFileToString(path, &content, true)) {
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index dda214e..4a97f81 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -73,6 +73,7 @@
 
 message DynamicPartitionMetadata {
     repeated DynamicPartitionGroup groups = 1;
+    optional bool vabc_enabled = 3;
 }
 
 message DeltaArchiveManifest {
diff --git a/init/Android.bp b/init/Android.bp
index cd295cf..06ecc59 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -233,6 +233,7 @@
             ],
         },
     },
+    visibility: ["//packages/modules/Virtualization/microdroid"],
 }
 
 // Tests
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 7c46281..7307237 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -253,6 +253,22 @@
 
     if (!InitDevices()) return false;
 
+    // Mount /metadata before creating logical partitions, since we need to
+    // know whether a snapshot merge is in progress.
+    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
+        return entry.mount_point == "/metadata";
+    });
+    if (metadata_partition != fstab_.end()) {
+        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
+            // Copies DSU AVB keys from the ramdisk to /metadata.
+            // Must be done before the following TrySwitchSystemAsRoot().
+            // Otherwise, ramdisk will be inaccessible after switching root.
+            CopyDsuAvbKeys();
+        }
+    }
+
+    if (!CreateLogicalPartitions()) return false;
+
     if (!MountPartitions()) return false;
 
     return true;
@@ -505,22 +521,6 @@
 }
 
 bool FirstStageMount::MountPartitions() {
-    // Mount /metadata before creating logical partitions, since we need to
-    // know whether a snapshot merge is in progress.
-    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
-        return entry.mount_point == "/metadata";
-    });
-    if (metadata_partition != fstab_.end()) {
-        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
-            // Copies DSU AVB keys from the ramdisk to /metadata.
-            // Must be done before the following TrySwitchSystemAsRoot().
-            // Otherwise, ramdisk will be inaccessible after switching root.
-            CopyDsuAvbKeys();
-        }
-    }
-
-    if (!CreateLogicalPartitions()) return false;
-
     if (!TrySwitchSystemAsRoot()) return false;
 
     if (!SkipMountingPartitions(&fstab_)) return false;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 923d769..331255b 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -16,6 +16,7 @@
 
 #include "ueventd.h"
 
+#include <android/api-level.h>
 #include <ctype.h>
 #include <dirent.h>
 #include <fcntl.h>
@@ -266,6 +267,17 @@
     LOG(INFO) << "Coldboot took " << cold_boot_timer.duration().count() / 1000.0f << " seconds";
 }
 
+static UeventdConfiguration GetConfiguration() {
+    // TODO: Remove these legacy paths once Android S is no longer supported.
+    if (android::base::GetIntProperty("ro.product.first_api_level", 10000) <= __ANDROID_API_S__) {
+        auto hardware = android::base::GetProperty("ro.hardware", "");
+        return ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc", "/odm/ueventd.rc",
+                            "/ueventd." + hardware + ".rc"});
+    }
+
+    return ParseConfig({"/system/etc/ueventd.rc"});
+}
+
 int ueventd_main(int argc, char** argv) {
     /*
      * init sets the umask to 077 for forked processes. We need to
@@ -283,7 +295,7 @@
 
     std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;
 
-    auto ueventd_configuration = ParseConfig("/system/etc/ueventd.rc");
+    auto ueventd_configuration = GetConfiguration();
 
     uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(
             std::move(ueventd_configuration.dev_permissions),
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 2605158..cab988b 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -230,7 +230,7 @@
     return {};
 }
 
-UeventdConfiguration ParseConfig(const std::string& config) {
+UeventdConfiguration ParseConfig(const std::vector<std::string>& configs) {
     Parser parser;
     UeventdConfiguration ueventd_configuration;
 
@@ -260,7 +260,9 @@
                                std::bind(ParseEnabledDisabledLine, _1,
                                          &ueventd_configuration.enable_parallel_restorecon));
 
-    parser.ParseConfig(config);
+    for (const auto& config : configs) {
+        parser.ParseConfig(config);
+    }
 
     return ueventd_configuration;
 }
diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h
index 2672626..eaafa5a 100644
--- a/init/ueventd_parser.h
+++ b/init/ueventd_parser.h
@@ -36,7 +36,7 @@
     bool enable_parallel_restorecon = false;
 };
 
-UeventdConfiguration ParseConfig(const std::string& config);
+UeventdConfiguration ParseConfig(const std::vector<std::string>& configs);
 
 }  // namespace init
 }  // namespace android
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index d669ebe..2bf48fc 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -131,13 +131,25 @@
     return StringPrintf("%s/uid_%d/pid_%d", cgroup, uid, pid);
 }
 
-static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid) {
-    int ret;
-
+static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
+    int ret = 0;
     auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
-    ret = rmdir(uid_pid_path.c_str());
-
     auto uid_path = ConvertUidToPath(cgroup, uid);
+
+    if (retries == 0) {
+        retries = 1;
+    }
+
+    while (retries--) {
+        ret = rmdir(uid_pid_path.c_str());
+        if (!ret || errno != EBUSY) break;
+        std::this_thread::sleep_for(5ms);
+    }
+
+    // With the exception of boot or shutdown, system uid_ folders are always populated. Spinning
+    // here would needlessly delay most pid removals. Additionally, once empty a uid_ cgroup won't
+    // have processes hanging on it (we've already spun for all its pid_), so there's no need to
+    // spin anyway.
     rmdir(uid_path.c_str());
 
     return ret;
@@ -176,7 +188,7 @@
     std::vector<std::string> cgroups;
     std::string path;
 
-    if (CgroupGetControllerPath("cpuacct", &path)) {
+    if (CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &path)) {
         cgroups.push_back(path);
     }
     if (CgroupGetControllerPath("memory", &path)) {
@@ -212,19 +224,49 @@
     }
 }
 
+/**
+ * Process groups are primarily created by the Zygote, meaning that uid/pid groups are created by
+ * the user root. Ownership for the newly created cgroup and all of its files must thus be
+ * transferred for the user/group passed as uid/gid before system_server can properly access them.
+ */
 static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
     if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
         return false;
     }
 
-    if (chown(path.c_str(), uid, gid) == -1) {
-        int saved_errno = errno;
-        rmdir(path.c_str());
-        errno = saved_errno;
-        return false;
+    auto dir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(path.c_str()), closedir);
+
+    if (dir == NULL) {
+        PLOG(ERROR) << "opendir failed for " << path;
+        goto err;
+    }
+
+    struct dirent* dir_entry;
+    while ((dir_entry = readdir(dir.get()))) {
+        if (!strcmp("..", dir_entry->d_name)) {
+            continue;
+        }
+
+        std::string file_path = path + "/" + dir_entry->d_name;
+
+        if (lchown(file_path.c_str(), uid, gid) < 0) {
+            PLOG(ERROR) << "lchown failed for " << file_path;
+            goto err;
+        }
+
+        if (fchmodat(AT_FDCWD, file_path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+            PLOG(ERROR) << "fchmodat failed for " << file_path;
+            goto err;
+        }
     }
 
     return true;
+err:
+    int saved_errno = errno;
+    rmdir(path.c_str());
+    errno = saved_errno;
+
+    return false;
 }
 
 // Returns number of processes killed on success
@@ -302,17 +344,9 @@
 
 static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
                             int* max_processes) {
-    std::string cpuacct_path;
-    std::string memory_path;
-
-    CgroupGetControllerPath("cpuacct", &cpuacct_path);
-    CgroupGetControllerPath("memory", &memory_path);
-    memory_path += "/apps";
-
-    const char* cgroup =
-            (!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK))
-                    ? cpuacct_path.c_str()
-                    : memory_path.c_str();
+    std::string hierarchy_root_path;
+    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+    const char* cgroup = hierarchy_root_path.c_str();
 
     std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
@@ -355,7 +389,17 @@
             LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
                       << " in " << static_cast<int>(ms) << "ms";
         }
-        return RemoveProcessGroup(cgroup, uid, initialPid);
+
+        int err = RemoveProcessGroup(cgroup, uid, initialPid, retries);
+
+        if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+            std::string memory_path;
+            CgroupGetControllerPath("memory", &memory_path);
+            memory_path += "/apps";
+            if (RemoveProcessGroup(memory_path.c_str(), uid, initialPid, retries)) return -1;
+        }
+
+        return err;
     } else {
         if (retries > 0) {
             LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
@@ -374,15 +418,7 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
 }
 
-int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
-    std::string cgroup;
-    if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) {
-        CgroupGetControllerPath("memory", &cgroup);
-        cgroup += "/apps";
-    } else {
-        CgroupGetControllerPath("cpuacct", &cgroup);
-    }
-
+static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
 
     if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
@@ -408,6 +444,27 @@
     return ret;
 }
 
+int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
+    std::string cgroup;
+
+    if (memControl && !UsePerAppMemcg()) {
+        PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
+        return -EINVAL;
+    }
+
+    if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+        CgroupGetControllerPath("memory", &cgroup);
+        cgroup += "/apps";
+        int ret = createProcessGroupInternal(uid, initialPid, cgroup);
+        if (ret != 0) {
+            return ret;
+        }
+    }
+
+    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
+    return createProcessGroupInternal(uid, initialPid, cgroup);
+}
+
 static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
         PLOG(ERROR) << "Memcg is not mounted.";
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 5b7a28a..7bcb94b 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -15,11 +15,6 @@
       "GID": "system"
     },
     {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    },
-    {
       "Controller": "cpuset",
       "Path": "/dev/cpuset",
       "Mode": "0755",
diff --git a/libprocessgroup/profiles/cgroups.recovery.json b/libprocessgroup/profiles/cgroups.recovery.json
index f0bf5fd..2c63c08 100644
--- a/libprocessgroup/profiles/cgroups.recovery.json
+++ b/libprocessgroup/profiles/cgroups.recovery.json
@@ -1,9 +1,2 @@
 {
-  "Cgroups": [
-    {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    }
-  ]
 }
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index aebcd5f..e9293b5 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -1,6 +1,5 @@
-import /vendor/ueventd.rc
-import /odm/ueventd.rc
-import /ueventd.${ro.hardware}.rc
+import /vendor/etc/ueventd.rc
+import /odm/etc/ueventd.rc
 
 firmware_directories /etc/firmware/ /odm/firmware/ /vendor/firmware/ /firmware/image/
 uevent_socket_rcvbuf_size 16M