Merge "Ensure CancelUpdate() always works in recovery." into main
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 966696b..b5b976a 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -374,11 +374,15 @@
     srcs: [
         "snapshotctl.cpp",
         "scratch_super.cpp",
+        "android/snapshot/snapshot.proto",
     ],
     static_libs: [
         "libbrotli",
         "libfstab",
         "libz",
+        "libavb",
+        "libfs_avb",
+        "libcrypto_static",
         "update_metadata-protos",
     ],
     shared_libs: [
@@ -488,7 +492,10 @@
     host_supported: true,
     device_supported: false,
 
-    srcs: ["libsnapshot_cow/create_cow.cpp"],
+    srcs: [
+        "libsnapshot_cow/create_cow.cpp",
+        "android/snapshot/snapshot.proto",
+    ],
 
     cflags: [
         "-Wall",
@@ -498,14 +505,21 @@
     static_libs: [
         "liblog",
         "libbase",
+        "libfstab",
         "libext4_utils",
         "libsnapshot_cow",
         "libcrypto",
         "libbrotli",
         "libz",
+        "libdm",
         "liblz4",
         "libzstd",
         "libgflags",
+        "libavb",
+        "libext2_uuid",
+        "libfs_avb",
+        "libcrypto",
+        "libprotobuf-cpp-lite",
     ],
     shared_libs: [
     ],
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 5fb71a3..6f31251 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -283,3 +283,14 @@
     // Size of v3 operation buffer. Needs to be determined during writer initialization
     uint64 estimated_op_count_max = 14;
 }
+
+message VerityHash {
+  // Partition name
+  string partition_name = 1;
+
+  // Salt used for verity hashes
+  string salt = 2;
+
+  // sha256 hash values of each block in the image
+  repeated bytes block_hash = 3;
+}
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp
index 5497b72..fd4e7da 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp
@@ -8,6 +8,7 @@
 
 #include <condition_variable>
 #include <cstring>
+#include <fstream>
 #include <future>
 #include <iostream>
 #include <limits>
@@ -17,26 +18,27 @@
 #include <unordered_map>
 #include <vector>
 
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <ext4_utils/ext4_utils.h>
-#include <storage_literals/storage_literals.h>
-
 #include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/hex.h>
+#include <android-base/logging.h>
 #include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-
+#include <android-base/unique_fd.h>
+#include <android/snapshot/snapshot.pb.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fs_avb/fs_avb_util.h>
 #include <gflags/gflags.h>
 #include <libsnapshot/cow_writer.h>
-
 #include <openssl/sha.h>
+#include <storage_literals/storage_literals.h>
 
 DEFINE_string(source, "", "Source partition image");
 DEFINE_string(target, "", "Target partition image");
 DEFINE_string(compression, "lz4",
               "Compression algorithm. Default is set to lz4. Available options: lz4, zstd, gz");
+DEFINE_bool(merkel_tree, false, "If true, source image hash is obtained from verity merkel tree");
 
 namespace android {
 namespace snapshot {
@@ -51,7 +53,8 @@
 class CreateSnapshot {
   public:
     CreateSnapshot(const std::string& src_file, const std::string& target_file,
-                   const std::string& patch_file, const std::string& compression);
+                   const std::string& patch_file, const std::string& compression,
+                   const bool& merkel_tree);
     bool CreateSnapshotPatch();
 
   private:
@@ -108,6 +111,14 @@
     bool WriteOrderedSnapshots();
     bool WriteNonOrderedSnapshots();
     bool VerifyMergeOrder();
+
+    bool CalculateDigest(const void* buffer, size_t size, const void* salt, uint32_t salt_length,
+                         uint8_t* digest);
+    bool ParseSourceMerkelTree();
+
+    bool use_merkel_tree_ = false;
+    std::vector<uint8_t> target_salt_;
+    std::vector<uint8_t> source_salt_;
 };
 
 void CreateSnapshotLogger(android::base::LogId, android::base::LogSeverity severity, const char*,
@@ -120,8 +131,12 @@
 }
 
 CreateSnapshot::CreateSnapshot(const std::string& src_file, const std::string& target_file,
-                               const std::string& patch_file, const std::string& compression)
-    : src_file_(src_file), target_file_(target_file), patch_file_(patch_file) {
+                               const std::string& patch_file, const std::string& compression,
+                               const bool& merkel_tree)
+    : src_file_(src_file),
+      target_file_(target_file),
+      patch_file_(patch_file),
+      use_merkel_tree_(merkel_tree) {
     if (!compression.empty()) {
         compression_ = compression;
     }
@@ -156,7 +171,76 @@
     if (!PrepareParse(src_file_, false)) {
         return false;
     }
-    return ParsePartition();
+
+    if (use_merkel_tree_) {
+        return ParseSourceMerkelTree();
+    } else {
+        return ParsePartition();
+    }
+}
+
+bool CreateSnapshot::CalculateDigest(const void* buffer, size_t size, const void* salt,
+                                     uint32_t salt_length, uint8_t* digest) {
+    SHA256_CTX ctx;
+    if (SHA256_Init(&ctx) != 1) {
+        return false;
+    }
+    if (SHA256_Update(&ctx, salt, salt_length) != 1) {
+        return false;
+    }
+    if (SHA256_Update(&ctx, buffer, size) != 1) {
+        return false;
+    }
+    if (SHA256_Final(digest, &ctx) != 1) {
+        return false;
+    }
+    return true;
+}
+
+bool CreateSnapshot::ParseSourceMerkelTree() {
+    std::string fname = android::base::Basename(target_file_.c_str());
+    std::string partitionName = fname.substr(0, fname.find(".img"));
+
+    auto vbmeta = android::fs_mgr::LoadAndVerifyVbmetaByPath(
+            target_file_, partitionName, "", true, false, false, nullptr, nullptr, nullptr);
+    if (vbmeta == nullptr) {
+        LOG(ERROR) << "LoadAndVerifyVbmetaByPath failed for partition: " << partitionName;
+        return false;
+    }
+    auto descriptor = android::fs_mgr::GetHashtreeDescriptor(partitionName, std::move(*vbmeta));
+    if (descriptor == nullptr) {
+        LOG(ERROR) << "GetHashtreeDescriptor failed for partition: " << partitionName;
+        return false;
+    }
+
+    std::fstream input(src_file_, std::ios::in | std::ios::binary);
+    VerityHash hash;
+    if (!hash.ParseFromIstream(&input)) {
+        LOG(ERROR) << "Failed to parse message.";
+        return false;
+    }
+
+    std::string source_salt = hash.salt();
+    source_salt.erase(std::remove(source_salt.begin(), source_salt.end(), '\0'), source_salt.end());
+    if (!android::base::HexToBytes(source_salt, &source_salt_)) {
+        LOG(ERROR) << "HexToBytes conversion failed for source salt: " << source_salt;
+        return false;
+    }
+
+    std::string target_salt = descriptor->salt;
+    if (!android::base::HexToBytes(target_salt, &target_salt_)) {
+        LOG(ERROR) << "HexToBytes conversion failed for target salt: " << target_salt;
+        return false;
+    }
+
+    std::vector<uint8_t> digest(32, 0);
+    for (int i = 0; i < hash.block_hash_size(); i++) {
+        CalculateDigest(hash.block_hash(i).data(), hash.block_hash(i).size(), target_salt_.data(),
+                        target_salt_.size(), digest.data());
+        source_block_hash_[ToHexString(digest.data(), 32)] = i;
+    }
+
+    return true;
 }
 
 /*
@@ -386,10 +470,21 @@
         while (num_blocks) {
             const void* bufptr = (char*)buffer.get() + buffer_offset;
             uint64_t blkindex = foffset / BLOCK_SZ;
+            std::string hash;
 
-            uint8_t checksum[32];
-            SHA256(bufptr, BLOCK_SZ, checksum);
-            std::string hash = ToHexString(checksum, sizeof(checksum));
+            if (create_snapshot_patch_ && use_merkel_tree_) {
+                std::vector<uint8_t> digest(32, 0);
+                CalculateDigest(bufptr, BLOCK_SZ, target_salt_.data(), target_salt_.size(),
+                                digest.data());
+                std::vector<uint8_t> final_digest(32, 0);
+                CalculateDigest(digest.data(), digest.size(), source_salt_.data(),
+                                source_salt_.size(), final_digest.data());
+                hash = ToHexString(final_digest.data(), final_digest.size());
+            } else {
+                uint8_t checksum[32];
+                SHA256(bufptr, BLOCK_SZ, checksum);
+                hash = ToHexString(checksum, sizeof(checksum));
+            }
 
             if (create_snapshot_patch_) {
                 PrepareMergeBlock(bufptr, blkindex, hash);
@@ -497,7 +592,7 @@
     auto parts = android::base::Split(fname, ".");
     std::string snapshotfile = parts[0] + ".patch";
     android::snapshot::CreateSnapshot snapshot(FLAGS_source, FLAGS_target, snapshotfile,
-                                               FLAGS_compression);
+                                               FLAGS_compression, FLAGS_merkel_tree);
 
     if (!snapshot.CreateSnapshotPatch()) {
         LOG(ERROR) << "Snapshot creation failed";
diff --git a/fs_mgr/libsnapshot/scripts/Android.bp b/fs_mgr/libsnapshot/scripts/Android.bp
index 829f5bc..b99da93 100644
--- a/fs_mgr/libsnapshot/scripts/Android.bp
+++ b/fs_mgr/libsnapshot/scripts/Android.bp
@@ -29,3 +29,8 @@
         "snapshot_proto_python",
     ],
 }
+
+sh_binary_host {
+    name: "apply_update",
+    src: "apply-update.sh",
+}
diff --git a/fs_mgr/libsnapshot/scripts/apply-update.sh b/fs_mgr/libsnapshot/scripts/apply-update.sh
index 90b0119..0b10721 100755
--- a/fs_mgr/libsnapshot/scripts/apply-update.sh
+++ b/fs_mgr/libsnapshot/scripts/apply-update.sh
@@ -1,77 +1,220 @@
 #!/bin/bash
 
-# This is a debug script to quicky test end-to-end flow
-# of snapshot updates without going through update-engine.
+# Copyright 2024 Google Inc. All rights reserved.
 #
-# Usage:
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
 #
-#  To update both dynamic and static partitions:
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
-# ./system/core/fs_mgr/libsnapshot/apply_update.sh [--update-static-partitions] [--wipe]
-#
-# --update-static-partitions: This will update bootloader and static A/B
-# partitions
-# --wipe: Allows data wipe as part of update flow
-#
-#  To update dynamic partitions only (this should be used when static
-#  partitions are present in both the slots):
-#
-#  ./system/core/fs_mgr/libsnapshot/apply_update.sh
-#
-#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
-rm -f $OUT/*.patch
+# apply_update.sh: Script to update the device in incremental way
 
-# Compare images and create snapshot patches. Currently, this
-# just compares two identical images in $OUT. In general, any source
-# and target images could be passed to create snapshot patches. However,
-# care must be taken to ensure source images are already present on the device.
-#
-# create_snapshot is a host side binary. Build it with `m create_snapshot`
-create_snapshot --source=$OUT/system.img --target=$OUT/system.img &
-create_snapshot --source=$OUT/product.img --target=$OUT/product.img &
-create_snapshot --source=$OUT/vendor.img --target=$OUT/vendor.img &
-create_snapshot --source=$OUT/system_ext.img --target=$OUT/system_ext.img &
-create_snapshot --source=$OUT/vendor_dlkm.img --target=$OUT/vendor_dlkm.img &
-create_snapshot --source=$OUT/system_dlkm.img --target=$OUT/system_dlkm.img &
+# Ensure OUT directory exists
+if [ -z "$OUT" ]; then
+  echo "Error: OUT environment variable not set." >&2
+  exit 1
+fi
 
-echo "Waiting for snapshot patch creation"
-wait $(jobs -p)
-echo "Snapshot patch creation completed"
+DEVICE_PATH="/data/verity-hash"
+HOST_PATH="$OUT/verity-hash"
 
-mv *.patch $OUT/
+# Create the log file path
+log_file="$HOST_PATH/snapshot.log"
+
+# Function to log messages to both console and log file
+log_message() {
+    message="$1"
+    echo "$message"  # Print to stdout
+    echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$log_file"  # Append to log file with timestamp
+}
+
+# Function to check for create_snapshot and build if needed
+ensure_create_snapshot() {
+  if ! command -v create_snapshot &> /dev/null; then
+    log_message "create_snapshot not found. Building..."
+    m create_snapshot
+    if [[ $? -ne 0 ]]; then
+      log_message "Error: Failed to build create_snapshot."
+      exit 1
+    fi
+  fi
+}
+
+ensure_create_snapshot
+
+# Function to flash static partitions
+flash_static_partitions() {
+  local wipe_flag="$1"
+
+  fastboot flash bootloader "$OUT"/bootloader.img
+  fastboot reboot bootloader
+  sleep 1
+  fastboot flash radio "$OUT"/radio.img
+  fastboot reboot bootloader
+  sleep 1
+  fastboot flashall --exclude-dynamic-partitions --disable-super-optimization --skip-reboot
+
+  if (( wipe_flag )); then
+      log_message "Wiping device..."
+      fastboot -w
+  fi
+  fastboot reboot
+}
+
+# Function to display the help message
+show_help() {
+  cat << EOF
+Usage: $0 [OPTIONS]
+
+This script updates an Android device with incremental flashing, optionally wiping data and flashing static partitions.
+
+Options:
+  --skip-static-partitions  Skip flashing static partitions (bootloader, radio, boot, vbmeta, dtbo and other static A/B partitions).
+                           * Requires manual update of static partitions on both A/B slots
+                             *before* using this flag.
+                           * Speeds up the update process and development iteration.
+                           * Ideal for development focused on the Android platform (AOSP,
+                             git_main).
+                           * Safe usage: First update static partitions on both slots, then
+                             use this flag for faster development iterations.
+                             Ex:
+                                1: Run this on both the slots - This will update the kernel and other static partitions:
+                                   $fastboot flashall --exclude-dynamic-partitions --disable-super-optimization --skip-reboot
+
+                                2: Update bootloader on both the slots:
+                                    $fastboot flash bootloader $OUT/bootloader.img --slot=all
+
+                                3: Update radio on both the slots:
+                                    $fastboot flash radio $OUT/radio.img --slot=all
+                            Now, the script can safely use this flag for update purpose.
+
+  --wipe                   Wipe user data during the update.
+  --help                   Display this help message.
+
+Environment Variables:
+  OUT                      Path to the directory containing build output.
+                           This is required for the script to function correctly.
+
+Examples:
+  <Development workflow for any project in the platform and build with 'm' to create the images>
+
+  Update the device:
+  $0
+
+  Update the device, but skip flashing static partitions (see above for the usage):
+  $0 --skip-static-partitions
+
+  Update the device and wipe user data:
+  $0 --wipe
+
+  Display this help message:
+  $0 --help
+EOF
+}
+
+skip_static_partitions=0
+wipe_flag=0
+help_flag=0
+
+# Parse arguments
+for arg in "$@"; do
+  case "$arg" in
+    --skip-static-partitions)
+      skip_static_partitions=1
+      ;;
+    --wipe)
+      wipe_flag=1
+      ;;
+    --help)
+      help_flag=1
+      ;;
+    *)
+      echo "Unknown argument: $arg" >&2
+      help_flag=1
+      ;;
+  esac
+done
+
+# Check if help flag is set
+if (( help_flag )); then
+  show_help
+  exit 0
+fi
+
+rm -rf $HOST_PATH
 
 adb root
 adb wait-for-device
-adb shell mkdir -p /data/update/
-adb push $OUT/*.patch /data/update/
 
-if [[ "$2" == "--wipe" ]]; then
-  adb shell snapshotctl apply-update /data/update/ -w
+adb shell rm -rf $DEVICE_PATH
+adb shell mkdir -p $DEVICE_PATH
+
+echo "Extracting device source hash from dynamic partitions"
+adb shell snapshotctl dump-verity-hash $DEVICE_PATH
+adb pull -q $DEVICE_PATH $OUT/
+
+log_message "Entering directory:"
+
+# Navigate to the verity-hash directory
+cd "$HOST_PATH" || { log_message "Error: Could not navigate to $HOST_PATH"; exit 1; }
+
+pwd
+
+# Iterate over all .pb files using a for loop
+for pb_file in *.pb; do
+  # Extract the base filename without the .pb extension
+  base_filename="${pb_file%.*}"
+
+  # Construct the source and target file names
+  source_file="$pb_file"
+  target_file="$OUT/$base_filename.img"
+
+  # Construct the create_snapshot command using an array
+  snapshot_args=(
+    "create_snapshot"
+    "--source" "$source_file"
+    "--target" "$target_file"
+    "--merkel_tree"
+  )
+
+  # Log the command about to be executed
+  log_message "Running: ${snapshot_args[*]}"
+
+  "${snapshot_args[@]}" >> "$log_file" 2>&1 &
+done
+
+log_message "Waiting for snapshot patch creation"
+
+# Wait for all background processes to complete
+wait $(jobs -p)
+
+log_message "Snapshot patches created successfully"
+
+adb push -q $HOST_PATH/*.patch $DEVICE_PATH
+
+log_message "Applying update"
+
+if (( wipe_flag )); then
+  adb shell snapshotctl apply-update $DEVICE_PATH -w
 else
-  adb shell snapshotctl apply-update /data/update/
+  adb shell snapshotctl apply-update $DEVICE_PATH
 fi
 
-# Check if the --update-static-partitions option is provided.
-# For quick developer workflow, there is no need to repeatedly
-# apply static partitions.
-if [[ "$1" == "--update-static-partitions" ]]; then
-  adb reboot bootloader
-  sleep 5
-  if [[ "$2" == "--wipe" ]]; then
-      fastboot -w
-  fi
-  fastboot flash bootloader $OUT/bootloader.img
-  sleep 1
-  fastboot reboot bootloader
-  sleep 1
-  fastboot flash radio $OUT/radio.img
-  sleep 1
-  fastboot reboot bootloader
-  sleep 1
-  fastboot flashall --exclude-dynamic-partitions --disable-super-optimization
+if (( skip_static_partitions )); then
+    log_message "Rebooting device - Skipping flashing static partitions"
+    adb reboot
 else
-  adb reboot
+    log_message "Rebooting device to bootloader"
+    adb reboot bootloader
+    log_message "Waiting to enter fastboot bootloader"
+    flash_static_partitions "$wipe_flag"
 fi
 
-echo "Update completed"
+log_message "Update completed"
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index 46de991..e1a3310 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -30,12 +30,15 @@
 #include <android-base/unique_fd.h>
 
 #include <android-base/chrono_utils.h>
+#include <android-base/hex.h>
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android/snapshot/snapshot.pb.h>
 
+#include <fs_avb/fs_avb_util.h>
 #include <fs_mgr.h>
 #include <fs_mgr_dm_linear.h>
 #include <fstab/fstab.h>
@@ -44,9 +47,13 @@
 #include <libsnapshot/snapshot.h>
 #include <storage_literals/storage_literals.h>
 
+#include <openssl/sha.h>
+
 #include "partition_cow_creator.h"
 #include "scratch_super.h"
 
+#include "utility.h"
+
 #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
 #include <BootControlClient.h>
 #endif
@@ -89,7 +96,12 @@
                  "  apply-update\n"
                  "    Apply the incremental OTA update wherein the snapshots are\n"
                  "    directly written to COW block device. This will bypass update-engine\n"
-                 "    and the device will be ready to boot from the target build.\n";
+                 "    and the device will be ready to boot from the target build.\n"
+                 "  dump-verity-hash <directory where verity merkel tree hashes are stored> "
+                 "[-verify]\n"
+                 "    Dump the verity merkel tree hashes at the specified path\n"
+                 "    -verify: Verify the dynamic partition blocks by comparing it with verity "
+                 "merkel tree\n";
     return EX_USAGE;
 }
 
@@ -631,6 +643,252 @@
     return true;
 }
 
+static bool GetBlockHashFromMerkelTree(android::base::borrowed_fd image_fd, uint64_t image_size,
+                                       uint32_t data_block_size, uint32_t hash_block_size,
+                                       uint64_t tree_offset,
+                                       std::vector<std::string>& out_block_hash) {
+    uint32_t padded_digest_size = 32;
+    if (image_size % data_block_size != 0) {
+        LOG(ERROR) << "Image_size: " << image_size
+                   << " not a multiple of data block size: " << data_block_size;
+        return false;
+    }
+
+    // vector of level-size and offset
+    std::vector<std::pair<uint64_t, uint64_t>> levels;
+    uint64_t data_block_count = image_size / data_block_size;
+    uint32_t digests_per_block = hash_block_size / padded_digest_size;
+    uint32_t level_block_count = data_block_count;
+    while (level_block_count > 1) {
+        uint32_t next_level_block_count =
+                (level_block_count + digests_per_block - 1) / digests_per_block;
+        levels.emplace_back(std::make_pair(next_level_block_count * hash_block_size, 0));
+        level_block_count = next_level_block_count;
+    }
+    // root digest
+    levels.emplace_back(std::make_pair(0, 0));
+    // initialize offset
+    for (auto level = std::prev(levels.end()); level != levels.begin(); level--) {
+        std::prev(level)->second = level->second + level->first;
+    }
+
+    // We just want level 0
+    auto level = levels.begin();
+    std::string hash_block(hash_block_size, '\0');
+    uint64_t block_offset = tree_offset + level->second;
+    uint64_t t_read_blocks = 0;
+    uint64_t blockidx = 0;
+    uint64_t num_hash_blocks = level->first / hash_block_size;
+    while ((t_read_blocks < num_hash_blocks) && (blockidx < data_block_count)) {
+        if (!android::base::ReadFullyAtOffset(image_fd, hash_block.data(), hash_block.size(),
+                                              block_offset)) {
+            LOG(ERROR) << "Failed to read tree block at offset: " << block_offset;
+            return false;
+        }
+
+        for (uint32_t offset = 0; offset < hash_block.size(); offset += padded_digest_size) {
+            std::string single_hash = hash_block.substr(offset, padded_digest_size);
+            out_block_hash.emplace_back(single_hash);
+
+            blockidx += 1;
+            if (blockidx >= data_block_count) {
+                break;
+            }
+        }
+
+        block_offset += hash_block_size;
+        t_read_blocks += 1;
+    }
+    return true;
+}
+
+static bool CalculateDigest(const void* buffer, size_t size, const void* salt, uint32_t salt_length,
+                            uint8_t* digest) {
+    SHA256_CTX ctx;
+    if (SHA256_Init(&ctx) != 1) {
+        return false;
+    }
+    if (SHA256_Update(&ctx, salt, salt_length) != 1) {
+        return false;
+    }
+    if (SHA256_Update(&ctx, buffer, size) != 1) {
+        return false;
+    }
+    if (SHA256_Final(digest, &ctx) != 1) {
+        return false;
+    }
+    return true;
+}
+
+bool verify_data_blocks(android::base::borrowed_fd fd, const std::vector<std::string>& block_hash,
+                        std::unique_ptr<android::fs_mgr::FsAvbHashtreeDescriptor>& descriptor,
+                        const std::vector<uint8_t>& salt) {
+    uint64_t data_block_count = descriptor->image_size / descriptor->data_block_size;
+    uint64_t foffset = 0;
+    uint64_t blk = 0;
+
+    std::string hash_block(descriptor->hash_block_size, '\0');
+    while (blk < data_block_count) {
+        if (!android::base::ReadFullyAtOffset(fd, hash_block.data(), descriptor->hash_block_size,
+                                              foffset)) {
+            LOG(ERROR) << "Failed to read from offset: " << foffset;
+            return false;
+        }
+
+        std::string digest(32, '\0');
+        CalculateDigest(hash_block.data(), descriptor->hash_block_size, salt.data(), salt.size(),
+                        reinterpret_cast<uint8_t*>(digest.data()));
+        if (digest != block_hash[blk]) {
+            LOG(ERROR) << "Hash mismatch for block: " << blk << " Expected: " << block_hash[blk]
+                       << " Received: " << digest;
+            return false;
+        }
+
+        foffset += descriptor->hash_block_size;
+        blk += 1;
+    }
+
+    return true;
+}
+
+bool DumpVerityHash(int argc, char** argv) {
+    android::base::InitLogging(argv, &android::base::KernelLogger);
+
+    if (::getuid() != 0) {
+        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
+        return EXIT_FAILURE;
+    }
+
+    if (argc < 3) {
+        std::cerr
+                << " dump-verity-hash <directory location where verity hash is saved> {-verify}\n";
+        return false;
+    }
+
+    bool verification_required = false;
+    std::string hash_file_path = argv[2];
+    bool metadata_on_super = false;
+    if (argc == 4) {
+        if (argv[3] == "-verify"s) {
+            verification_required = true;
+        }
+    }
+
+    auto& dm = android::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;
+    }
+
+    android::fs_mgr::Fstab fstab;
+    if (!ReadDefaultFstab(&fstab)) {
+        LOG(ERROR) << "Failed to read fstab";
+        return false;
+    }
+
+    for (const auto& pair : dm_block_devices) {
+        std::string partition_name = pair.first;
+        android::fs_mgr::FstabEntry* fstab_entry =
+                GetEntryForMountPoint(&fstab, "/" + partition_name);
+        auto vbmeta = LoadAndVerifyVbmeta(*fstab_entry, "", nullptr, nullptr, nullptr);
+        if (vbmeta == nullptr) {
+            LOG(ERROR) << "LoadAndVerifyVbmetaByPath failed for partition: " << partition_name;
+            return false;
+        }
+
+        auto descriptor =
+                android::fs_mgr::GetHashtreeDescriptor(partition_name, std::move(*vbmeta));
+        if (descriptor == nullptr) {
+            LOG(ERROR) << "GetHashtreeDescriptor failed for partition: " << partition_name;
+            return false;
+        }
+
+        std::string device_path = fstab_entry->blk_device;
+        if (!dm.GetDmDevicePathByName(fstab_entry->blk_device, &device_path)) {
+            LOG(ERROR) << "Failed to resolve logical device path for: " << fstab_entry->blk_device;
+            return false;
+        }
+
+        android::base::unique_fd fd(open(device_path.c_str(), O_RDONLY));
+        if (fd < 0) {
+            LOG(ERROR) << "Failed to open file: " << device_path;
+            return false;
+        }
+        std::vector<std::string> block_hash;
+        if (!GetBlockHashFromMerkelTree(fd, descriptor->image_size, descriptor->data_block_size,
+                                        descriptor->hash_block_size, descriptor->tree_offset,
+                                        block_hash)) {
+            LOG(ERROR) << "GetBlockHashFromMerkelTree failed";
+            return false;
+        }
+
+        uint64_t dev_sz = lseek(fd, 0, SEEK_END);
+        uint64_t fec_size = dev_sz - descriptor->image_size;
+        if (fec_size % descriptor->data_block_size != 0) {
+            LOG(ERROR) << "fec_size: " << fec_size
+                       << " isn't multiple of: " << descriptor->data_block_size;
+            return false;
+        }
+
+        std::vector<uint8_t> salt;
+        const std::string& salt_str = descriptor->salt;
+        bool ok = android::base::HexToBytes(salt_str, &salt);
+        if (!ok) {
+            LOG(ERROR) << "HexToBytes conversion failed";
+            return false;
+        }
+        uint64_t file_offset = descriptor->image_size;
+        std::vector<uint8_t> hash_block(descriptor->hash_block_size, 0);
+        while (file_offset < dev_sz) {
+            if (!android::base::ReadFullyAtOffset(fd, hash_block.data(),
+                                                  descriptor->hash_block_size, file_offset)) {
+                LOG(ERROR) << "Failed to read tree block at offset: " << file_offset;
+                return false;
+            }
+            std::string digest(32, '\0');
+            CalculateDigest(hash_block.data(), descriptor->hash_block_size, salt.data(),
+                            salt.size(), reinterpret_cast<uint8_t*>(digest.data()));
+            block_hash.push_back(digest);
+            file_offset += descriptor->hash_block_size;
+            fec_size -= descriptor->hash_block_size;
+        }
+
+        if (fec_size != 0) {
+            LOG(ERROR) << "Checksum calculation pending: " << fec_size;
+            return false;
+        }
+
+        if (verification_required) {
+            if (!verify_data_blocks(fd, block_hash, descriptor, salt)) {
+                LOG(ERROR) << "verify_data_blocks failed";
+                return false;
+            }
+        }
+
+        VerityHash verity_hash;
+        verity_hash.set_partition_name(partition_name);
+        verity_hash.set_salt(salt_str);
+        for (auto hash : block_hash) {
+            verity_hash.add_block_hash(hash.data(), hash.size());
+        }
+        std::string hash_file = hash_file_path + "/" + partition_name + ".pb";
+        std::string content;
+        if (!verity_hash.SerializeToString(&content)) {
+            LOG(ERROR) << "Unable to serialize verity_hash";
+            return false;
+        }
+        if (!WriteStringToFileAtomic(content, hash_file)) {
+            PLOG(ERROR) << "Unable to write VerityHash to " << hash_file;
+            return false;
+        }
+
+        LOG(INFO) << partition_name
+                  << ": GetBlockHashFromMerkelTree success. Num Blocks: " << block_hash.size();
+    }
+    return true;
+}
+
 bool MapPrecreatedSnapshots(int argc, char** argv) {
     android::base::InitLogging(argv, &android::base::KernelLogger);
 
@@ -827,6 +1085,7 @@
         {"unmap-snapshots", UnMapPrecreatedSnapshots},
         {"delete-snapshots", DeletePrecreatedSnapshots},
         {"revert-snapshots", RemovePrecreatedSnapshots},
+        {"dump-verity-hash", DumpVerityHash},
 #endif
         {"unmap", UnmapCmdHandler},
         // clang-format on
diff --git a/init/oneshot_on_test.cpp b/init/oneshot_on_test.cpp
index 650f065..b039ac2 100644
--- a/init/oneshot_on_test.cpp
+++ b/init/oneshot_on_test.cpp
@@ -39,11 +39,13 @@
 
     // Bootanim exits quickly when the device is fully booted, so check that it goes back to the
     // 'restarting' state that non-oneshot services enter once they've restarted.
-    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "restarting", 10s));
+    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "restarting", 10s))
+            << "Value is: " << GetProperty("init.svc.bootanim", "");
 
     SetProperty("ctl.oneshot_on", "bootanim");
     SetProperty("ctl.start", "bootanim");
 
     // Now that oneshot is enabled again, bootanim should transition into the 'stopped' state.
-    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "stopped", 10s));
+    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "stopped", 10s))
+            << "Value is: " << GetProperty("init.svc.bootanim", "");
 }
diff --git a/init/property_service.cpp b/init/property_service.cpp
index f2606e3..31af94e 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -595,7 +595,7 @@
 }
 
 static void handle_property_set_fd(int fd) {
-    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
+    static constexpr uint32_t kDefaultSocketTimeout = 5000; /* ms */
 
     int s = accept4(fd, nullptr, nullptr, SOCK_CLOEXEC);
     if (s == -1) {
diff --git a/libcutils/ashmem-dev.cpp b/libcutils/ashmem-dev.cpp
index cebfa5d..615ef5e 100644
--- a/libcutils/ashmem-dev.cpp
+++ b/libcutils/ashmem-dev.cpp
@@ -132,6 +132,16 @@
         return false;
     }
 
+    /*
+     * Ensure that the kernel supports ashmem ioctl commands on memfds. If not,
+     * fall back to using ashmem.
+     */
+    if (TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, getpagesize())) < 0) {
+        ALOGE("ioctl(ASHMEM_SET_SIZE) failed: %s, no ashmem-memfd compat support.\n",
+              strerror(errno));
+        return false;
+    }
+
     if (debug_log) {
         ALOGD("memfd: device has memfd support, using it\n");
     }
