Merge "Revert "Set block device as RO/RW before mount"" into main
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index 32e8b88..192232d 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -34,6 +34,9 @@
   ],
   "kernel-presubmit": [
     {
+      "name": "adb-remount-sh"
+    },
+    {
       "name": "libdm_test"
     },
     {
diff --git a/fs_mgr/libfstab/fstab.cpp b/fs_mgr/libfstab/fstab.cpp
index f00e0dc..21d2e2e 100644
--- a/fs_mgr/libfstab/fstab.cpp
+++ b/fs_mgr/libfstab/fstab.cpp
@@ -520,6 +520,24 @@
 
 }  // namespace
 
+// Return the path to the recovery fstab file.  There may be multiple fstab files;
+// the one that is returned will be the first that exists of recovery.fstab.<fstab_suffix>,
+// recovery.fstab.<hardware>, and recovery.fstab.<hardware.platform>.
+std::string GetRecoveryFstabPath() {
+    for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
+        std::string suffix;
+
+        if (!fs_mgr_get_boot_config(prop, &suffix)) continue;
+
+        std::string fstab_path = "/etc/recovery.fstab." + suffix;
+        if (access(fstab_path.c_str(), F_OK) == 0) {
+            return fstab_path;
+        }
+    }
+
+    return "/etc/recovery.fstab";
+}
+
 // Return the path to the fstab file.  There may be multiple fstab files; the
 // one that is returned will be the first that exists of fstab.<fstab_suffix>,
 // fstab.<hardware>, and fstab.<hardware.platform>.  The fstab is searched for
@@ -529,7 +547,7 @@
 // the system/etc directory is supported too and is the preferred location.
 std::string GetFstabPath() {
     if (InRecovery()) {
-        return "/etc/recovery.fstab";
+        return GetRecoveryFstabPath();
     }
     for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
         std::string suffix;
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 2e948dd..62f9901 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -229,6 +229,10 @@
 
     // Enable direct reads from source device
     bool o_direct = 12;
+
+    // Number of cow operations to be merged at once
+    uint32 cow_op_merge_size = 13;
+
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 1ec8634..4a3ec1d 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -831,6 +831,8 @@
     // Check if direct reads are enabled for the source image
     bool UpdateUsesODirect(LockedFile* lock);
 
+    // Get value of maximum cow op merge size
+    uint32_t GetUpdateCowOpMergeSize(LockedFile* lock);
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
                               const std::chrono::milliseconds& timeout_ms = {});
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 90813fe..0afd8bd 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -30,6 +30,8 @@
 #include <storage_literals/storage_literals.h>
 #include <update_engine/update_metadata.pb.h>
 
