[automerger skipped] Backport of Win-specific suppression of potentially rogue construct that can engage am: c3483e4c8a am: 07995227fe -s ours am: b446f13c58 -s ours

am skip reason: Merged-In Ie1f82db2fb14e1bdd183bf8d3d93d5e9f974be5d with SHA-1 efe701798b is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/core/+/17586924

Change-Id: I2dc27f6e8b7b78bb5df0ac2fb8489cf2ae2a1d19
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index ee0aa58..9b5d2cd 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -187,11 +187,17 @@
                                  ", build may be missing broken or missing boot_devices");
     }
 
+    std::string slot_suffix = device->GetCurrentSlot();
+    uint32_t slot_number = SlotNumberForSlotSuffix(slot_suffix);
+
+    std::string other_slot_suffix;
+    if (!slot_suffix.empty()) {
+        other_slot_suffix = (slot_suffix == "_a") ? "_b" : "_a";
+    }
+
     // If we are unable to read the existing metadata, then the super partition
     // is corrupt. In this case we reflash the whole thing using the provided
     // image.
-    std::string slot_suffix = device->GetCurrentSlot();
-    uint32_t slot_number = SlotNumberForSlotSuffix(slot_suffix);
     std::unique_ptr<LpMetadata> old_metadata = ReadMetadata(super_name, slot_number);
     if (wipe || !old_metadata) {
         if (!FlashPartitionTable(super_name, *new_metadata.get())) {
@@ -203,11 +209,15 @@
     }
 
     std::set<std::string> partitions_to_keep;
+    bool virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
     for (const auto& partition : old_metadata->partitions) {
         // Preserve partitions in the other slot, but not the current slot.
         std::string partition_name = GetPartitionName(partition);
-        if (!slot_suffix.empty() && GetPartitionSlotSuffix(partition_name) == slot_suffix) {
-            continue;
+        if (!slot_suffix.empty()) {
+            auto part_suffix = GetPartitionSlotSuffix(partition_name);
+            if (part_suffix == slot_suffix || (part_suffix == other_slot_suffix && virtual_ab)) {
+                continue;
+            }
         }
         std::string group_name = GetPartitionGroupName(old_metadata->groups[partition.group_index]);
         // Skip partitions in the COW group
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index d0c89b9..f5ab557 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -413,17 +413,24 @@
     return fstab_result;
 }
 
-// Identify path to fstab file. Lookup is based on pattern
-// fstab.<fstab_suffix>, fstab.<hardware>, fstab.<hardware.platform> in
-// folders /odm/etc, vendor/etc, or /.
+// 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
+// in /odm/etc/ and /vendor/etc/, as well as in the locations where it may be in
+// the first stage ramdisk during early boot.  Previously, the first stage
+// ramdisk's copy of the fstab had to be located in the root directory, but now
+// the system/etc directory is supported too and is the preferred location.
 std::string GetFstabPath() {
     for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
         std::string suffix;
 
         if (!fs_mgr_get_boot_config(prop, &suffix)) continue;
 
-        for (const char* prefix :
-             {"/odm/etc/fstab.", "/vendor/etc/fstab.", "/fstab.", "/first_stage_ramdisk/fstab."}) {
+        for (const char* prefix : {// late-boot/post-boot locations
+                                   "/odm/etc/fstab.", "/vendor/etc/fstab.",
+                                   // early boot locations
+                                   "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.",
+                                   "/fstab.", "/first_stage_ramdisk/fstab."}) {
             std::string fstab_path = prefix + suffix;
             if (access(fstab_path.c_str(), F_OK) == 0) {
                 return fstab_path;
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index d09c6e9..bd432bb 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -108,6 +108,7 @@
     void MergeInterruptFixed(int duration);
     void MergeInterruptRandomly(int max_duration);
     void ReadDmUserBlockWithoutDaemon();
+    void ReadLastBlock();
 
     std::string snapshot_dev() const { return snapshot_dev_->path(); }
 
@@ -256,6 +257,73 @@
     }
 }
 
+void CowSnapuserdTest::ReadLastBlock() {
+    unique_fd rnd_fd;
+    total_base_size_ = BLOCK_SZ * 2;
+
+    base_fd_ = CreateTempFile("base_device", total_base_size_);
+    ASSERT_GE(base_fd_, 0);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+    for (size_t j = 0; j < ((total_base_size_) / BLOCK_SZ); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), BLOCK_SZ, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), BLOCK_SZ), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(total_base_size_);
+    loff_t offset = 0;
+
+    // Fill random data
+    for (size_t j = 0; j < (total_base_size_ / BLOCK_SZ); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, BLOCK_SZ, 0),
+                  true);
+
+        offset += BLOCK_SZ;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), BLOCK_SZ));
+    ASSERT_TRUE(writer.AddRawBlocks(1, (char*)random_buffer_1_.get() + BLOCK_SZ, BLOCK_SZ));
+
+    ASSERT_TRUE(writer.Finalize());
+
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+    InitCowDevice();
+
+    CreateDmUserDevice();
+    InitDaemon();
+
+    CreateSnapshotDevice();
+
+    unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
+    ASSERT_TRUE(snapshot_fd > 0);
+
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+    offset = 7680;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), 512, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)random_buffer_1_.get() + offset, 512), 0);
+}
+
 void CowSnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
