Merge "Make RemoveCgroup idempotent" into main
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 87c4cd2..a6d8226 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -2825,7 +2825,8 @@
     }
 
     prev_file = match[1];
-    unwindstack::Elf elf(unwindstack::Memory::CreateFileMemory(prev_file, 0).release());
+    auto elf_memory = unwindstack::Memory::CreateFileMemory(prev_file, 0);
+    unwindstack::Elf elf(elf_memory);
     if (!elf.Init() || !elf.valid()) {
       // Skipping invalid elf files.
       continue;
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index eed81fc..e44dc10 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -28,7 +28,6 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <async_safe/log.h>
 #include <bionic/macros.h>
 
 #include "tombstone.pb.h"
@@ -138,7 +137,8 @@
       break;
 
     default:
-      async_safe_fatal("unknown architecture");
+      CBL("Unknown architecture %d printing thread registers", tombstone.arch());
+      return;
   }
 
   for (const auto& reg : thread.registers()) {
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index d55f8d3..faea5eb 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -828,7 +828,14 @@
                   << ",type=" << entry.fs_type << ", gc_allowance=" << gc_allowance << "%)=" << ret
                   << "(" << save_errno << ")";
         }
-        ret = mount(source.c_str(), target.c_str(), entry.fs_type.c_str(), mountflags,
+
+        // Let's get the raw dm target, if it's a symlink, since some existing applications
+        // rely on /proc/mounts to find the userdata's dm target path. Don't break that assumption.
+        std::string real_source;
+        if (!android::base::Realpath(source, &real_source)) {
+            real_source = source;
+        }
+        ret = mount(real_source.c_str(), target.c_str(), entry.fs_type.c_str(), mountflags,
                     opts.c_str());
         save_errno = errno;
         if (try_f2fs_gc_allowance) gc_allowance += 10;
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
index ae7ed4c..cdbac00 100644
--- a/fs_mgr/fs_mgr_overlayfs_mount.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -34,6 +34,7 @@
 #include <android-base/file.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <ext4_utils/ext4_utils.h>
@@ -58,6 +59,9 @@
 constexpr char kCacheMountPoint[] = "/cache";
 constexpr char kPhysicalDevice[] = "/dev/block/by-name/";
 
+// Mount tree to temporarily hold references to submounts.
+constexpr char kMoveMountTempDir[] = "/dev/remount";
+
 constexpr char kLowerdirOption[] = "lowerdir=";
 constexpr char kUpperdirOption[] = "upperdir=";
 constexpr char kWorkdirOption[] = "workdir=";
@@ -284,10 +288,6 @@
     if (ret) {
         PERROR << "__mount(target=" << mount_point
                << ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret;
-        // If "/system" doesn't look like a mountpoint, retry with "/".
-        if (errno == EINVAL && mount_point == "/system") {
-            return fs_mgr_overlayfs_set_shared_mount("/", shared_flag);
-        }
         return false;
     }
     return true;
@@ -374,24 +374,23 @@
     return info;
 }
 