+#include "utility.h"
+
 namespace android {
 namespace snapshot {
 
@@ -234,5 +236,21 @@
 
 #define RETURN_IF_NON_VIRTUAL_AB() RETURN_IF_NON_VIRTUAL_AB_MSG("")
 
+#define SKIP_IF_VENDOR_ON_ANDROID_S()                                        \
+    do {                                                                     \
+        if (IsVendorFromAndroid12())                                         \
+            GTEST_SKIP() << "Skip test as Vendor partition is on Android S"; \
+    } while (0)
+
+#define RETURN_IF_VENDOR_ON_ANDROID_S_MSG(msg) \
+    do {                                       \
+        if (IsVendorFromAndroid12()) {         \
+            std::cerr << (msg);                \
+            return;                            \
+        }                                      \
+    } while (0)
+
+#define RETURN_IF_VENDOR_ON_ANDROID_S() RETURN_IF_VENDOR_ON_ANDROID_S_MSG("")
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 6674378..265445b 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -20,6 +20,7 @@
 #include <sys/file.h>
 #include <sys/types.h>
 #include <sys/unistd.h>
+#include <sys/xattr.h>
 
 #include <chrono>
 #include <filesystem>
@@ -91,6 +92,8 @@
 static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator";
 static constexpr char kSnapuserdFromSystem[] = "/metadata/ota/snapuserd-from-system";
 static constexpr auto kUpdateStateCheckInterval = 2s;
+static constexpr char kOtaFileContext[] = "u:object_r:ota_metadata_file:s0";
+
 /*
  * The readahead size is set to 32kb so that
  * there is no significant memory pressure (/proc/pressure/memory) during boot.
@@ -1706,6 +1709,10 @@
         if (UpdateUsesODirect(lock.get())) {
             snapuserd_argv->emplace_back("-o_direct");
         }
+        uint cow_op_merge_size = GetUpdateCowOpMergeSize(lock.get());
+        if (cow_op_merge_size != 0) {
+            snapuserd_argv->emplace_back("-cow_op_merge_size=" + std::to_string(cow_op_merge_size));
+        }
     }
 
     size_t num_cows = 0;
@@ -2128,6 +2135,11 @@
     return update_status.o_direct();
 }
 
+uint32_t SnapshotManager::GetUpdateCowOpMergeSize(LockedFile* lock) {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    return update_status.cow_op_merge_size();
+}
+
 bool SnapshotManager::MarkSnapuserdFromSystem() {
     auto path = GetSnapuserdFromSystemPath();
 
@@ -2135,6 +2147,24 @@
         PLOG(ERROR) << "Unable to write to vendor update path: " << path;
         return false;
     }
+
+    unique_fd fd(open(path.c_str(), O_PATH));
+    if (fd < 0) {
+        PLOG(ERROR) << "Failed to open file: " << path;
+        return false;
+    }
+
+    /*
+     * This function is invoked by first stage init and hence we need to
+     * explicitly set the correct selinux label for this file as update_engine
+     * will try to remove this file later on once the snapshot merge is
+     * complete.
+     */
+    if (fsetxattr(fd.get(), XATTR_NAME_SELINUX, kOtaFileContext, strlen(kOtaFileContext) + 1, 0) <
+        0) {
+        PLOG(ERROR) << "fsetxattr for the path: " << path << " failed";
+    }
+
     return true;
 }
 
@@ -2185,18 +2215,24 @@
  *
  */
 bool SnapshotManager::IsLegacySnapuserdPostReboot() {
-    if (is_legacy_snapuserd_.has_value() && is_legacy_snapuserd_.value() == true) {
-        auto slot = GetCurrentSlot();
-        if (slot == Slot::Target) {
-            // If this marker is present, then daemon can handle userspace
-            // snapshots; also, it indicates that the vendor partition was
-            // updated from Android 12.
-            if (access(GetSnapuserdFromSystemPath().c_str(), F_OK) == 0) {
-                return false;
-            }
+    auto slot = GetCurrentSlot();
+    if (slot == Slot::Target) {
+        /*
+            If this marker is present, the daemon can handle userspace snapshots.
+            During post-OTA reboot, this implies that the vendor partition is
+            Android 13 or higher. If the snapshots were created on an
+            Android 12 vendor, this means the vendor partition has been updated.
+        */
+        if (access(GetSnapuserdFromSystemPath().c_str(), F_OK) == 0) {
+            is_snapshot_userspace_ = true;
+            return false;
+        }
+        // If the marker isn't present and if the vendor is still in Android 12
+        if (is_legacy_snapuserd_.has_value() && is_legacy_snapuserd_.value() == true) {
             return true;
         }
     }
+
     return false;
 }
 
@@ -3065,6 +3101,7 @@
         status.set_io_uring_enabled(old_status.io_uring_enabled());
         status.set_legacy_snapuserd(old_status.legacy_snapuserd());
         status.set_o_direct(old_status.o_direct());
+        status.set_cow_op_merge_size(old_status.cow_op_merge_size());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -3447,6 +3484,8 @@
             status.set_legacy_snapuserd(true);
             LOG(INFO) << "Setting legacy_snapuserd to true";
         }
+        status.set_cow_op_merge_size(
+                android::base::GetUintProperty<uint32_t>("ro.virtual_ab.cow_op_merge_size", 0));
     } else if (legacy_compression) {
         LOG(INFO) << "Virtual A/B using legacy snapuserd";
     } else {
@@ -3882,6 +3921,7 @@
     ss << "Using userspace snapshots: " << update_status.userspace_snapshots() << std::endl;
     ss << "Using io_uring: " << update_status.io_uring_enabled() << std::endl;
     ss << "Using o_direct: " << update_status.o_direct() << std::endl;
+    ss << "Cow op merge size (0 for uncapped): " << update_status.cow_op_merge_size() << std::endl;
     ss << "Using XOR compression: " << GetXorCompressionEnabledProperty() << std::endl;
     ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
     ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 1435b12..b2e36d4 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -122,6 +122,7 @@
         LOG(INFO) << "Starting test: " << test_name_;
 
         SKIP_IF_NON_VIRTUAL_AB();
+        SKIP_IF_VENDOR_ON_ANDROID_S();
 
         SetupProperties();
         if (!DeviceSupportsMode()) {
@@ -168,6 +169,7 @@
 
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
+        RETURN_IF_VENDOR_ON_ANDROID_S();
 
         LOG(INFO) << "Tearing down SnapshotTest test: " << test_name_;
 
@@ -1015,6 +1017,7 @@
   public:
     void SetUp() override {
         SKIP_IF_NON_VIRTUAL_AB();
+        SKIP_IF_VENDOR_ON_ANDROID_S();
 
         SnapshotTest::SetUp();
         if (!image_manager_) {
@@ -1097,6 +1100,7 @@
     }
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
+        RETURN_IF_VENDOR_ON_ANDROID_S();
 
         LOG(INFO) << "Tearing down SnapshotUpdateTest test: " << test_name_;
 
@@ -2661,6 +2665,7 @@
     status.set_o_direct(true);
     status.set_io_uring_enabled(true);
     status.set_userspace_snapshots(true);
+    status.set_cow_op_merge_size(16);
 
     sm->WriteSnapshotUpdateStatus(lock_.get(), status);
     // Ensure a connection to the second-stage daemon, but use the first-stage
@@ -2682,6 +2687,8 @@
                 snapuserd_argv.end());
     ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-user_snapshot") !=
                 snapuserd_argv.end());
+    ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-cow_op_merge_size=16") !=
+                snapuserd_argv.end());
 }
 
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
@@ -2833,6 +2840,7 @@
     // that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test
     // suites.
     RETURN_IF_NON_VIRTUAL_AB_MSG("Virtual A/B is not enabled, skipping global setup.\n");