@@ -1068,6 +1136,12 @@
     harness.Shutdown();
 }
 
+TEST(Snapuserd_Test, Snapshot_END_IO_TEST) {
+    CowSnapuserdTest harness;
+    harness.ReadLastBlock();
+    harness.Shutdown();
+}
+
 TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
     CowSnapuserdTest harness;
     ASSERT_TRUE(harness.SetupCopyOverlap_1());
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 15882b3..9bf5db1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -399,6 +399,7 @@
     FRIEND_TEST(SnapshotTest, MergeFailureCode);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
+    FRIEND_TEST(SnapshotUpdateTest, AddPartition);
     FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 0e36da1..4c94da2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -518,6 +518,13 @@
             break;
     }
 
+    if (mode == SnapshotStorageMode::Persistent && status.state() == SnapshotState::MERGING) {
+        LOG(ERROR) << "Snapshot: " << name
+                   << " has snapshot status Merging but mode set to Persistent."
+                   << " Changing mode to Snapshot-Merge.";
+        mode = SnapshotStorageMode::Merge;
+    }
+
     DmTable table;
     table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_device, mode,
                                     kSnapshotChunkSize);
@@ -886,6 +893,10 @@
     if (target_type) {
         *target_type = DeviceMapper::GetTargetType(target.spec);
     }
+    if (!status->error.empty()) {
+        LOG(ERROR) << "Snapshot: " << dm_name << " returned error code: " << status->error;
+        return false;
+    }
     return true;
 }
 
@@ -1456,7 +1467,7 @@
                                             std::vector<std::string>* snapuserd_argv) {
     LOG(INFO) << "Performing transition for snapuserd.";
 
-    // Don't use EnsuerSnapuserdConnected() because this is called from init,
+    // Don't use EnsureSnapuserdConnected() because this is called from init,
     // and attempting to do so will deadlock.
     if (!snapuserd_client_ && transition != InitTransition::SELINUX_DETACH) {
         snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
@@ -1513,8 +1524,15 @@
             continue;
         }
 
+        std::string source_device_name;
+        if (snapshot_status.old_partition_size() > 0) {
+            source_device_name = GetSourceDeviceName(snapshot);
+        } else {
+            source_device_name = GetBaseDeviceName(snapshot);
+        }
+
         std::string source_device;
-        if (!dm.GetDmDevicePathByName(GetSourceDeviceName(snapshot), &source_device)) {
+        if (!dm.GetDmDevicePathByName(source_device_name, &source_device)) {
             LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
             continue;
         }
@@ -2091,14 +2109,18 @@
     if (live_snapshot_status->compression_enabled()) {
         // Get the source device (eg the view of the partition from before it was resized).
         std::string source_device_path;
-        if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
-                             &source_device_path)) {
-            LOG(ERROR) << "Could not map source device for: " << cow_name;
-            return false;
-        }
+        if (live_snapshot_status->old_partition_size() > 0) {
+            if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
+                                 &source_device_path)) {
+                LOG(ERROR) << "Could not map source device for: " << cow_name;
+                return false;
+            }
 