@@ -390,8 +400,8 @@
     }
 
     /* We would only allow read-only for any future file operations */
-    if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE | F_SEAL_SEAL) == -1) {
-        ALOGE("memfd_set_prot_region(%d, %d): F_SEAL_FUTURE_WRITE | F_SEAL_SEAL seal failed: %s\n",
+    if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE) == -1) {
+        ALOGE("memfd_set_prot_region(%d, %d): F_SEAL_FUTURE_WRITE seal failed: %s\n",
               fd, prot, strerror(errno));
         return -1;
     }
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 1e76e76..6725acc 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -7,7 +7,6 @@
     module_type: "cc_defaults",
     config_namespace: "ANDROID",
     bool_variables: [
-        "memcg_v2_force_enabled",
         "cgroup_v2_sys_app_isolation",
     ],
     properties: [
@@ -19,11 +18,6 @@
     name: "libprocessgroup_build_flags_cc",
     cpp_std: "gnu++23",
     soong_config_variables: {
-        memcg_v2_force_enabled: {
-            cflags: [
-                "-DMEMCG_V2_FORCE_ENABLED=true",
-            ],
-        },
         cgroup_v2_sys_app_isolation: {
             cflags: [
                 "-DCGROUP_V2_SYS_APP_ISOLATION=true",
diff --git a/libprocessgroup/build_flags.h b/libprocessgroup/build_flags.h
index bc3e7df..d0948c3 100644
--- a/libprocessgroup/build_flags.h
+++ b/libprocessgroup/build_flags.h
@@ -16,20 +16,12 @@
 
 #pragma once
 
-#ifndef MEMCG_V2_FORCE_ENABLED
-#define MEMCG_V2_FORCE_ENABLED false
-#endif
-
 #ifndef CGROUP_V2_SYS_APP_ISOLATION
 #define CGROUP_V2_SYS_APP_ISOLATION false
 #endif
 
 namespace android::libprocessgroup_flags {
 
-inline consteval bool force_memcg_v2() {
-    return MEMCG_V2_FORCE_ENABLED;
-}
-
 inline consteval bool cgroup_v2_sys_app_isolation() {
     return CGROUP_V2_SYS_APP_ISOLATION;
 }
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index c4e1fb6..0d1739e 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -27,9 +27,6 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <optional>
-
-#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <processgroup/cgroup_descriptor.h>
 #include <processgroup/processgroup.h>
@@ -260,39 +257,6 @@
     controller_.set_flags(flags);
 }
 
-static std::optional<bool> MGLRUDisabled() {
-    const std::string file_name = "/sys/kernel/mm/lru_gen/enabled";
-    std::string content;
-    if (!android::base::ReadFileToString(file_name, &content)) {
-        PLOG(ERROR) << "Failed to read MGLRU state from " << file_name;
-        return {};
-    }
-
-    return content == "0x0000";
-}
-
-static std::optional<bool> MEMCGDisabled(const CgroupDescriptorMap& descriptors) {
-    std::string cgroup_v2_root = CGROUP_V2_ROOT_DEFAULT;
-    const auto it = descriptors.find(CGROUPV2_HIERARCHY_NAME);
-    if (it == descriptors.end()) {
-        LOG(WARNING) << "No Cgroups2 path found in cgroups.json. Vendor has modified Android, and "
-                     << "kernel memory use will be higher than intended.";
-    } else if (it->second.controller()->path() != cgroup_v2_root) {
-        cgroup_v2_root = it->second.controller()->path();
-    }
-
-    const std::string file_name = cgroup_v2_root + "/cgroup.controllers";
-    std::string content;
-    if (!android::base::ReadFileToString(file_name, &content)) {
-        PLOG(ERROR) << "Failed to read cgroup controllers from " << file_name;
-        return {};
-    }
-
-    // If we've forced memcg to v2 and it's not available, then it could only have been disabled
-    // on the kernel command line (GKI sets CONFIG_MEMCG).
-    return content.find("memory") == std::string::npos;
-}
-
 static bool CreateV2SubHierarchy(const std::string& path, const CgroupDescriptorMap& descriptors) {
     const auto cgv2_iter = descriptors.find(CGROUPV2_HIERARCHY_NAME);
     if (cgv2_iter == descriptors.end()) return false;
@@ -335,17 +299,6 @@
         }
     }
 
-    if (android::libprocessgroup_flags::force_memcg_v2()) {
-        if (MGLRUDisabled().value_or(false)) {
-            LOG(WARNING) << "Memcg forced to v2 hierarchy with MGLRU disabled! "
-                         << "Global reclaim performance will suffer.";
-        }
-        if (MEMCGDisabled(descriptors).value_or(false)) {
-            LOG(WARNING) << "Memcg forced to v2 hierarchy while memcg is disabled by kernel "
-                         << "command line!";
-        }
-    }
-
     // System / app isolation.
     // This really belongs in early-init in init.rc, but we cannot use the flag there.
     if (android::libprocessgroup_flags::cgroup_v2_sys_app_isolation()) {
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index c03c257..89ca7f1 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -20,7 +20,6 @@
 #include <task_profiles.h>
 
 #include <map>
-#include <optional>
 #include <string>
 
 #include <dirent.h>
@@ -31,6 +30,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -121,15 +121,6 @@
     return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
 }
 
-std::optional<long> readLong(const std::string& str) {
-    char* end;
-    const long result = strtol(str.c_str(), &end, 10);
-    if (end > str.c_str()) {
-        return result;
-    }
-    return std::nullopt;
-}
-
 }  // namespace
 
 IProfileAttribute::~IProfileAttribute() = default;
@@ -930,9 +921,8 @@
                 }
             } else if (action_name == "SetTimerSlack") {
                 const std::string slack_string = params_val["Slack"].asString();
-                std::optional<long> slack = readLong(slack_string);
-                if (slack && *slack >= 0) {
-                    profile->Add(std::make_unique<SetTimerSlackAction>(*slack));
+                if (long slack; android::base::ParseInt(slack_string, &slack) && slack >= 0) {
+                    profile->Add(std::make_unique<SetTimerSlackAction>(slack));
                 } else {
                     LOG(WARNING) << "SetTimerSlack: invalid parameter: " << slack_string;
                 }
@@ -994,18 +984,17 @@
                         // to setpriority(), since the sched_priority value must be 0 for calls to
                         // sched_setscheduler() with "normal" policies.
                         const std::string nice_string = params_val["Nice"].asString();
-                        const std::optional<int> nice = readLong(nice_string);
-
-                        if (!nice) {
+                        int nice;
+                        if (!android::base::ParseInt(nice_string, &nice)) {
                             LOG(FATAL) << "Invalid nice value specified: " << nice_string;
                         }
                         const int LINUX_MIN_NICE = -20;
                         const int LINUX_MAX_NICE = 19;
-                        if (*nice < LINUX_MIN_NICE || *nice > LINUX_MAX_NICE) {
-                            LOG(WARNING) << "SetSchedulerPolicy: Provided nice (" << *nice
+                        if (nice < LINUX_MIN_NICE || nice > LINUX_MAX_NICE) {
+                            LOG(WARNING) << "SetSchedulerPolicy: Provided nice (" << nice
                                          << ") appears out of range.";
                         }
-                        profile->Add(std::make_unique<SetSchedulerPolicyAction>(policy, *nice));
+                        profile->Add(std::make_unique<SetSchedulerPolicyAction>(policy, nice));
                     } else {
                         profile->Add(std::make_unique<SetSchedulerPolicyAction>(policy));
                     }
@@ -1020,10 +1009,11 @@
                     // [sched_get_priority_min(), sched_get_priority_max()]
 
                     const std::string priority_string = params_val["Priority"].asString();
-                    std::optional<long> virtual_priority = readLong(priority_string);
-                    if (virtual_priority && *virtual_priority > 0) {
+                    if (long virtual_priority;
+                        android::base::ParseInt(priority_string, &virtual_priority) &&
+                        virtual_priority > 0) {
                         int priority;
-                        if (SetSchedulerPolicyAction::toPriority(policy, *virtual_priority,
+                        if (SetSchedulerPolicyAction::toPriority(policy, virtual_priority,
                                                                  priority)) {
                             profile->Add(
                                     std::make_unique<SetSchedulerPolicyAction>(policy, priority));
diff --git a/libprocessgroup/util/Android.bp b/libprocessgroup/util/Android.bp
index 1c74d4e..266a53f 100644
--- a/libprocessgroup/util/Android.bp
+++ b/libprocessgroup/util/Android.bp
@@ -21,6 +21,7 @@
 
 cc_library_static {
     name: "libprocessgroup_util",
+    cpp_std: "gnu++23",
     vendor_available: true,
     product_available: true,
     ramdisk_available: true,
@@ -47,7 +48,6 @@
     static_libs: [
         "libjsoncpp",
     ],
-    defaults: ["libprocessgroup_build_flags_cc"],
 }
 
 cc_test {
diff --git a/libprocessgroup/util/util.cpp b/libprocessgroup/util/util.cpp
index 1401675..c772bc5 100644
--- a/libprocessgroup/util/util.cpp
+++ b/libprocessgroup/util/util.cpp
@@ -111,7 +111,6 @@
 }
 
 bool ReadDescriptorsFromFile(const std::string& file_name, CgroupDescriptorMap* descriptors) {
-    static constexpr bool force_memcg_v2 = android::libprocessgroup_flags::force_memcg_v2();
     std::vector<CgroupDescriptor> result;
     std::string json_doc;
 
@@ -133,14 +132,10 @@
         const Json::Value& cgroups = root["Cgroups"];
         for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
             std::string name = cgroups[i]["Controller"].asString();
-
-            if (force_memcg_v2 && name == "memory") continue;
-
             MergeCgroupToDescriptors(descriptors, cgroups[i], name, "", 1);
         }
     }
 
-    bool memcgv2_present = false;
     std::string root_path;
     if (root.isMember("Cgroups2")) {
         const Json::Value& cgroups2 = root["Cgroups2"];
@@ -150,24 +145,10 @@
         const Json::Value& childGroups = cgroups2["Controllers"];
         for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
             std::string name = childGroups[i]["Controller"].asString();
-
-            if (force_memcg_v2 && name == "memory") memcgv2_present = true;
-
             MergeCgroupToDescriptors(descriptors, childGroups[i], name, root_path, 2);
         }
     }
 
-    if (force_memcg_v2 && !memcgv2_present) {
-        LOG(INFO) << "Forcing memcg to v2 hierarchy";
-        Json::Value memcgv2;
-        memcgv2["Controller"] = "memory";
-        memcgv2["NeedsActivation"] = true;
-        memcgv2["Path"] = ".";
-        memcgv2["Optional"] = true;  // In case of cgroup_disabled=memory, so we can still boot
-        MergeCgroupToDescriptors(descriptors, memcgv2, "memory",
-                                 root_path.empty() ? CGROUP_V2_ROOT_DEFAULT : root_path, 2);
-    }
-
     return true;
 }
 
diff --git a/libprocessgroup/vts/Android.bp b/libprocessgroup/vts/Android.bp
new file mode 100644
index 0000000..1ec49a4
--- /dev/null
+++ b/libprocessgroup/vts/Android.bp
@@ -0,0 +1,15 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "vts_libprocessgroup",
+    srcs: ["vts_libprocessgroup.cpp"],
+    shared_libs: ["libbase"],
+    static_libs: ["libgmock"],
+    require_root: true,
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/libprocessgroup/vts/vts_libprocessgroup.cpp b/libprocessgroup/vts/vts_libprocessgroup.cpp
new file mode 100644
index 0000000..af0b0af
--- /dev/null
+++ b/libprocessgroup/vts/vts_libprocessgroup.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cerrno>
+#include <chrono>
+#include <cstdio>
+#include <filesystem>
+#include <future>
+#include <iostream>
+#include <optional>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+using android::base::ReadFileToString;
+using android::base::Split;
+using android::base::WriteStringToFile;
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+const std::string CGROUP_V2_ROOT_PATH = "/sys/fs/cgroup";
+
+std::optional<bool> isMemcgV2Enabled() {
+    if (std::string proc_cgroups; ReadFileToString("/proc/cgroups", &proc_cgroups)) {
+        const std::vector<std::string> lines = Split(proc_cgroups, "\n");
+        for (const std::string& line : lines) {
+            if (line.starts_with("memory")) {
+                const bool enabled = line.back() == '1';
+                if (!enabled) return false;
+
+                const std::vector<std::string> memcg_tokens = Split(line, "\t");
+                return memcg_tokens[1] == "0";  // 0 == default hierarchy == v2
+            }
+        }
+        // We know for sure it's not enabled, either because it is mounted as v1 (cgroups.json
+        // override) which would be detected above, or because it was intentionally disabled via
+        // kernel command line (cgroup_disable=memory), or because it's not built in to the kernel
+        // (CONFIG_MEMCG is not set).
+        return false;
+    }
+
+    // Problems accessing /proc/cgroups (sepolicy?) Try checking the root cgroup.controllers file.
+    perror("Warning: Could not read /proc/cgroups");
+    if (std::string controllers;
+        ReadFileToString(CGROUP_V2_ROOT_PATH + "/cgroup.controllers", &controllers)) {
+        return controllers.find("memory") != std::string::npos;
+    }
+
+    std::cerr << "Error: Could not read " << CGROUP_V2_ROOT_PATH
+              << "/cgroup.controllers: " << std::strerror(errno) << std::endl;
+    return std::nullopt;
+}
+
+std::optional<bool> checkRootSubtreeState() {
+    if (std::string controllers;
+        ReadFileToString(CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control", &controllers)) {
+        return controllers.find("memory") != std::string::npos;
+    }
+    std::cerr << "Error: Could not read " << CGROUP_V2_ROOT_PATH
+              << "/cgroup.subtree_control: " << std::strerror(errno) << std::endl;
+    return std::nullopt;
+}
+
+}  // anonymous namespace
+
+
+class MemcgV2Test : public testing::Test {
+  protected:
+    void SetUp() override {
+        std::optional<bool> memcgV2Enabled = isMemcgV2Enabled();
+        ASSERT_NE(memcgV2Enabled, std::nullopt);
+        if (!*memcgV2Enabled) GTEST_SKIP() << "Memcg v2 not enabled";
+    }
+};
+
+class MemcgV2SubdirTest : public testing::Test {
+  protected:
+    std::optional<std::string> mRandDir;
+
+    void SetUp() override {
+        std::optional<bool> memcgV2Enabled = isMemcgV2Enabled();
+        ASSERT_NE(memcgV2Enabled, std::nullopt);
+        if (!*memcgV2Enabled) GTEST_SKIP() << "Memcg v2 not enabled";
+
+        mRootSubtreeState = checkRootSubtreeState();
+        ASSERT_NE(mRootSubtreeState, std::nullopt);
+
+        if (!*mRootSubtreeState) {
+            ASSERT_TRUE(
+                    WriteStringToFile("+memory", CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control"))
+                    << "Could not enable memcg under root: " << std::strerror(errno);
+        }
+
+        // Make a new, temporary, randomly-named v2 cgroup in which we will attempt to activate
+        // memcg
+        std::random_device rd;
+        std::uniform_int_distribution dist(static_cast<int>('A'), static_cast<int>('Z'));
+        std::string randName = CGROUP_V2_ROOT_PATH + "/vts_libprocessgroup.";
+        for (int i = 0; i < 10; ++i) randName.append(1, static_cast<char>(dist(rd)));
+        ASSERT_TRUE(std::filesystem::create_directory(randName));
+        mRandDir = randName;  // For cleanup in TearDown
+
+        std::string subtree_controllers;
+        ASSERT_TRUE(ReadFileToString(*mRandDir + "/cgroup.controllers", &subtree_controllers));
+        ASSERT_NE(subtree_controllers.find("memory"), std::string::npos)
+                << "Memcg was not activated in child cgroup";
+    }
+
+    void TearDown() override {
+        if (mRandDir) {
+            if (!std::filesystem::remove(*mRandDir)) {
+                std::cerr << "Could not remove temporary memcg v2 test directory" << std::endl;
+            }
+        }
+
+        if (!*mRootSubtreeState) {
+            if (!WriteStringToFile("-memory", CGROUP_V2_ROOT_PATH + "/cgroup.subtree_control")) {
+                std::cerr << "Could not disable memcg under root: " << std::strerror(errno)
+                          << std::endl;
+            }
+        }
+    }
+
+  private:
+    std::optional<bool> mRootSubtreeState;
+};
+
+
+TEST_F(MemcgV2SubdirTest, CanActivateMemcgV2Subtree) {
+    ASSERT_TRUE(WriteStringToFile("+memory", *mRandDir + "/cgroup.subtree_control"))
+            << "Could not enable memcg under child cgroup subtree";
+}
+
+// Test for fix: mm: memcg: use larger batches for proactive reclaim
+// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=287d5fedb377ddc232b216b882723305b27ae31a
+TEST_F(MemcgV2Test, ProactiveReclaimDoesntTakeForever) {
+    // Not all kernels have memory.reclaim
+    const std::filesystem::path reclaim(CGROUP_V2_ROOT_PATH + "/memory.reclaim");
+    if (!std::filesystem::exists(reclaim)) GTEST_SKIP() << "memory.reclaim not found";
+
+    // Use the total device memory as the amount to reclaim
+    const long numPages = sysconf(_SC_PHYS_PAGES);
+    const long pageSize = sysconf(_SC_PAGE_SIZE);
+    ASSERT_GT(numPages, 0);
+    ASSERT_GT(pageSize, 0);
+    const unsigned long long totalMem =
+            static_cast<unsigned long long>(numPages) * static_cast<unsigned long long>(pageSize);
+
+    auto fut = std::async(std::launch::async,
+                          [&]() { WriteStringToFile(std::to_string(totalMem), reclaim); });
+
+    // This is a test for completion within the timeout. The command is likely to "fail" since we
+    // are asking to reclaim all device memory.
+    ASSERT_NE(fut.wait_for(std::chrono::seconds(20)), std::future_status::timeout);
+}
diff --git a/property_service/libpropertyinfoserializer/trie_serializer.cpp b/property_service/libpropertyinfoserializer/trie_serializer.cpp
index adeed1b..f1632cd 100644
--- a/property_service/libpropertyinfoserializer/trie_serializer.cpp
+++ b/property_service/libpropertyinfoserializer/trie_serializer.cpp
@@ -16,6 +16,8 @@
 
 #include "trie_serializer.h"
 
+#include <algorithm>
+
 namespace android {
 namespace properties {