+    RETURN_IF_VENDOR_ON_ANDROID_S_MSG("Test not enabled for Vendor on Android S.\n");
 
     std::vector<std::string> paths = {
             // clang-format off
@@ -2887,6 +2895,8 @@
 
 void SnapshotTestEnvironment::TearDown() {
     RETURN_IF_NON_VIRTUAL_AB();
+    RETURN_IF_VENDOR_ON_ANDROID_S();
+
     if (super_images_ != nullptr) {
         DeleteBackingImage(super_images_.get(), "fake-super");
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index 67e9e52..dd2dd56 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -30,6 +30,7 @@
 DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
 DEFINE_bool(io_uring, false, "If true, io_uring feature is enabled");
 DEFINE_bool(o_direct, false, "If true, enable direct reads on source device");
+DEFINE_int32(cow_op_merge_size, 0, "number of operations to be processed at once");
 
 namespace android {
 namespace snapshot {
@@ -106,7 +107,6 @@
         }
         return user_server_.Run();
     }
-
     for (int i = arg_start; i < argc; i++) {
         auto parts = android::base::Split(argv[i], ",");
 
@@ -114,8 +114,8 @@
             LOG(ERROR) << "Malformed message, expected at least four sub-arguments.";
             return false;
         }
-        auto handler =
-                user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3], FLAGS_o_direct);
+        auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3],
+                                               FLAGS_o_direct, FLAGS_cow_op_merge_size);
         if (!handler || !user_server_.StartHandler(parts[0])) {
             return false;
         }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
index c85331b..ef4ba93 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp
@@ -41,7 +41,7 @@
 bool Extractor::Init() {
     auto opener = factory_.CreateTestOpener(control_name_);
     handler_ = std::make_shared<SnapshotHandler>(control_name_, cow_path_, base_path_, base_path_,
-                                                 opener, 1, false, false, false);
+                                                 opener, 1, false, false, false, 0);
     if (!handler_->InitCowDevice()) {
         return false;
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
index ea11f0e..fdd9cce 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -53,10 +53,10 @@
         const std::string& misc_name, const std::string& cow_device_path,
         const std::string& backing_device, const std::string& base_path_merge,
         std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring,
-        bool o_direct) {
+        bool o_direct, uint32_t cow_op_merge_size) {
     auto snapuserd = std::make_shared<SnapshotHandler>(
             misc_name, cow_device_path, backing_device, base_path_merge, opener, num_worker_threads,
-            use_iouring, perform_verification_, o_direct);
+            use_iouring, perform_verification_, o_direct, cow_op_merge_size);
     if (!snapuserd->InitCowDevice()) {
         LOG(ERROR) << "Failed to initialize Snapuserd";
         return nullptr;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
index f23f07e..ecf5d5c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -52,13 +52,11 @@
     virtual ~ISnapshotHandlerManager() {}
 
     // Add a new snapshot handler but do not start serving requests yet.
-    virtual std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
-                                                      const std::string& cow_device_path,
-                                                      const std::string& backing_device,
-                                                      const std::string& base_path_merge,
-                                                      std::shared_ptr<IBlockServerOpener> opener,
-                                                      int num_worker_threads, bool use_iouring,
-                                                      bool o_direct) = 0;
+    virtual std::shared_ptr<HandlerThread> AddHandler(
+            const std::string& misc_name, const std::string& cow_device_path,
+            const std::string& backing_device, const std::string& base_path_merge,
+            std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads, bool use_iouring,
+            bool o_direct, uint32_t cow_op_merge_size) = 0;
 
     // Start serving requests on a snapshot handler.
     virtual bool StartHandler(const std::string& misc_name) = 0;
@@ -98,7 +96,7 @@
                                               const std::string& base_path_merge,
                                               std::shared_ptr<IBlockServerOpener> opener,
                                               int num_worker_threads, bool use_iouring,
-                                              bool o_direct) override;
+                                              bool o_direct, uint32_t cow_op_merge_size) override;
     bool StartHandler(const std::string& misc_name) override;
     bool DeleteHandler(const std::string& misc_name) override;
     bool InitiateMerge(const std::string& misc_name) override;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index e6a9a29..e2c5874 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -32,12 +32,18 @@
 
 MergeWorker::MergeWorker(const std::string& cow_device, const std::string& misc_name,
                          const std::string& base_path_merge,
-                         std::shared_ptr<SnapshotHandler> snapuserd)
-    : Worker(cow_device, misc_name, base_path_merge, snapuserd) {}
+                         std::shared_ptr<SnapshotHandler> snapuserd, uint32_t cow_op_merge_size)
+    : Worker(cow_device, misc_name, base_path_merge, snapuserd),
+      cow_op_merge_size_(cow_op_merge_size) {}
 
 int MergeWorker::PrepareMerge(uint64_t* source_offset, int* pending_ops,
                               std::vector<const CowOperation*>* replace_zero_vec) {
     int num_ops = *pending_ops;
+    // 0 indicates ro.virtual_ab.cow_op_merge_size was not set in the build
+    if (cow_op_merge_size_ != 0) {
+        num_ops = std::min(cow_op_merge_size_, static_cast<uint32_t>(*pending_ops));
+    }
+
     int nr_consecutive = 0;
     bool checkOrderedOp = (replace_zero_vec == nullptr);
     size_t num_blocks = 1;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h
index 478d4c8..a19352d 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h
@@ -23,7 +23,8 @@
 class MergeWorker : public Worker {
   public:
     MergeWorker(const std::string& cow_device, const std::string& misc_name,
-                const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd);
+                const std::string& base_path_merge, std::shared_ptr<SnapshotHandler> snapuserd,
+                uint32_t cow_op_merge_size);
     bool Run();
 
   private:
@@ -53,6 +54,7 @@
     // syscalls and fallback to synchronous I/O, we
     // don't want huge queue depth
     int queue_depth_ = 8;
+    uint32_t cow_op_merge_size_ = 0;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 05ba047..7c9a64e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -36,7 +36,8 @@
 SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
                                  std::string backing_device, std::string base_path_merge,
                                  std::shared_ptr<IBlockServerOpener> opener, int num_worker_threads,
-                                 bool use_iouring, bool perform_verification, bool o_direct) {
+                                 bool use_iouring, bool perform_verification, bool o_direct,
+                                 uint32_t cow_op_merge_size) {
     misc_name_ = std::move(misc_name);
     cow_device_ = std::move(cow_device);
     backing_store_device_ = std::move(backing_device);
@@ -46,6 +47,7 @@
     is_io_uring_enabled_ = use_iouring;
     perform_verification_ = perform_verification;
     o_direct_ = o_direct;
+    cow_op_merge_size_ = cow_op_merge_size;
 }
 
 bool SnapshotHandler::InitializeWorkers() {
@@ -60,12 +62,11 @@
 
         worker_threads_.push_back(std::move(wt));
     }