-static bool fs_mgr_overlayfs_mount(const FstabEntry& entry) {
-    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
-    const auto options = fs_mgr_get_overlayfs_options(entry);
+static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) {
+    const auto mount_point = fs_mgr_mount_point(fstab_entry.mount_point);
+    const auto options = fs_mgr_get_overlayfs_options(fstab_entry);
     if (options.empty()) return false;
 
-    auto retval = true;
-
     struct MoveEntry {
         std::string mount_point;
         std::string dir;
         bool shared_flag;
     };
-
     std::vector<MoveEntry> moved_mounts;
-    auto parent_private = false;
-    auto parent_made_private = false;
-    auto dev_private = false;
-    auto dev_made_private = false;
+
+    bool retval = true;
+    bool move_dir_shared = true;
+    bool parent_shared = true;
+    bool root_shared = true;
+    bool root_made_private = false;
 
     // There could be multiple mount entries with the same mountpoint.
     // Group these entries together with stable_sort, and keep only the last entry of a group.
@@ -411,18 +410,32 @@
     // mountinfo is reversed twice, so still is in lexical sorted order.
 
     for (const auto& entry : mountinfo) {
-        if ((entry.mount_point == mount_point) && !entry.shared_flag) {
-            parent_private = true;
+        if (entry.mount_point == kMoveMountTempDir) {
+            move_dir_shared = entry.shared_flag;
         }
-        if ((entry.mount_point == "/dev") && !entry.shared_flag) {
-            dev_private = true;
+        if (entry.mount_point == mount_point ||
+            (mount_point == "/system" && entry.mount_point == "/")) {
+            parent_shared = entry.shared_flag;
         }
+        if (entry.mount_point == "/") {
+            root_shared = entry.shared_flag;
+        }
+    }
+
+    // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any
+    // submount in to or out of it.
+    if (move_dir_shared) {
+        mountinfo.clear();
     }
 
     // Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE.
     // This could happen if its parent mount is remounted later.
-    if (!parent_private) {
-        parent_made_private = fs_mgr_overlayfs_set_shared_mount(mount_point, false);
+    if (!fs_mgr_overlayfs_set_shared_mount(mount_point, false)) {
+        // If failed to set "/system" mount type, it might be due to "/system" not being a valid
+        // mountpoint after switch root. Retry with "/" in this case.
+        if (errno == EINVAL && mount_point == "/system") {
+            root_made_private = fs_mgr_overlayfs_set_shared_mount("/", false);
+        }
     }
 
     for (const auto& entry : mountinfo) {
@@ -440,8 +453,8 @@
         // mountinfo is in lexical order, so no need to worry about |entry| being a parent mount of
         // entries of |moved_mounts|.
 
-        // use as the bound directory in /dev.
-        MoveEntry new_entry{entry.mount_point, "/dev/TemporaryDir-XXXXXX", entry.shared_flag};
+        MoveEntry new_entry{entry.mount_point, kMoveMountTempDir + "/TemporaryDir-XXXXXX"s,
+                            entry.shared_flag};
         {
             AutoSetFsCreateCon createcon;
             auto new_context = fs_mgr_get_context(entry.mount_point);
@@ -497,10 +510,6 @@
 
     // Move submounts back.
     for (const auto& entry : moved_mounts) {
-        if (!dev_private && !dev_made_private) {
-            dev_made_private = fs_mgr_overlayfs_set_shared_mount("/dev", false);
-        }
-
         if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) {
             retval = false;
         } else if (entry.shared_flag &&
@@ -509,12 +518,13 @@
         }
         rmdir(entry.dir.c_str());
     }
-    if (dev_made_private) {
-        fs_mgr_overlayfs_set_shared_mount("/dev", true);
-    }
-    if (parent_made_private) {
+    // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED.
+    if (parent_shared) {
         fs_mgr_overlayfs_set_shared_mount(mount_point, true);
     }
+    if (root_shared && root_made_private) {
+        fs_mgr_overlayfs_set_shared_mount("/", true);
+    }
 
     return retval;
 }
@@ -730,6 +740,25 @@
     if (!OverlayfsSetupAllowed()) {
         return false;
     }
+
+    // Ensure kMoveMountTempDir is standalone mount tree with 'private' propagation by bind mounting
+    // to itself and set to MS_PRIVATE.
+    // Otherwise mounts moved in to it would have their propagation type changed unintentionally.
+    // Section 5d, https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
+    if (!fs_mgr_overlayfs_already_mounted(kMoveMountTempDir, false)) {
+        if (mkdir(kMoveMountTempDir, 0755) && errno != EEXIST) {
+            PERROR << "mkdir " << kMoveMountTempDir;
+        }
+        if (mount(kMoveMountTempDir, kMoveMountTempDir, nullptr, MS_BIND, nullptr)) {
+            PERROR << "bind mount " << kMoveMountTempDir;
+        }
+    }
+    fs_mgr_overlayfs_set_shared_mount(kMoveMountTempDir, false);
+    android::base::ScopeGuard umountDir([]() {
+        umount(kMoveMountTempDir);
+        rmdir(kMoveMountTempDir);
+    });
+
     auto ret = true;
     auto scratch_can_be_mounted = !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false);
     for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) {
@@ -742,7 +771,7 @@
             scratch_can_be_mounted = false;
             TryMountScratch();
         }
-        ret &= fs_mgr_overlayfs_mount(entry);
+        ret &= fs_mgr_overlayfs_mount_one(entry);
     }
     return ret;
 }
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index 3a9ed9b..e261aa3 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -612,8 +612,12 @@
 
 std::optional<std::string> ExtractBlockDeviceName(const std::string& path) {
     static constexpr std::string_view kDevBlockPrefix("/dev/block/");
-    if (android::base::StartsWith(path, kDevBlockPrefix)) {
-        return path.substr(kDevBlockPrefix.length());
+    std::string real_path;
+    if (!android::base::Realpath(path, &real_path)) {
+        real_path = path;
+    }
+    if (android::base::StartsWith(real_path, kDevBlockPrefix)) {
+        return real_path.substr(kDevBlockPrefix.length());
     }
     return {};
 }
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 3e7ecc6..22c475f 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -52,8 +52,8 @@
 
 static constexpr uint64_t kSectorSize = 512;
 
-// Returns `path` without /dev/block prefix if and only if `path` starts with
-// that prefix.
+// Returns `path` without /dev/block prefix if `path` starts with that prefix.
+// Or, if `path` is a symlink, do the same with its real path.
 std::optional<std::string> ExtractBlockDeviceName(const std::string& path);
 
 // This interface is for testing purposes. See DeviceMapper proper for what these methods do.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 8917116..9e6cfea 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -105,10 +105,12 @@
 static constexpr uint8_t kNumResumePoints = 4;
 
 struct CowHeaderV3 : public CowHeader {
-    // Location of sequence buffer in COW.
-    uint64_t sequence_buffer_offset;
+    // Number of sequence data stored (each of which is a 32 byte integer)
+    uint64_t sequence_data_count;
+    // number of currently written resume points
+    uint32_t resume_point_count;
     // Size, in bytes, of the CowResumePoint buffer.
-    uint32_t resume_buffer_size;
+    uint32_t resume_point_max;
     // Number of CowOperationV3 structs in the operation buffer, currently and total
     // region size.
     uint32_t op_count;
@@ -230,6 +232,19 @@
     return op.source_info & kCowOpSourceInfoDataMask;
 }
 
+static constexpr off_t GetOpOffset(uint32_t op_index, const CowHeaderV3 header) {
+    return header.prefix.header_size + header.buffer_size +
+           (header.resume_point_max * sizeof(ResumePoint)) + (op_index * sizeof(CowOperationV3));
+}
+static constexpr off_t GetDataOffset(const CowHeaderV3 header) {
+    return header.prefix.header_size + header.buffer_size +
+           (header.resume_point_max * sizeof(ResumePoint)) +
+           header.op_count_max * sizeof(CowOperation);
+}
+static constexpr off_t GetResumeOffset(const CowHeaderV3 header) {
+    return header.prefix.header_size + header.buffer_size;
+}
+
 struct CowFooter {
     CowFooterOperation op;
     uint8_t unused[64];
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
index d411ab9..8e0f190 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
@@ -75,14 +75,14 @@
 }
 
 bool CowParserV3::ReadResumeBuffer(borrowed_fd fd) {
-    resume_points_ = std::make_shared<std::vector<ResumePoint>>(header_.resume_buffer_size);
+    resume_points_ = std::make_shared<std::vector<ResumePoint>>(header_.resume_point_count);
 
     return android::base::ReadFullyAtOffset(fd, resume_points_->data(),
-                                            header_.resume_buffer_size * sizeof(ResumePoint),
-                                            header_.prefix.header_size + header_.buffer_size);
+                                            header_.resume_point_count * sizeof(ResumePoint),
+                                            GetResumeOffset(header_));
 }
 
-std::optional<uint32_t> CowParserV3::FindResumeOp(const uint32_t label) {
+std::optional<uint32_t> CowParserV3::FindResumeOp(const uint64_t label) {
     for (auto& resume_point : *resume_points_) {
         if (resume_point.label == label) {
             return resume_point.op_index;
@@ -94,18 +94,12 @@
     return std::nullopt;
 }
 
-off_t CowParserV3::GetDataOffset() const {
-    return sizeof(CowHeaderV3) + header_.buffer_size +
-           header_.resume_buffer_size * sizeof(ResumePoint) +
-           header_.op_count_max * sizeof(CowOperation);
-}
-
 bool CowParserV3::ParseOps(borrowed_fd fd, const uint32_t op_index) {
     ops_ = std::make_shared<std::vector<CowOperationV3>>();
     ops_->resize(op_index);
 
-    const off_t offset = header_.prefix.header_size + header_.buffer_size +
-                         header_.resume_buffer_size * sizeof(ResumePoint);
+    // read beginning of operation buffer -> so op_index = 0
+    const off_t offset = GetOpOffset(0, header_);
     if (!android::base::ReadFullyAtOffset(fd, ops_->data(), ops_->size() * sizeof(CowOperationV3),
                                           offset)) {
         PLOG(ERROR) << "read ops failed";
@@ -113,7 +107,7 @@
     }
 
     // fill out mapping of XOR op data location
-    uint64_t data_pos = GetDataOffset();
+    uint64_t data_pos = GetDataOffset(header_);
 
     xor_data_loc_ = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
index dceb815..afc01af 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
@@ -49,8 +49,7 @@
 
   private:
     bool ParseOps(android::base::borrowed_fd fd, const uint32_t op_index);
-    std::optional<uint32_t> FindResumeOp(const uint32_t label);
-    off_t GetDataOffset() const;
+    std::optional<uint32_t> FindResumeOp(const uint64_t label);
     CowHeaderV3 header_ = {};
     std::shared_ptr<std::vector<CowOperationV3>> ops_;
     bool ReadResumeBuffer(android::base::borrowed_fd fd);
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index c41e07c..9f857ee 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -482,5 +482,43 @@
     header = reader.header_v3();
     ASSERT_EQ(header.op_count, 15);
 }
+
+TEST_F(CowTestV3, BufferMetadataSyncTest) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    /*
+    Header metadafields
+    sequence_data_count = 0;
+    resume_point_count = 0;
+    resume_point_max = 4;
+    */
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto header = reader.header_v3();
+    ASSERT_EQ(header.sequence_data_count, 0);
+    ASSERT_EQ(header.resume_point_count, 0);
+    ASSERT_EQ(header.resume_point_max, 4);
+
+    writer->AddLabel(0);
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    header = reader.header_v3();
+    ASSERT_EQ(header.sequence_data_count, 0);
+    ASSERT_EQ(header.resume_point_count, 1);
+    ASSERT_EQ(header.resume_point_max, 4);
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    header = reader.header_v3();
+
+    /*
+    Header metadafields
+    sequence_data_count = 1;
+    resume_point_count = 0;
+    resume_point_max = 4;
+    */
+}
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index 89fd903..81ccea9 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -77,8 +77,9 @@
     // v3 specific fields
     // WIP: not quite sure how some of these are calculated yet, assuming buffer_size is determined
     // during COW size estimation
-    header_.sequence_buffer_offset = 0;
-    header_.resume_buffer_size = kNumResumePoints;
+    header_.sequence_data_count = 0;
+    header_.resume_point_count = 0;
+    header_.resume_point_max = kNumResumePoints;
     header_.op_count = 0;
     header_.op_count_max = 0;
     header_.compression_algorithm = kCowCompressNone;
@@ -99,6 +100,7 @@
         return false;
     }
     header_.compression_algorithm = *algorithm;
+    header_.op_count_max = options_.op_count_max;
 
     if (parts.size() > 1) {
         if (!android::base::ParseUint(parts[1], &compression_.compression_level)) {
@@ -162,14 +164,14 @@
             return false;
         }
     }
-    header_.op_count_max = options_.op_count_max;
+
     resume_points_ = std::make_shared<std::vector<ResumePoint>>();
 
     if (!Sync()) {
         LOG(ERROR) << "Header sync failed";
         return false;
     }
-    next_data_pos_ = GetDataOffset();
+    next_data_pos_ = GetDataOffset(header_);
     return true;
 }
 
@@ -191,7 +193,7 @@
 
     resume_points_ = parser.resume_points();
     options_.block_size = header_.block_size;
-    next_data_pos_ = GetDataOffset();
+    next_data_pos_ = GetDataOffset(header_);
 
     TranslatedCowOps ops;
     parser.Translate(&ops);
@@ -296,20 +298,21 @@
             resume_points_->end());
 
     resume_points_->push_back({label, header_.op_count});