-        auto source_device = GetSourceDeviceName(params.GetPartitionName());
-        created_devices.EmplaceBack<AutoUnmapDevice>(&dm, source_device);
+            auto source_device = GetSourceDeviceName(params.GetPartitionName());
+            created_devices.EmplaceBack<AutoUnmapDevice>(&dm, source_device);
+        } else {
+            source_device_path = base_path;
+        }
 
         if (!WaitForDevice(source_device_path, remaining_time)) {
             return false;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 6018643..7630efe 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -963,7 +963,7 @@
     }
 
     AssertionResult UnmapAll() {
-        for (const auto& name : {"sys", "vnd", "prd"}) {
+        for (const auto& name : {"sys", "vnd", "prd", "dlkm"}) {
             if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
                 return AssertionFailure() << "Cannot unmap " << name << "_a";
             }
@@ -2026,6 +2026,80 @@
     ASSERT_LT(res.required_size(), 40_MiB);
 }
 
+TEST_F(SnapshotUpdateTest, AddPartition) {
+    // OTA client blindly unmaps all partitions that are possibly mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+    }
+
+    group_->add_partition_names("dlkm");
+
+    auto dlkm = manifest_.add_partitions();
+    dlkm->set_partition_name("dlkm");
+    dlkm->set_estimate_cow_size(2_MiB);
+    SetSize(dlkm, 3_MiB);
+
+    // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
+    // fit in super, but not |prd|.
+    constexpr uint64_t partition_size = 3788_KiB;
+    SetSize(sys_, partition_size);
+    SetSize(vnd_, partition_size);
+    SetSize(prd_, partition_size);
+    SetSize(dlkm, partition_size);
+
+    AddOperationForPartitions({sys_, vnd_, prd_, dlkm});
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(name));
+    }
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+
+    ASSERT_TRUE(init->EnsureSnapuserdConnected());
+    init->set_use_first_stage_snapuserd(true);
+
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Check that the target partitions have the same content.
+    std::vector<std::string> partitions = {"sys_b", "vnd_b", "prd_b", "dlkm_b"};
+    for (const auto& name : partitions) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
+    for (const auto& name : partitions) {
+        ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init"));
+    }
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+    // Check that the target partitions have the same content after the merge.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name))
+                << "Content of " << name << " changes after the merge";
+    }
+}
+
 class AutoKill final {
   public:
     explicit AutoKill(pid_t pid) : pid_(pid) {}
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
index 682f9da..defb5bb 100644
--- a/fs_mgr/libsnapshot/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -287,16 +287,36 @@
     it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
                           Snapuserd::compare);
 