-
     merge_thread_ = std::make_unique<MergeWorker>(cow_device_, misc_name_, base_path_merge_,
-                                                  GetSharedPtr());
+                                                  GetSharedPtr(), cow_op_merge_size_);
 
     read_ahead_thread_ = std::make_unique<ReadAhead>(cow_device_, backing_store_device_, misc_name_,
-                                                     GetSharedPtr());
+                                                     GetSharedPtr(), cow_op_merge_size_);
 
     update_verify_ = std::make_unique<UpdateVerify>(misc_name_);
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index 9b7238a..c7de995 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -104,7 +104,8 @@
   public:
     SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
                     std::string base_path_merge, std::shared_ptr<IBlockServerOpener> opener,
-                    int num_workers, bool use_iouring, bool perform_verification, bool o_direct);
+                    int num_workers, bool use_iouring, bool perform_verification, bool o_direct,
+                    uint32_t cow_op_merge_size);
     bool InitCowDevice();
     bool Start();
 
@@ -247,7 +248,7 @@
     bool resume_merge_ = false;
     bool merge_complete_ = false;
     bool o_direct_ = false;
-
+    uint32_t cow_op_merge_size_ = 0;
     std::unique_ptr<UpdateVerify> update_verify_;
     std::shared_ptr<IBlockServerOpener> block_server_opener_;
 };
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 2baf20d..6b1ed0c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -18,6 +18,7 @@
 
 #include <pthread.h>
 
+#include "android-base/properties.h"
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -29,11 +30,13 @@
 using android::base::unique_fd;
 
 ReadAhead::ReadAhead(const std::string& cow_device, const std::string& backing_device,
-                     const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd) {
+                     const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd,
+                     uint32_t cow_op_merge_size) {
     cow_device_ = cow_device;
     backing_store_device_ = backing_device;
     misc_name_ = misc_name;
     snapuserd_ = snapuserd;
+    cow_op_merge_size_ = cow_op_merge_size;
 }
 
 void ReadAhead::CheckOverlap(const CowOperation* cow_op) {
@@ -62,8 +65,11 @@
                                     std::vector<uint64_t>& blocks,
                                     std::vector<const CowOperation*>& xor_op_vec) {
     int num_ops = *pending_ops;
-    int nr_consecutive = 0;
+    if (cow_op_merge_size_ != 0) {
+        num_ops = std::min(static_cast<int>(cow_op_merge_size_), *pending_ops);
+    }
 
+    int nr_consecutive = 0;
     bool is_ops_present = (!RAIterDone() && num_ops);
 
     if (!is_ops_present) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h
index d3ba126..4885c96 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h
@@ -35,7 +35,8 @@
 class ReadAhead {
   public:
     ReadAhead(const std::string& cow_device, const std::string& backing_device,
-              const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd);
+              const std::string& misc_name, std::shared_ptr<SnapshotHandler> snapuserd,
+              uint32_t cow_op_merge_size);
     bool RunThread();
 
   private:
@@ -106,6 +107,7 @@
     // syscalls and fallback to synchronous I/O, we
     // don't want huge queue depth
     int queue_depth_ = 8;
+    uint32_t cow_op_merge_size_;
     std::unique_ptr<struct io_uring> ring_;
 };
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index 0b881b6..c0af5c5 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -347,7 +347,8 @@
                                                               const std::string& cow_device_path,
                                                               const std::string& backing_device,
                                                               const std::string& base_path_merge,
-                                                              const bool o_direct) {
+                                                              const bool o_direct,
+                                                              uint32_t cow_op_merge_size) {
     // We will need multiple worker threads only during
     // device boot after OTA. For all other purposes,
     // one thread is sufficient. We don't want to consume
@@ -369,7 +370,8 @@
     auto opener = block_server_factory_->CreateOpener(misc_name);
 
     return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
-                                 opener, num_worker_threads, io_uring_enabled_, o_direct);
+                                 opener, num_worker_threads, io_uring_enabled_, o_direct,
+                                 cow_op_merge_size);
 }
 
 bool UserSnapshotServer::WaitForSocket() {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index d9cf97f..ceea36a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -87,7 +87,8 @@
                                               const std::string& cow_device_path,
                                               const std::string& backing_device,
                                               const std::string& base_path_merge,
-                                              bool o_direct = false);
+                                              bool o_direct = false,
+                                              uint32_t cow_op_merge_size = 0);
     bool StartHandler(const std::string& misc_name);
 
     void SetTerminating() { terminating_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 6d0ae3d..9042f2b 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -67,6 +67,7 @@
     std::string compression;
     int block_size;
     int num_threads;
+    uint32_t cow_op_merge_size;
 };
 
 class SnapuserdTestBase : public ::testing::TestWithParam<TestParam> {
@@ -708,9 +709,9 @@
     auto opener = factory->CreateOpener(system_device_ctrl_name_);
     handlers_->DisableVerification();
     const TestParam params = GetParam();
-    auto handler = handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path,
-                                         base_dev_->GetPath(), base_dev_->GetPath(), opener, 1,
-                                         params.io_uring, params.o_direct);
+    auto handler = handlers_->AddHandler(
+            system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(), base_dev_->GetPath(),
+            opener, 1, params.io_uring, params.o_direct, params.cow_op_merge_size);
     ASSERT_NE(handler, nullptr);
     ASSERT_NE(handler->snapuserd(), nullptr);
 #ifdef __ANDROID__
