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 {