+    header_.resume_point_count++;
     // remove the oldest resume point if resume_buffer is full
-    while (resume_points_->size() > header_.resume_buffer_size) {
+    while (resume_points_->size() > header_.resume_point_max) {
         resume_points_->erase(resume_points_->begin());
     }
 
-    CHECK_LE(resume_points_->size(), header_.resume_buffer_size);
+    CHECK_LE(resume_points_->size(), header_.resume_point_max);
 
     if (!android::base::WriteFullyAtOffset(fd_, resume_points_->data(),
                                            resume_points_->size() * sizeof(ResumePoint),
-                                           GetResumeOffset())) {
+                                           GetResumeOffset(header_))) {
         PLOG(ERROR) << "writing resume buffer failed";
         return false;
     }
-    return Sync();
+    return Finalize();
 }
 
 bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) {
@@ -331,7 +334,7 @@
         return false;
     }
 
-    const off_t offset = GetOpOffset(header_.op_count);
+    const off_t offset = GetOpOffset(header_.op_count, header_);
     if (!android::base::WriteFullyAtOffset(fd_, &op, sizeof(op), offset)) {
         PLOG(ERROR) << "write failed for " << op << " at " << offset;
         return false;
@@ -360,9 +363,7 @@
 }
 
 uint64_t CowWriterV3::GetCowSize() {
-    LOG(ERROR) << __LINE__ << " " << __FILE__
-               << " <- Get Cow Size function here should never be called";
-    return 0;
+    return next_data_pos_;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 8a2bd2c..3dfc33c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -49,19 +49,6 @@
                     uint16_t offset, uint8_t type);
     bool CompressBlocks(size_t num_blocks, const void* data);
 