@@ -1227,9 +1228,9 @@
     ASSERT_NE(opener_, nullptr);
 
     const TestParam params = GetParam();
-    handler_ = std::make_shared<SnapshotHandler>(system_device_ctrl_name_, cow_system_->path,
-                                                 base_dev_->GetPath(), base_dev_->GetPath(),
-                                                 opener_, 1, false, false, params.o_direct);
+    handler_ = std::make_shared<SnapshotHandler>(
+            system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(), base_dev_->GetPath(),
+            opener_, 1, false, false, params.o_direct, params.cow_op_merge_size);
     ASSERT_TRUE(handler_->InitCowDevice());
     ASSERT_TRUE(handler_->InitializeWorkers());
 
@@ -1507,6 +1508,7 @@
                     param.num_threads = thread;
                     param.io_uring = io_uring;
                     param.o_direct = false;
+                    param.cow_op_merge_size = 0;
                     testParams.push_back(std::move(param));
                 }
             }
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 26af13b..54b469b 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -306,8 +306,8 @@
 
     if (!batt_anim_.run || now < next_screen_transition_) return;
 
-    // If battery level is not ready, keep checking in the defined time
-    if (health_info_.battery_level == 0 && health_info_.battery_status == BatteryStatus::UNKNOWN) {
+    // If battery status is not ready, keep checking in the defined time
+    if (health_info_.battery_status == BatteryStatus::UNKNOWN) {
         if (wait_batt_level_timestamp_ == 0) {
             // Set max delay time and skip drawing screen
             wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME;
@@ -317,7 +317,7 @@
             // Do nothing, keep waiting
             return;
         }
-        // If timeout and battery level is still not ready, draw unknown battery
+        // If timeout and battery status is still not ready, draw unknown battery
     }
 
     if (healthd_draw_ == nullptr) return;
diff --git a/init/Android.bp b/init/Android.bp
index c70a5de..ffb6380 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -330,7 +330,7 @@
     static_libs: ["libinit.microdroid"],
     cflags: ["-DMICRODROID=1"],
     no_full_install: true,
-    visibility: ["//packages/modules/Virtualization/microdroid"],
+    visibility: ["//packages/modules/Virtualization/build/microdroid"],
 }
 
 soong_config_module_type {
diff --git a/init/README.md b/init/README.md
index 11c4e1c..0bb26e8 100644
--- a/init/README.md
+++ b/init/README.md
@@ -499,9 +499,11 @@
    4. `late-fs` - Mount partitions marked as latemounted.
    5. `post-fs-data` - Mount and configure `/data`; set up encryption. `/metadata` is
       reformatted here if it couldn't mount in first-stage init.
-   6. `zygote-start` - Start the zygote.
-   7. `early-boot` - After zygote has started.
-   8. `boot` - After `early-boot` actions have completed.
+   6. `post-fs-data-checkpointed` - Triggered when vold has completed committing a checkpoint
+      after an OTA update. Not triggered if checkpointing is not needed or supported.
+   7. `zygote-start` - Start the zygote.
+   8. `early-boot` - After zygote has started.
+   9. `boot` - After `early-boot` actions have completed.
 
 Commands
 --------
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 2443b7c..b804c1b 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -1087,6 +1087,9 @@
     # Update dm-verity state and set partition.*.verified properties.
     verity_update_state
 
+on property:vold.checkpoint_committed=1
+    trigger post-fs-data-checkpointed
+
 # It is recommended to put unnecessary data/ initialization from post-fs-data
 # to start-zygote in device's init.rc to unblock zygote start.
 on zygote-start
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index b249013..aca59b6 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -44,7 +44,7 @@
         "libtrusty",
         "libkeymaster_messages",
         "libkeymaster3device",
-        "android.hardware.keymaster@3.0"
+        "android.hardware.keymaster@3.0",
     ],
 }
 
@@ -74,7 +74,7 @@
         "libtrusty",
         "libkeymaster_messages",
         "libkeymaster4",
-        "android.hardware.keymaster@4.0"
+        "android.hardware.keymaster@4.0",
     ],
 
     vintf_fragments: ["4.0/android.hardware.keymaster@4.0-service.trusty.xml"],
@@ -208,3 +208,36 @@
         "-Werror",
     ],
 }