-    if (!(it != chunk_vec.end())) {
-        SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
-        return -1;
+    bool read_end_of_device = false;
+    if (it == chunk_vec.end()) {
+        // |-------|-------|-------|
+        // 0       1       2       3
+        //
+        // Block 0 - op 1
+        // Block 1 - op 2
+        // Block 2 - op 3
+        //
+        // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops.
+        //
+        // Each block is 4k bytes. Thus, the last block will span 8 sectors
+        // ranging till block 3 (However, block 3 won't be in chunk_vec as
+        // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector
+        // spanning between block 2 and block 3, we need to step back
+        // and get hold of the last element.
+        //
+        // Additionally, dm-snapshot makes sure that I/O request beyond block 3
+        // will not be routed to the daemon. Hence, it is safe to assume that
+        // if a sector is not available in the chunk_vec, the I/O falls in the
+        // end of region.
+        it = std::prev(chunk_vec.end());
+        read_end_of_device = true;
     }
 
     // We didn't find the required sector; hence find the previous sector
     // as lower_bound will gives us the value greater than
     // the requested sector
     if (it->first != sector) {
-        if (it != chunk_vec.begin()) {
+        if (it != chunk_vec.begin() && !read_end_of_device) {
             --it;
         }
 
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
index 9f83785..56053e0 100644
--- a/libprocessgroup/profiles/task_profiles_28.json
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -40,6 +40,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "HighPerformance",
       "Actions": [
         {
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
index 9f83785..52279b8 100644
--- a/libprocessgroup/profiles/task_profiles_29.json
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -53,6 +53,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "MaxPerformance",
       "Actions": [
         {
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
index 9f83785..56053e0 100644
--- a/libprocessgroup/profiles/task_profiles_30.json
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -40,6 +40,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "HighPerformance",
       "Actions": [
         {
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 540dcf4..6e293c7 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -86,8 +86,10 @@
 
         // A new thread will be in its parent's sched group by default,
         // so we just need to handle the background case.
+        // currently set to system_background group which is different
+        // from background group for app.
         if (prio >= ANDROID_PRIORITY_BACKGROUND) {
-            SetTaskProfiles(0, {"SCHED_SP_BACKGROUND"}, true);
+            SetTaskProfiles(0, {"SCHED_SP_SYSTEM"}, true);
         }
 
         if (name) {
@@ -313,7 +315,7 @@
     }
 
     if (pri >= ANDROID_PRIORITY_BACKGROUND) {
-        rc = SetTaskProfiles(tid, {"SCHED_SP_BACKGROUND"}, true) ? 0 : -1;
+        rc = SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
     } else if (curr_pri >= ANDROID_PRIORITY_BACKGROUND) {
         SchedPolicy policy = SP_FOREGROUND;
         // Change to the sched policy group of the process.
diff --git a/trusty/storage/interface/include/trusty/interface/storage.h b/trusty/storage/interface/include/trusty/interface/storage.h
index b196d88..3f1dcb8 100644
--- a/trusty/storage/interface/include/trusty/interface/storage.h
+++ b/trusty/storage/interface/include/trusty/interface/storage.h
@@ -112,26 +112,30 @@
 
 /**
  * enum storage_msg_flag - protocol-level flags in struct storage_msg
- * @STORAGE_MSG_FLAG_BATCH:             if set, command belongs to a batch transaction.
- *                                      No response will be sent by the server until
- *                                      it receives a command with this flag unset, at
- *                                      which point a cummulative result for all messages
- *                                      sent with STORAGE_MSG_FLAG_BATCH will be sent.
- *                                      This is only supported by the non-secure disk proxy
- *                                      server.
- * @STORAGE_MSG_FLAG_PRE_COMMIT:        if set, indicates that server need to commit
- *                                      pending changes before processing this message.
- * @STORAGE_MSG_FLAG_POST_COMMIT:       if set, indicates that server need to commit
- *                                      pending changes after processing this message.
- * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE: if set, indicates that server need to commit
- *                                      current transaction after processing this message.
- *                                      It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_BATCH:                 if set, command belongs to a batch transaction.
+ *                                          No response will be sent by the server until
+ *                                          it receives a command with this flag unset, at
+ *                                          which point a cumulative result for all messages
+ *                                          sent with STORAGE_MSG_FLAG_BATCH will be sent.
+ *                                          This is only supported by the non-secure disk proxy
+ *                                          server.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT:            if set, indicates that server need to commit
+ *                                          pending changes before processing this message.
+ * @STORAGE_MSG_FLAG_POST_COMMIT:           if set, indicates that server need to commit
+ *                                          pending changes after processing this message.
+ * @STORAGE_MSG_FLAG_TRANSACT_COMPLETE:     if set, indicates that server need to commit
+ *                                          current transaction after processing this message.
+ *                                          It is an alias for STORAGE_MSG_FLAG_POST_COMMIT.
+ * @STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT: if set, indicates that server needs to ensure
+ *                                          that there is not a pending checkpoint for
+ *                                          userdata before processing this message.
  */
 enum storage_msg_flag {
-	STORAGE_MSG_FLAG_BATCH = 0x1,
-	STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
-	STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
-	STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+    STORAGE_MSG_FLAG_BATCH = 0x1,
+    STORAGE_MSG_FLAG_PRE_COMMIT = 0x2,
+    STORAGE_MSG_FLAG_POST_COMMIT = 0x4,
+    STORAGE_MSG_FLAG_TRANSACT_COMPLETE = STORAGE_MSG_FLAG_POST_COMMIT,
+    STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT = 0x8,
 };
 
 /*
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index d67089f..38d8685 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -23,6 +23,7 @@
     vendor: true,
 
     srcs: [
+        "checkpoint_handling.cpp",
         "ipc.c",
         "rpmb.c",
         "storage.c",
@@ -30,12 +31,14 @@
     ],
 
     shared_libs: [
+        "libbase",
         "liblog",
         "libhardware_legacy",
     ],
     header_libs: ["libcutils_headers"],
 
     static_libs: [
+        "libfstab",
         "libtrustystorageinterface",
         "libtrusty",
     ],
diff --git a/trusty/storage/proxy/checkpoint_handling.cpp b/trusty/storage/proxy/checkpoint_handling.cpp
new file mode 100644
index 0000000..6c2fd36
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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 "checkpoint_handling.h"
+#include "log.h"
+
+#include <fstab/fstab.h>
+#include <cstring>
+#include <string>
+
+namespace {
+
+bool checkpointingDoneForever = false;
+
+}  // namespace
+
+int is_data_checkpoint_active(bool* active) {
+    if (!active) {
+        ALOGE("active out parameter is null");
+        return 0;
+    }
+
+    *active = false;
+
+    if (checkpointingDoneForever) {
+        return 0;
+    }
+
+    android::fs_mgr::Fstab procMounts;
+    bool success = android::fs_mgr::ReadFstabFromFile("/proc/mounts", &procMounts);
+    if (!success) {
+        ALOGE("Could not parse /proc/mounts\n");
+        /* Really bad. Tell the caller to abort the write. */
+        return -1;
+    }
+
+    android::fs_mgr::FstabEntry* dataEntry =
+            android::fs_mgr::GetEntryForMountPoint(&procMounts, "/data");
+    if (dataEntry == NULL) {
+        ALOGE("/data is not mounted yet\n");
+        return 0;
+    }
+
+    /* We can't handle e.g., ext4. Nothing we can do about it for now. */
+    if (dataEntry->fs_type != "f2fs") {
+        ALOGW("Checkpoint status not supported for filesystem %s\n", dataEntry->fs_type.c_str());
+        checkpointingDoneForever = true;
+        return 0;
+    }
+
+    /*
+     * The data entry looks like "... blah,checkpoint=disable:0,blah ...".
+     * checkpoint=disable means checkpointing is on (yes, arguably reversed).
+     */
+    size_t checkpointPos = dataEntry->fs_options.find("checkpoint=disable");
+    if (checkpointPos == std::string::npos) {
+        /* Assumption is that once checkpointing turns off, it stays off */
+        checkpointingDoneForever = true;
+    } else {
+        *active = true;
+    }
+
+    return 0;
+}
diff --git a/trusty/storage/proxy/checkpoint_handling.h b/trusty/storage/proxy/checkpoint_handling.h
new file mode 100644
index 0000000..f1bf27c
--- /dev/null
+++ b/trusty/storage/proxy/checkpoint_handling.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * is_data_checkpoint_active() - Check for an active, uncommitted checkpoint of
+ * /data. If a checkpoint is active, storage should not commit any
+ * rollback-protected writes to /data.
+ * @active: Out parameter that will be set to the result of the check.
+ *
+ * Return: 0 if active was set and is valid, non-zero otherwise.
+ */
+int is_data_checkpoint_active(bool* active);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index e230941..c690a28 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -26,6 +26,7 @@
 
 #include <cutils/android_filesystem_config.h>
 
+#include "checkpoint_handling.h"
 #include "ipc.h"
 #include "log.h"
 #include "rpmb.h"
@@ -130,6 +131,21 @@
         }
     }
 
+    if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT_CHECKPOINT) {
+        bool is_checkpoint_active = false;
+
+        rc = is_data_checkpoint_active(&is_checkpoint_active);
+        if (rc != 0) {
+            ALOGE("is_data_checkpoint_active failed in an unexpected way. Aborting.\n");
+            msg->result = STORAGE_ERR_GENERIC;
+            return ipc_respond(msg, NULL, 0);
+        } else if (is_checkpoint_active) {
+            ALOGE("Checkpoint in progress, dropping write ...\n");
+            msg->result = STORAGE_ERR_GENERIC;
+            return ipc_respond(msg, NULL, 0);
+        }
+    }
+
     switch (msg->cmd) {
         case STORAGE_FILE_DELETE:
             rc = storage_file_delete(msg, req, req_len);