-    off_t GetOpOffset(uint32_t op_index) const {
-        CHECK_LT(op_index, header_.op_count_max);
-        return header_.prefix.header_size + header_.buffer_size +
-               (header_.resume_buffer_size * sizeof(ResumePoint)) +
-               (op_index * sizeof(CowOperationV3));
-    }
-    off_t GetDataOffset() const {
-        return sizeof(CowHeaderV3) + header_.buffer_size +
-               (header_.resume_buffer_size * sizeof(ResumePoint)) +
-               header_.op_count_max * sizeof(CowOperation);
-    }
-    off_t GetResumeOffset() const { return sizeof(CowHeaderV3) + header_.buffer_size; }
-
   private:
     CowHeaderV3 header_{};
     CowCompression compression_;
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index c639e43..e91e3b7 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -4339,32 +4339,30 @@
 }
 
 bool SnapshotManager::IsUserspaceSnapshotUpdateInProgress() {
-    auto slot = GetCurrentSlot();
-    if (slot == Slot::Target) {
-        // Merge in-progress
-        if (IsSnapuserdRequired()) {
+    // We cannot grab /metadata/ota lock here as this
+    // is in reboot path. See b/308900853
+    //
+    // Check if any of the partitions are mounted
+    // off dm-user block device. If so, then we are certain
+    // that OTA update in progress.
+    auto current_suffix = device_->GetSlotSuffix();
+    auto& dm = DeviceMapper::Instance();
+    auto dm_block_devices = dm.FindDmPartitions();
+    if (dm_block_devices.empty()) {
+        LOG(ERROR) << "No dm-enabled block device is found.";
+        return false;
+    }
+    for (auto& partition : dm_block_devices) {
+        std::string partition_name = partition.first + current_suffix;
+        DeviceMapper::TargetInfo snap_target;
+        if (!GetSingleTarget(partition_name, TableQuery::Status, &snap_target)) {
+            return false;
+        }
+        auto type = DeviceMapper::GetTargetType(snap_target.spec);
+        if (type == "user") {
             return true;
         }
     }
-
-    // Let's check more deeper to see if snapshots are mounted
-    auto lock = LockExclusive();
-    if (!lock) {
-        return false;
-    }
-
-    std::vector<std::string> snapshots;
-    if (!ListSnapshots(lock.get(), &snapshots)) {
-        return false;
-    }
-
-    for (const auto& snapshot : snapshots) {
-        // Active snapshot and daemon is alive
-        if (IsSnapshotDevice(snapshot) && EnsureSnapuserdConnected(2s)) {
-            return true;
-        }
-    }
-
     return false;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
index d979e20..ffd7a4b 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -51,11 +51,10 @@
 std::shared_ptr<HandlerThread> SnapshotHandlerManager::AddHandler(
         const std::string& misc_name, const std::string& cow_device_path,
         const std::string& backing_device, const std::string& base_path_merge,
-        std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring,
-        bool perform_verification) {
+        std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring) {
     auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
                                                        base_path_merge, opener, num_worker_threads,
-                                                       use_iouring, perform_verification);
+                                                       use_iouring, perform_verification_);
     if (!snapuserd->InitCowDevice()) {
         LOG(ERROR) << "Failed to initialize Snapuserd";
         return nullptr;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
index b1605f0..ff6ee8f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -57,8 +57,7 @@
                                                       const std::string& backing_device,
                                                       const std::string& base_path_merge,
                                                       std::shared_ptr<IBlockServerOpener> opener,
-                                                      int num_worker_threads, bool use_iouring,
-                                                      bool perform_verification) = 0;
+                                                      int num_worker_threads, bool use_iouring) = 0;
 
     // Start serving requests on a snapshot handler.
     virtual bool StartHandler(const std::string& misc_name) = 0;
@@ -84,6 +83,9 @@
 
     // Returns whether all snapshots have verified.
     virtual bool GetVerificationStatus() = 0;
+
+    // Disable partition verification
+    virtual void DisableVerification() = 0;
 };
 
 class SnapshotHandlerManager final : public ISnapshotHandlerManager {
@@ -94,8 +96,7 @@
                                               const std::string& backing_device,
                                               const std::string& base_path_merge,
                                               std::shared_ptr<IBlockServerOpener> opener,
-                                              int num_worker_threads, bool use_iouring,
-                                              bool perform_verification) override;
+                                              int num_worker_threads, bool use_iouring) override;
     bool StartHandler(const std::string& misc_name) override;
     bool DeleteHandler(const std::string& misc_name) override;
     bool InitiateMerge(const std::string& misc_name) override;