+
+prebuilt_etc {
+    name: "rkp_uds_cert_test.xml",
+    vendor: true,
+    src: "set_uds_certs/rkp_uds_cert_test.xml",
+}
+
+cc_binary {
+    name: "trusty_rkp_set_uds_cert",
+    vendor: true,
+
+    srcs: [
+        "set_uds_certs/set_uds_certificates.cpp",
+        "ipc/trusty_keymaster_ipc.cpp",
+    ],
+
+    local_include_dirs: ["include"],
+
+    shared_libs: [
+        "libc",
+        "libcrypto",
+        "liblog",
+        "libtrusty",
+        "libhardware",
+        "libkeymaster_messages",
+        "libutils",
+        "libxml2",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
index 09f696b..822e933 100644
--- a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
+++ b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
@@ -78,6 +78,8 @@
     KM_SET_ATTESTATION_IDS = (0xc000 << KEYMASTER_REQ_SHIFT),
     KM_SET_ATTESTATION_IDS_KM3 = (0xc001 << KEYMASTER_REQ_SHIFT),
     KM_CONFIGURE_BOOT_PATCHLEVEL = (0xd000 << KEYMASTER_REQ_SHIFT),
+    KM_APPEND_UDS_CERT_CHAIN = (0xe0000 << KEYMASTER_REQ_SHIFT),
+    KM_CLEAR_UDS_CERT_CHAIN = (0xe0001 << KEYMASTER_REQ_SHIFT),
 };
 
 #ifdef __ANDROID__
diff --git a/trusty/keymaster/set_uds_certs/rkp_uds_cert_test.xml b/trusty/keymaster/set_uds_certs/rkp_uds_cert_test.xml
new file mode 100644
index 0000000..73b7b4c
--- /dev/null
+++ b/trusty/keymaster/set_uds_certs/rkp_uds_cert_test.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<PixelUdsCertificates>
+  <CertificateChain>
+    <NumberOfCertificates>3</NumberOfCertificates>
+    <Certificate format="pem">
+-----BEGIN CERTIFICATE-----
+MIIBWTCB3qADAgECAgUA3q2+7zAMBggqhkjOPQQDAwUAMBIxEDAOBgNVBAMMB0dT
+TUkxLjAwIhgPMjAyNDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowEjEQMA4G
+A1UEAwwHR1NNSTEuMDB2MBAGByqGSM49AgEGBSuBBAAiA2IABFsNPdsPmx2NKNiT
+7oReF36HTXjftRfGffYgPf/vEhrT7XLJ7dThkcb+OFYWYlOckdk3DRgqTNWQXsot
+UsZhwRveU8huEjOBO5+dwDiWPs+s9jSRAn5inlTqJ0YGAmdHRzAMBggqhkjOPQQD
+AwUAA2gAMGUCMQCuiJwmRWOgfWbqdSlnXfhCbphjdWc6sHelLkkM21vxQ3RZkhC2
+ERh90RA1rxB+XTgCMHZrYG3leS0PEoz5hUviv27LbBHBU6GeOzrS2e0VH0BMSNXN
+iP9Wnit+mJw58niEGw==
+-----END CERTIFICATE-----
+    </Certificate>
+    <Certificate format="pem">
+-----BEGIN CERTIFICATE-----
+MIIBWTCB3qADAgECAgUA3q2+7zAMBggqhkjOPQQDAwUAMBIxEDAOBgNVBAMMB0dT
+TUkxLjAwIhgPMjAyNDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowEjEQMA4G
+A1UEAwwHR1NNSTEuMDB2MBAGByqGSM49AgEGBSuBBAAiA2IABFsNPdsPmx2NKNiT
+7oReF36HTXjftRfGffYgPf/vEhrT7XLJ7dThkcb+OFYWYlOckdk3DRgqTNWQXsot
+UsZhwRveU8huEjOBO5+dwDiWPs+s9jSRAn5inlTqJ0YGAmdHRzAMBggqhkjOPQQD
+AwUAA2gAMGUCMQCuiJwmRWOgfWbqdSlnXfhCbphjdWc6sHelLkkM21vxQ3RZkhC2
+ERh90RA1rxB+XTgCMHZrYG3leS0PEoz5hUviv27LbBHBU6GeOzrS2e0VH0BMSNXN
+iP9Wnit+mJw58niEGw==
+-----END CERTIFICATE-----
+    </Certificate>
+    <Certificate format="pem">
+-----BEGIN CERTIFICATE-----
+MIIBWTCB3qADAgECAgUA3q2+7zAMBggqhkjOPQQDAwUAMBIxEDAOBgNVBAMMB0dT
+TUkxLjAwIhgPMjAyNDAxMDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowEjEQMA4G
+A1UEAwwHR1NNSTEuMDB2MBAGByqGSM49AgEGBSuBBAAiA2IABFsNPdsPmx2NKNiT
+7oReF36HTXjftRfGffYgPf/vEhrT7XLJ7dThkcb+OFYWYlOckdk3DRgqTNWQXsot
+UsZhwRveU8huEjOBO5+dwDiWPs+s9jSRAn5inlTqJ0YGAmdHRzAMBggqhkjOPQQD
+AwUAA2gAMGUCMQCuiJwmRWOgfWbqdSlnXfhCbphjdWc6sHelLkkM21vxQ3RZkhC2
+ERh90RA1rxB+XTgCMHZrYG3leS0PEoz5hUviv27LbBHBU6GeOzrS2e0VH0BMSNXN
+iP9Wnit+mJw58niEGw==
+-----END CERTIFICATE-----
+    </Certificate>
+  </CertificateChain>
+</PixelUdsCertificates>
diff --git a/trusty/keymaster/set_uds_certs/set_uds_certificates.cpp b/trusty/keymaster/set_uds_certs/set_uds_certificates.cpp
new file mode 100644
index 0000000..13356a9
--- /dev/null
+++ b/trusty/keymaster/set_uds_certs/set_uds_certificates.cpp
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2020 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 <errno.h>
+#include <getopt.h>
+#include <libxml/xmlreader.h>
+#include <openssl/pem.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <string>
+
+#include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
+
+static const char* _sopts = "h";
+static const struct option _lopts[] = {
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0},
+};
+
+static const char* usage =
+        "Usage: %s [options] xml-file\n"
+        "\n"
+        "options:\n"
+        "  -h, --help            prints this message and exit\n"
+        "\n";
+
+static void print_usage_and_exit(const char* prog, int code) {
+    fprintf(stderr, usage, prog);
+    exit(code);
+}
+
+static void parse_options(int argc, char** argv) {
+    int c;
+    int oidx = 0;
+
+    while (1) {
+        c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
+        if (c == -1) {
+            break; /* done */
+        }
+
+        switch (c) {
+            case 'h':
+                print_usage_and_exit(argv[0], EXIT_SUCCESS);
+                break;
+
+            default:
+                print_usage_and_exit(argv[0], EXIT_FAILURE);
+        }
+    }
+}
+
+struct AppendUdsCertificateRequest : public keymaster::KeymasterMessage {
+    explicit AppendUdsCertificateRequest(int32_t ver = keymaster::kDefaultMessageVersion)
+        : KeymasterMessage(ver) {}
+
+    size_t SerializedSize() const override { return cert_data.SerializedSize(); }
+    uint8_t* Serialize(uint8_t* buf, const uint8_t* end) const override {
+        return cert_data.Serialize(buf, end);
+    }
+    bool Deserialize(const uint8_t** buf_ptr, const uint8_t* end) override {
+        return cert_data.Deserialize(buf_ptr, end);
+    }
+
+    keymaster::Buffer cert_data;
+};
+
+struct ClearUdsCertificateRequest : public keymaster::KeymasterMessage {
+    explicit ClearUdsCertificateRequest(int32_t ver = keymaster::kDefaultMessageVersion)
+        : KeymasterMessage(ver) {}
+
+    size_t SerializedSize() const override { return 0; }
+    uint8_t* Serialize(uint8_t* buf, const uint8_t*) const override { return buf; }
+    bool Deserialize(const uint8_t**, const uint8_t*) override { return true; };
+};
+
+struct KeymasterNoResponse : public keymaster::KeymasterResponse{
+    explicit KeymasterNoResponse(int32_t ver = keymaster::kDefaultMessageVersion)
+        : keymaster::KeymasterResponse(ver) {}
+
+    size_t NonErrorSerializedSize() const override { return 0; }
+    uint8_t* NonErrorSerialize(uint8_t* buf, const uint8_t*) const override { return buf; }
+    bool NonErrorDeserialize(const uint8_t**, const uint8_t*) override { return true; }
+};
+
+struct AppendUdsCertificateResponse : public KeymasterNoResponse {};
+struct ClearUdsCertificateResponse : public KeymasterNoResponse {};
+
+static int set_uds_cert_bin(uint32_t cmd, const void* cert_data, size_t cert_data_size) {
+    int ret;
+
+    AppendUdsCertificateRequest req;
+    req.cert_data.Reinitialize(cert_data, cert_data_size);
+    AppendUdsCertificateResponse rsp;
+
+    ret = trusty_keymaster_send(cmd, req, &rsp);
+    if (ret) {
+        fprintf(stderr, "trusty_keymaster_send cmd 0x%x failed %d\n", cmd, ret);
+        return ret;
+    }
+
+    return 0;
+}
+
+static int set_uds_cert_pem(uint32_t cmd, const xmlChar* pemkey) {
+    int ret;
+    int sslret;
+
+    /* Convert from pem to binary */
+    BIO* bio = BIO_new_mem_buf(pemkey, xmlStrlen(pemkey));
+    if (!bio) {
+        fprintf(stderr, "BIO_new_mem_buf failed\n");
+        ERR_print_errors_fp(stderr);
+        return -1;
+    }
+
+    char* key_name;
+    char* key_header;
+    uint8_t* key;
+    long keylen;
+    sslret = PEM_read_bio(bio, &key_name, &key_header, &key, &keylen);
+    BIO_free(bio);
+
+    if (!sslret) {
+        fprintf(stderr, "PEM_read_bio failed\n");
+        ERR_print_errors_fp(stderr);
+        return -1;
+    }
+
+    /* Send key in binary format to trusty */
+    ret = set_uds_cert_bin(cmd, key, keylen);
+
+    OPENSSL_free(key_name);
+    OPENSSL_free(key_header);
+    OPENSSL_free(key);
+
+    return ret;
+}
+
+static int set_uds_cert(uint32_t cmd, const xmlChar* format, const xmlChar* str) {
+    int ret;
+
+    if (xmlStrEqual(format, BAD_CAST "pem")) {
+        ret = set_uds_cert_pem(cmd, str);
+    } else {
+        printf("unsupported key/cert format: %s\n", format);
+        return -1;
+    }
+    return ret;
+}
+
+// TODO: Guard by Production Mode
+static int clear_cert_chain() {
+    int ret;
+    ClearUdsCertificateRequest req;
+    ClearUdsCertificateResponse rsp;
+
+    ret = trusty_keymaster_send(KM_CLEAR_UDS_CERT_CHAIN, req, &rsp);
+    if (ret) {
+        fprintf(stderr, "%s: trusty_keymaster_send failed %d\n", __func__, ret);
+        return ret;
+    }
+    return 0;
+}
+
+static int process_xml(xmlTextReaderPtr xml) {
+    int ret;
+    const xmlChar* element = NULL;
+    const xmlChar* element_format = NULL;
+    bool isPixelUdsCert = false;
+
+    while ((ret = xmlTextReaderRead(xml)) == 1) {
+        int nodetype = xmlTextReaderNodeType(xml);
+        const xmlChar *name, *value;
+        name = xmlTextReaderConstName(xml);
+        switch (nodetype) {
+            case XML_READER_TYPE_ELEMENT:
+                element = name;
+                element_format = xmlTextReaderGetAttribute(xml, BAD_CAST "format");
+                if (isPixelUdsCert || xmlStrEqual(name, BAD_CAST "PixelUdsCertificates")) {
+                    // The first element name must be "PixelUdsCertificates"
+                    isPixelUdsCert = true;
+                } else {
+                    fprintf(stderr, "Not a PixelUdsCertificates: \"%s\"\n", name);
+                    return -1;
+                }
+                if (xmlStrEqual(name, BAD_CAST "CertificateChain")) {
+                    ret = clear_cert_chain();
+                    if (ret) {
+                        fprintf(stderr, "%s: Clear cert chain cmd failed, %d\n", element, ret);
+                        return ret;
+                    }
+                    printf("%s: Clear cert chain cmd done\n", element);
+                }
+                break;
+            case XML_READER_TYPE_TEXT:
+                value = xmlTextReaderConstValue(xml);
+                uint32_t cmd;
+                if (xmlStrEqual(element, BAD_CAST "Certificate")) {
+                    cmd = KM_APPEND_UDS_CERT_CHAIN;
+                } else {
+                    break;
+                }
+
+                ret = set_uds_cert(cmd, element_format, value);
+                if (ret) {
+                    fprintf(stderr, "%s, format %s: Cmd 0x%x failed, %d\n", element, element_format,
+                            cmd, ret);
+                    return ret;
+                }
+                printf("%s, format %s: Cmd 0x%x done\n", element, element_format, cmd);
+                break;
+            case XML_READER_TYPE_END_ELEMENT:
+                element = NULL;
+                break;
+        }
+    }
+    return ret;
+}
+
+static int parse_and_provision_xml_file(const char* filename) {
+    int ret;
+    xmlTextReaderPtr xml = xmlReaderForFile(filename, NULL, 0);
+    if (!xml) {
+        fprintf(stderr, "failed to open %s\n", filename);
+        return -1;
+    }
+
+    ret = process_xml(xml);
+
+    xmlFreeTextReader(xml);
+    if (ret != 0) {
+        fprintf(stderr, "Failed to parse or process %s\n", filename);
+        return -1;
+    }
+
+    return 0;
+}
+
+int main(int argc, char** argv) {
+    int ret = 0;
+
+    parse_options(argc, argv);
+    if (optind + 1 != argc) {
+        print_usage_and_exit(argv[0], EXIT_FAILURE);
+    }
+
+    ret = trusty_keymaster_connect();
+    if (ret) {
+        fprintf(stderr, "trusty_keymaster_connect failed %d\n", ret);
+        return EXIT_FAILURE;
+    }
+
+    ret = parse_and_provision_xml_file(argv[optind]);
+    if (ret) {
+        fprintf(stderr, "parse_and_provision_xml_file failed %d\n", ret);
+        trusty_keymaster_disconnect();
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 6d0c616..ca39f6a 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -55,7 +55,7 @@
 static struct storage_mapping_node* storage_mapping_head;
 
 /*
- * Property set to 1 after we have opened a file under ssdir_name. The backing
+ * Properties set to 1 after we have opened a file under ssdir_name. The backing
  * files for both TD and TDP are currently located under /data/vendor/ss and can
  * only be opened once userdata is mounted. This storageproxyd service is
  * restarted when userdata is available, which causes the Trusty storage service
@@ -64,11 +64,16 @@
  * ports will be available (although they may block if still being initialized),
  * and connections will not be reset after this point (assuming the
  * storageproxyd service stays running).
+ *
+ * fs_ready - secure storage is read-only (due to checkpointing after upgrade)
+ * fs_ready_rw - secure storage is readable and writable
  */
 #define FS_READY_PROPERTY "ro.vendor.trusty.storage.fs_ready"
+#define FS_READY_RW_PROPERTY "ro.vendor.trusty.storage.fs_ready_rw"
 
 /* has FS_READY_PROPERTY been set? */
-static bool fs_ready_initialized = false;
+static bool fs_ready_set = false;
+static bool fs_ready_rw_set = false;
 
 static enum sync_state fs_state;
 static enum sync_state fd_state[FD_TBL_SIZE];
@@ -80,6 +85,17 @@
    uint8_t data[MAX_READ_SIZE];
 }  read_rsp;
 
+static bool property_set_helper(const char* prop) {
+    int rc = property_set(prop, "1");
+    if (rc == 0) {
+        ALOGI("Set property %s\n", prop);
+    } else {
+        ALOGE("Could not set property %s, rc: %d\n", prop, rc);
+    }
+
+    return rc == 0;
+}
+
 static uint32_t insert_fd(int open_flags, int fd, struct storage_mapping_node* node) {
     uint32_t handle = fd;
 
@@ -520,12 +536,20 @@
     path = NULL;
 
     /* a backing file has been opened, notify any waiting init steps */
-    if (!fs_ready_initialized) {
-        rc = property_set(FS_READY_PROPERTY, "1");
-        if (rc == 0) {
-            fs_ready_initialized = true;
+    if (!fs_ready_set || !fs_ready_rw_set) {
+        bool is_checkpoint_active = false;
+
+        rc = is_data_checkpoint_active(&is_checkpoint_active);
+        if (rc != 0) {
+            ALOGE("is_data_checkpoint_active() failed (%d)\n", rc);
         } else {
-            ALOGE("Could not set property %s, rc: %d\n", FS_READY_PROPERTY, rc);
+            if (!fs_ready_rw_set && !is_checkpoint_active) {
+                fs_ready_rw_set = property_set_helper(FS_READY_RW_PROPERTY);
+            }
+
+            if (!fs_ready_set) {
+                fs_ready_set = property_set_helper(FS_READY_PROPERTY);
+            }
         }
     }