@@ -104,6 +105,7 @@
     void TerminateMergeThreads() override;
     double GetMergePercentage() override;
     bool GetVerificationStatus() override;
+    void DisableVerification() override { perform_verification_ = false; }
 
   private:
     bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
@@ -128,6 +130,7 @@
     int num_partitions_merge_complete_ = 0;
     std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
     android::base::unique_fd monitor_merge_event_fd_;
+    bool perform_verification_ = true;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 13b9a00..6eee357 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -360,16 +360,15 @@
         num_worker_threads = 1;
     }
 
-    bool perform_verification = true;
-    if (android::base::EndsWith(misc_name, "-init") || is_socket_present_) {
-        perform_verification = false;
+    if (android::base::EndsWith(misc_name, "-init") || is_socket_present_ ||
+        (access(kBootSnapshotsWithoutSlotSwitch, F_OK) == 0)) {
+        handlers_->DisableVerification();
     }
 
     auto opener = block_server_factory_->CreateOpener(misc_name);
 
     return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
-                                 opener, num_worker_threads, io_uring_enabled_,
-                                 perform_verification);
+                                 opener, num_worker_threads, io_uring_enabled_);
 }
 
 bool UserSnapshotServer::WaitForSocket() {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index be28541..9926071 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -40,6 +40,8 @@
 
 static constexpr uint32_t kMaxPacketSize = 512;
 static constexpr uint8_t kMaxMergeThreads = 2;
+static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
+        "/metadata/ota/snapshot-boot-without-slot-switch";
 
 class UserSnapshotServer {
   private:
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index bed71cf..73c3cbf 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -627,9 +627,10 @@
 void SnapuserdTest::InitCowDevice() {
     auto factory = harness_->GetBlockServerFactory();
     auto opener = factory->CreateOpener(system_device_ctrl_name_);
+    handlers_->DisableVerification();
     auto handler =
             handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(),
-                                  base_dev_->GetPath(), opener, 1, GetParam(), false);
+                                  base_dev_->GetPath(), opener, 1, GetParam());
     ASSERT_NE(handler, nullptr);
     ASSERT_NE(handler->snapuserd(), nullptr);
 #ifdef __ANDROID__
diff --git a/init/init.cpp b/init/init.cpp
index 83cb78b..aeccd66 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -117,7 +117,6 @@
 
 static int property_triggers_enabled = 0;
 
-int sigchld_fd = -1;
 static int sigterm_fd = -1;
 static int property_fd = -1;
 
@@ -717,7 +716,7 @@
 
 static void HandleSignalFd(int signal) {
     signalfd_siginfo siginfo;
-    const int signal_fd = signal == SIGCHLD ? sigchld_fd : sigterm_fd;
+    const int signal_fd = signal == SIGCHLD ? Service::GetSigchldFd() : sigterm_fd;
     ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
     if (bytes_read != sizeof(siginfo)) {
         PLOG(ERROR) << "Failed to read siginfo from signal_fd";
@@ -751,20 +750,24 @@
     }
 }
 
+static Result<void> RegisterSignalFd(Epoll* epoll, int signal, int fd) {
+    return epoll->RegisterHandler(
+            fd, [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
+}
+
 static Result<int> CreateAndRegisterSignalFd(Epoll* epoll, int signal) {
     sigset_t mask;
     sigemptyset(&mask);
     sigaddset(&mask, signal);
-    unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
-    if (signal_fd == -1) {
-        return ErrnoError() << "failed to create signalfd for signal " << signal;
+    if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
+        return ErrnoError() << "failed to block signal " << signal;
     }
 
-    auto result = epoll->RegisterHandler(
-            signal_fd.get(), [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
-    if (!result.ok()) {
-        return result.error();
+    unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+    if (signal_fd.get() < 0) {
+        return ErrnoError() << "failed to create signalfd for signal " << signal;
     }
+    OR_RETURN(RegisterSignalFd(epoll, signal, signal_fd.get()));
 
     return signal_fd.release();
 }
@@ -775,34 +778,18 @@
     const struct sigaction act { .sa_flags = SA_NOCLDSTOP, .sa_handler = SIG_DFL };
     sigaction(SIGCHLD, &act, nullptr);
 
-    sigset_t mask;
-    sigemptyset(&mask);
-    sigaddset(&mask, SIGCHLD);
-
-    if (!IsRebootCapable()) {
-        // If init does not have the CAP_SYS_BOOT capability, it is running in a container.
-        // In that case, receiving SIGTERM will cause the system to shut down.
-        sigaddset(&mask, SIGTERM);
-    }
-
-    if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
-        PLOG(FATAL) << "failed to block signals";
-    }
-
     // Register a handler to unblock signals in the child processes.
     const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
     if (result != 0) {
         LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
     }
 
-    Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGCHLD);
+    Result<void> cs_result = RegisterSignalFd(epoll, SIGCHLD, Service::GetSigchldFd());
     if (!cs_result.ok()) {
         PLOG(FATAL) << cs_result.error();
     }
-    sigchld_fd = cs_result.value();
-    Service::SetSigchldFd(sigchld_fd);
 
-    if (sigismember(&mask, SIGTERM)) {
+    if (!IsRebootCapable()) {
         Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGTERM);
         if (!cs_result.ok()) {
             PLOG(FATAL) << cs_result.error();
diff --git a/init/init.h b/init/init.h
index b781167..9c7e918 100644
--- a/init/init.h
+++ b/init/init.h
@@ -28,8 +28,6 @@
 namespace android {
 namespace init {
 
-extern int sigchld_fd;
-
 Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
 Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list);
 
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 5757922..1a26c4d 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -563,7 +563,7 @@
         }
     }
     if (timeout > 0ms) {
-        WaitToBeReaped(sigchld_fd, pids, timeout);
+        WaitToBeReaped(Service::GetSigchldFd(), pids, timeout);
     } else {
         // Even if we don't to wait for services to stop, we still optimistically reap zombies.
         ReapAnyOutstandingChildren();
diff --git a/init/security.cpp b/init/security.cpp
index 0c73fae..3e15447 100644
--- a/init/security.cpp
+++ b/init/security.cpp
@@ -20,6 +20,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/perf_event.h>
+#include <math.h>
 #include <selinux/selinux.h>
 #include <sys/ioctl.h>
 #include <sys/syscall.h>
@@ -121,8 +122,12 @@
     }
 #elif defined(__x86_64__)
     // x86_64 supports 28 - 32 rnd bits, but Android wants to ensure that the
-    // theoretical maximum of 32 bits is always supported and used.
-    if (SetMmapRndBitsMin(32, 32, false) && (!Has32BitAbi() || SetMmapRndBitsMin(16, 16, true))) {
+    // theoretical maximum of 32 bits is always supported and used; except in
+    // the case of the x86 page size emulator which supports a maximum
+    // of 30 bits for 16k page size, or 28 bits for 64k page size.
+    int max_bits = 32 - (static_cast<int>(log2(getpagesize())) - 12);
+    if (SetMmapRndBitsMin(max_bits, max_bits, false) &&
+        (!Has32BitAbi() || SetMmapRndBitsMin(16, 16, true))) {
         return {};
     }
 #elif defined(__arm__) || defined(__i386__)
diff --git a/init/service.cpp b/init/service.cpp
index d351a8f..eb24dd5 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -37,6 +37,7 @@
 #include <cutils/sockets.h>
 #include <processgroup/processgroup.h>
 #include <selinux/selinux.h>
+#include <sys/signalfd.h>
 
 #include <string>
 
@@ -68,6 +69,7 @@
 using android::base::SetProperty;
 using android::base::StartsWith;
 using android::base::StringPrintf;
+using android::base::unique_fd;
 using android::base::WriteStringToFile;
 
 namespace android {
@@ -136,7 +138,6 @@
 
 unsigned long Service::next_start_order_ = 1;
 bool Service::is_exec_service_running_ = false;
-int Service::sigchld_fd_ = -1;
 
 Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
                  const std::string& filename, const std::vector<std::string>& args)
@@ -792,6 +793,35 @@
     mount_namespace_ = IsDefaultMountNamespaceReady() ? NS_DEFAULT : NS_BOOTSTRAP;
 }
 
+static int ThreadCount() {
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/proc/self/task"), closedir);
+    if (!dir) {
+        return -1;
+    }
+
+    int count = 0;
+    dirent* entry;
+    while ((entry = readdir(dir.get())) != nullptr) {
+        if (entry->d_name[0] != '.') {
+            count++;
+        }
+    }
+    return count;
+}
+
+// Must be called BEFORE any threads are created. See also the sigprocmask() man page.
+unique_fd Service::CreateSigchldFd() {
+    CHECK_EQ(ThreadCount(), 1);
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGCHLD);
+    if (sigprocmask(SIG_BLOCK, &mask, nullptr) < 0) {
+        PLOG(FATAL) << "Failed to block SIGCHLD";
+    }
+
+    return unique_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+}
+
 void Service::SetStartedInFirstStage(pid_t pid) {
     LOG(INFO) << "adding first-stage service '" << name_ << "'...";
 
diff --git a/init/service.h b/init/service.h
index 13c8b5f..5e9af25 100644
--- a/init/service.h
+++ b/init/service.h
@@ -156,7 +156,10 @@
     const Subcontext* subcontext() const { return subcontext_; }
     const std::string& filename() const { return filename_; }
     void set_filename(const std::string& name) { filename_ = name; }
-    static void SetSigchldFd(int sigchld_fd) { sigchld_fd_ = sigchld_fd; }
+    static int GetSigchldFd() {
+        static int sigchld_fd = CreateSigchldFd().release();
+        return sigchld_fd;
+    }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
@@ -169,10 +172,10 @@
     void RunService(const std::vector<Descriptor>& descriptors, InterprocessFifo cgroups_activated,
                     InterprocessFifo setsid_finished);
     void SetMountNamespace();
+    static ::android::base::unique_fd CreateSigchldFd();
 
     static unsigned long next_start_order_;
     static bool is_exec_service_running_;
-    static int sigchld_fd_;
 
     const std::string name_;
     std::set<std::string> classnames_;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 586e2cf..3f0d0e9 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -297,6 +297,10 @@
 }
 
 static UeventdConfiguration GetConfiguration() {
+    if (IsMicrodroid()) {
+        return ParseConfig({"/system/etc/ueventd.rc", "/vendor/etc/ueventd.rc"});
+    }
+
     auto hardware = android::base::GetProperty("ro.hardware", "");
 
     struct LegacyPathInfo {
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index a319c63..9107838 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -87,7 +87,6 @@
 bool setProcessGroupSoftLimit(uid_t uid, int initialPid, int64_t softLimitInBytes);
 bool setProcessGroupLimit(uid_t uid, int initialPid, int64_t limitInBytes);
 
-void removeAllProcessGroups(void);
 void removeAllEmptyProcessGroups(void);
 
 // Provides the path for an attribute in a specific process group
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 6d07538..76868bb 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -242,7 +242,7 @@
     return ret;
 }
 
-static bool RemoveUidCgroups(const std::string& uid_path, bool empty_only) {
+static bool RemoveEmptyUidCgroups(const std::string& uid_path) {
     std::unique_ptr<DIR, decltype(&closedir)> uid(opendir(uid_path.c_str()), closedir);
     bool empty = true;
     if (uid != NULL) {
@@ -257,21 +257,6 @@
             }
 
             auto path = StringPrintf("%s/%s", uid_path.c_str(), dir->d_name);
-            if (empty_only) {
-                struct stat st;
-                auto procs_file = StringPrintf("%s/%s", path.c_str(),
-                                               PROCESSGROUP_CGROUP_PROCS_FILE);
-                if (stat(procs_file.c_str(), &st) == -1) {
-                    PLOG(ERROR) << "Failed to get stats for " << procs_file;
-                    continue;
-                }
-                if (st.st_size > 0) {
-                    // skip non-empty groups
-                    LOG(VERBOSE) << "Skipping non-empty group " << path;
-                    empty = false;
-                    continue;
-                }
-            }
             LOG(VERBOSE) << "Removing " << path;
             if (rmdir(path.c_str()) == -1) {
                 if (errno != EBUSY) {
@@ -284,7 +269,9 @@
     return empty;
 }
 
-static void removeAllProcessGroupsInternal(bool empty_only) {
+void removeAllEmptyProcessGroups() {
+    LOG(VERBOSE) << "removeAllEmptyProcessGroups()";
+
     std::vector<std::string> cgroups;
     std::string path, memcg_apps_path;
 
@@ -311,7 +298,7 @@
                 }
 
                 auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name);
-                if (!RemoveUidCgroups(path, empty_only)) {
+                if (!RemoveEmptyUidCgroups(path)) {
                     LOG(VERBOSE) << "Skip removing " << path;
                     continue;
                 }
@@ -324,16 +311,6 @@
     }
 }
 
-void removeAllProcessGroups() {
-    LOG(VERBOSE) << "removeAllProcessGroups()";
-    removeAllProcessGroupsInternal(false);
-}
-
-void removeAllEmptyProcessGroups() {
-    LOG(VERBOSE) << "removeAllEmptyProcessGroups()";
-    removeAllProcessGroupsInternal(true);
-}
-
 /**
  * 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
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 3e4393d..d013ec8 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -1,13 +1,6 @@
 {
   "Cgroups": [
     {
-      "Controller": "blkio",
-      "Path": "/dev/blkio",
-      "Mode": "0775",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
       "Controller": "cpu",
       "Path": "/dev/cpuctl",
       "Mode": "0755",
@@ -39,6 +32,12 @@
       {
         "Controller": "freezer",
         "Path": "."
+      },
+      {
+        "Controller": "io",
+        "Path": ".",
+        "NeedsActivation": true,
+        "Optional": true
       }
     ]
   }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 1fc66ba..2c08b0b 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -76,6 +76,21 @@
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
+    },
+    {
+      "Name": "BfqWeight",
+      "Controller": "io",
+      "File": "io.bfq.weight"
+    },
+    {
+      "Name": "CfqGroupIdle",
+      "Controller": "io",
+      "File": "io.group_idle"
+    },
+    {
+      "Name": "CfqWeight",
+      "Controller": "io",
+      "File": "io.weight"
     }
   ],
 
@@ -439,11 +454,30 @@
       "Name": "LowIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": "background"
+            "Name": "BfqWeight",
+            "Value": "10",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "200",
+            "Optional": "true"
           }
         }
       ]
@@ -452,11 +486,30 @@
       "Name": "NormalIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": ""
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
           }
         }
       ]
@@ -465,11 +518,30 @@
       "Name": "HighIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": ""
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
           }
         }
       ]
@@ -478,11 +550,30 @@
       "Name": "MaxIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "blkio",
-            "Path": ""
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
           }
         }
       ]
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index f51b076..d5bd47c 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -316,7 +316,7 @@
     FdCacheHelper::Init(controller_.GetProcsFilePath(path_, 0, 0), fd_[ProfileAction::RCT_PROCESS]);
 }
 
-bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd, ResourceCacheType cache_type) const {
     if (tid <= 0) {
         return true;
     }
@@ -332,6 +332,7 @@
         return true;
     }
 
+    const char* controller_name = controller()->name();
     // ENOSPC is returned when cpuset cgroup that we are joining has no online cpus
     if (errno == ENOSPC && !strcmp(controller_name, "cpuset")) {
         // This is an abnormal case happening only in testing, so report it only once
@@ -345,7 +346,8 @@
                    << "' into cpuset because all cpus in that cpuset are offline";
         empty_cpuset_reported = true;
     } else {
-        PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; fd=" << fd;
+        PLOG(ERROR) << "AddTidToCgroup failed to write '" << value << "'; path=" << path_ << "; "
+                    << (cache_type == RCT_TASK ? "task" : "process");
     }
 
     return false;
@@ -356,7 +358,7 @@
     std::lock_guard<std::mutex> lock(fd_mutex_);
     if (FdCacheHelper::IsCached(fd_[cache_type])) {
         // fd is cached, reuse it
-        if (!AddTidToCgroup(id, fd_[cache_type], controller()->name())) {
+        if (!AddTidToCgroup(id, fd_[cache_type], cache_type)) {
             LOG(ERROR) << "Failed to add task into cgroup";
             return ProfileAction::FAIL;
         }
@@ -391,7 +393,7 @@
         PLOG(WARNING) << Name() << "::" << __func__ << ": failed to open " << procs_path;
         return false;
     }
-    if (!AddTidToCgroup(pid, tmp_fd, controller()->name())) {
+    if (!AddTidToCgroup(pid, tmp_fd, RCT_PROCESS)) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
@@ -412,7 +414,7 @@
         PLOG(WARNING) << Name() << "::" << __func__ << ": failed to open " << tasks_path;
         return false;
     }
-    if (!AddTidToCgroup(tid, tmp_fd, controller()->name())) {
+    if (!AddTidToCgroup(tid, tmp_fd, RCT_TASK)) {
         LOG(ERROR) << "Failed to add task into cgroup";
         return false;
     }
@@ -866,7 +868,13 @@
 
                 auto controller = cg_map.FindController(controller_name);
                 if (controller.HasValue()) {
-                    profile->Add(std::make_unique<SetCgroupAction>(controller, path));
+                    if (controller.version() == 1) {
+                        profile->Add(std::make_unique<SetCgroupAction>(controller, path));
+                    } else {
+                        LOG(WARNING) << "A JoinCgroup action in the " << profile_name
+                                     << " profile is used for controller " << controller_name
+                                     << " in the cgroup v2 hierarchy and will be ignored";
+                    }
                 } else {
                     LOG(WARNING) << "JoinCgroup: controller " << controller_name << " is not found";
                 }
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 4663f64..16ffe63 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -160,7 +160,7 @@
     android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
     mutable std::mutex fd_mutex_;
 
-    static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+    bool AddTidToCgroup(int tid, int fd, ResourceCacheType cache_type) const;
     CacheUseResult UseCachedFd(ResourceCacheType cache_type, int id) const;
 };
 
diff --git a/libutils/binder/StrongPointer_test.cpp b/libutils/binder/StrongPointer_test.cpp
index f27c1f1..aa993c3 100644
--- a/libutils/binder/StrongPointer_test.cpp
+++ b/libutils/binder/StrongPointer_test.cpp
@@ -106,3 +106,17 @@
     EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
     delete foo;
 }
+
+TYPED_TEST(StrongPointer, release) {
+    bool isDeleted = false;
+    TypeParam* foo = nullptr;
+    {
+        sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+        ASSERT_EQ(1, sp1->getStrongCount());
+        foo = sp1.release();
+    }
+    ASSERT_FALSE(isDeleted) << "release failed, deleted anyway when sp left scope";
+    ASSERT_EQ(1, foo->getStrongCount()) << "release mismanaged refcount";
+    foo->decStrong(nullptr);
+    ASSERT_TRUE(isDeleted) << "foo was leaked!";
+}
diff --git a/libutils/binder/include/utils/StrongPointer.h b/libutils/binder/include/utils/StrongPointer.h
index 54aa691..43c00c9 100644
--- a/libutils/binder/include/utils/StrongPointer.h
+++ b/libutils/binder/include/utils/StrongPointer.h
@@ -98,6 +98,15 @@
 
     void clear();
 
+    // Releases the ownership of the object managed by this instance of sp, if any.
+    // The caller is now responsible for managing it. That is, the caller must ensure
+    // decStrong() is called when the pointer is no longer used.
+    [[nodiscard]] inline T* release() noexcept {
+        auto ret = m_ptr;
+        m_ptr = nullptr;
+        return ret;
+    }
+
     // Accessors
 
     inline T&       operator* () const     { return *m_ptr; }
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 317f809..fb64736 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -219,26 +219,6 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
-    # Create blkio group and apply initial settings.
-    # This feature needs kernel to support it, and the
-    # device's init.rc must actually set the correct values.
-    mkdir /dev/blkio/background
-    chown system system /dev/blkio
-    chown system system /dev/blkio/background
-    chown system system /dev/blkio/tasks
-    chown system system /dev/blkio/background/tasks
-    chown system system /dev/blkio/cgroup.procs
-    chown system system /dev/blkio/background/cgroup.procs
-    chmod 0664 /dev/blkio/tasks
-    chmod 0664 /dev/blkio/background/tasks
-    chmod 0664 /dev/blkio/cgroup.procs
-    chmod 0664 /dev/blkio/background/cgroup.procs
-    write /dev/blkio/blkio.weight 1000
-    write /dev/blkio/background/blkio.weight 200
-    write /dev/blkio/background/blkio.bfq.weight 10
-    write /dev/blkio/blkio.group_idle 0
-    write /dev/blkio/background/blkio.group_idle 0
-
     restorecon_recursive /mnt
 
     mount configfs none /config nodev noexec nosuid
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 60dcc2a..3927501 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -71,6 +71,7 @@
 /dev/mtp_usb              0660   root       mtp
 /dev/usb_accessory        0660   root       usb
 /dev/tun                  0660   system     vpn
+/dev/hidraw*              0660   system     system
 
 # CDMA radio interface MUX
 /dev/ppp                  0660   radio      vpn