Merge "Split fsverity_init in two phases."
diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 023a578..44a9214 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -34,7 +34,7 @@
     unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
     if (map_info != nullptr) {
       std::string* new_build_id = new std::string(build_id);
-      map_info->build_id = reinterpret_cast<uintptr_t>(new_build_id);
+      map_info->build_id = new_build_id;
     }
   }
 };
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 6294b3f..4c9fd9b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -656,7 +656,17 @@
 // If needed, we'll also enable (or disable) filesystem features as specified by
 // the fstab record.
 //
-static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry) {
+static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry,
+                                const std::string& alt_mount_point = "") {
+    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
+    // We need this because sometimes we have legacy symlinks that are
+    // lingering around and need cleaning up.
+    struct stat info;
+    if (lstat(mount_point.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) {
+        unlink(mount_point.c_str());
+    }
+    mkdir(mount_point.c_str(), 0755);
+
     int fs_stat = 0;
 
     if (is_extfs(entry.fs_type)) {
@@ -684,7 +694,7 @@
 
     if (entry.fs_mgr_flags.check ||
         (fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
-        check_fs(blk_device, entry.fs_type, entry.mount_point, &fs_stat);
+        check_fs(blk_device, entry.fs_type, mount_point, &fs_stat);
     }
 
     if (is_extfs(entry.fs_type) &&
@@ -729,13 +739,6 @@
 // sets the underlying block device to read-only if the mount is read-only.
 // See "man 2 mount" for return values.
 static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry) {
-    // We need this because sometimes we have legacy symlinks that are
-    // lingering around and need cleaning up.
-    struct stat info;
-    if (lstat(target.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) {
-        unlink(target.c_str());
-    }
-    mkdir(target.c_str(), 0755);
     errno = 0;
     unsigned long mountflags = entry.flags;
     int ret = 0;
@@ -1799,17 +1802,18 @@
 
 // wrapper to __mount() and expects a fully prepared fstab_rec,
 // unlike fs_mgr_do_mount which does more things with avb / verity etc.
-int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& mount_point) {
+int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) {
     // First check the filesystem if requested.
     if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) {
         LERROR << "Skipping mounting '" << entry.blk_device << "'";
     }
 
-    // Run fsck if needed
-    prepare_fs_for_mount(entry.blk_device, entry);
+    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
 
-    int ret =
-            __mount(entry.blk_device, mount_point.empty() ? entry.mount_point : mount_point, entry);
+    // Run fsck if needed
+    prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+
+    int ret = __mount(entry.blk_device, mount_point, entry);
     if (ret) {
       ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED;
     }
@@ -1868,7 +1872,14 @@
             continue;
         }
 
-        int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry);
+        // Now mount it where requested */
+        if (tmp_mount_point) {
+            mount_point = tmp_mount_point;
+        } else {
+            mount_point = fstab_entry.mount_point;
+        }
+
+        int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry, mount_point);
 
         if (fstab_entry.fs_mgr_flags.avb) {
             if (!avb_handle) {
@@ -1902,12 +1913,6 @@
             }
         }
 
-        // Now mount it where requested */
-        if (tmp_mount_point) {
-            mount_point = tmp_mount_point;
-        } else {
-            mount_point = fstab_entry.mount_point;
-        }
         int retry_count = 2;
         while (retry_count-- > 0) {
             if (!__mount(n_blk_device, mount_point, fstab_entry)) {
@@ -1919,7 +1924,7 @@
                 mount_errors++;
                 fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
                 // try again after fsck
-                check_fs(n_blk_device, fstab_entry.fs_type, fstab_entry.mount_point, &fs_stat);
+                check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat);
             }
         }
         log_fs_stat(fstab_entry.blk_device, fs_stat);
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 34049d4..e32fde0 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -563,6 +563,7 @@
         "libsnapshot_snapuserd",
         "libcutils_sockets",
         "libz",
+	"libfs_mgr",
         "libdm",
     ],
     header_libs: [
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 4db6584..35020f4 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -525,6 +525,165 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, ClusterTest) {
+    CowOptions options;
+    options.cluster_ops = 4;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+
+    ASSERT_TRUE(writer->AddLabel(4));
+
+    ASSERT_TRUE(writer->AddZeroBlocks(50, 2));  // Cluster split in middle
+
+    ASSERT_TRUE(writer->AddLabel(5));
+
+    ASSERT_TRUE(writer->AddCopy(5, 6));
+
+    // Cluster split
+
+    ASSERT_TRUE(writer->AddLabel(6));
+
+    ASSERT_TRUE(writer->Finalize());  // No data for cluster, so no cluster split needed
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    // Read back all ops
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    StringSink sink;
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
+
+    iter->Next();
+    sink.Reset();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 4);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowZeroOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowZeroOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 5);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowCopyOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 6);
+
+    iter->Next();
+
+    ASSERT_TRUE(iter->Done());
+}
+
+TEST_F(CowTest, ClusterAppendTest) {
+    CowOptions options;
+    options.cluster_ops = 3;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(writer->AddLabel(50));
+    ASSERT_TRUE(writer->Finalize());  // Adds a cluster op, should be dropped on append
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 50));
+
+    std::string data2 = "More data!";
+    data2.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
+    ASSERT_TRUE(writer->Finalize());  // Adds a cluster op
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    struct stat buf;
+    ASSERT_EQ(fstat(cow_->fd, &buf), 0);
+    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+
+    // Read back both operations, plus cluster op at end
+    CowReader reader;
+    uint64_t label;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_TRUE(reader.GetLastLabel(&label));
+    ASSERT_EQ(label, 50);
+
+    StringSink sink;
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 50);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data2);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+
+    ASSERT_TRUE(iter->Done());
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp
index 49ba11f..0753c49 100644
--- a/fs_mgr/libsnapshot/cow_format.cpp
+++ b/fs_mgr/libsnapshot/cow_format.cpp
@@ -35,6 +35,10 @@
         os << "kCowFooterOp,  ";
     else if (op.type == kCowLabelOp)
         os << "kCowLabelOp,   ";
+    else if (op.type == kCowClusterOp)
+        os << "kCowClusterOp  ";
+    else if (op.type == kCowFooterOp)
+        os << "kCowFooterOp  ";
     else
         os << (int)op.type << "?,";
     os << "compression:";
@@ -52,11 +56,35 @@
     return os;
 }
 
-int64_t GetNextOpOffset(const CowOperation& op) {
-    if (op.type == kCowReplaceOp)
+int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_ops) {
+    if (op.type == kCowClusterOp) {
+        return op.source;
+    } else if (op.type == kCowReplaceOp && cluster_ops == 0) {
         return op.data_length;
-    else
+    } else {
         return 0;
+    }
+}
+
+int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_ops) {
+    if (op.type == kCowClusterOp) {
+        return cluster_ops * sizeof(CowOperation);
+    } else if (cluster_ops == 0) {
+        return sizeof(CowOperation);
+    } else {
+        return 0;
+    }
+}
+
+bool IsMetadataOp(const CowOperation& op) {
+    switch (op.type) {
+        case kCowLabelOp:
+        case kCowClusterOp:
+        case kCowFooterOp:
+            return true;
+        default:
+            return false;
+    }
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 6b7ada5..3fbc5f3 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -81,6 +81,24 @@
                    << sizeof(CowFooter);
         return false;
     }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
 
     if ((header_.major_version != kCowVersionMajor) ||
         (header_.minor_version != kCowVersionMinor)) {
@@ -103,45 +121,64 @@
     }
 
     auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
+    uint64_t current_op_num = 0;
+    uint64_t cluster_ops = header_.cluster_ops ?: 1;
+    bool done = false;
 
-    // Alternating op and data
-    while (true) {
-        ops_buffer->emplace_back();
-        if (!android::base::ReadFully(fd_, &ops_buffer->back(), sizeof(CowOperation))) {
+    // Alternating op clusters and data
+    while (!done) {
+        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
+        if (to_add == 0) break;
+        ops_buffer->resize(current_op_num + to_add);
+        if (!android::base::ReadFully(fd_, &ops_buffer->data()[current_op_num],
+                                      to_add * sizeof(CowOperation))) {
             PLOG(ERROR) << "read op failed";
             return false;
         }
+        // Parse current cluster to find start of next cluster
+        while (current_op_num < ops_buffer->size()) {
+            auto& current_op = ops_buffer->data()[current_op_num];
+            current_op_num++;
+            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
 
-        auto& current_op = ops_buffer->back();
-        off_t offs = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
-        if (offs < 0) {
+            if (current_op.type == kCowClusterOp) {
+                break;
+            } else if (current_op.type == kCowLabelOp) {
+                last_label_ = {current_op.source};
+
+                // If we reach the requested label, stop reading.
+                if (label && label.value() == current_op.source) {
+                    done = true;
+                    break;
+                }
+            } else if (current_op.type == kCowFooterOp) {
+                footer_.emplace();
+                CowFooter* footer = &footer_.value();
+                memcpy(&footer_->op, &current_op, sizeof(footer->op));
+                off_t offs = lseek(fd_.get(), pos, SEEK_SET);
+                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+                    PLOG(ERROR) << "lseek next op failed";
+                    return false;
+                }
+                if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
+                    LOG(ERROR) << "Could not read COW footer";
+                    return false;
+                }
+
+                // Drop the footer from the op stream.
+                current_op_num--;
+                done = true;
+                break;
+            }
+        }
+
+        // Position for next cluster read
+        off_t offs = lseek(fd_.get(), pos, SEEK_SET);
+        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
             PLOG(ERROR) << "lseek next op failed";
             return false;
         }
-        pos = static_cast<uint64_t>(offs);
-
-        if (current_op.type == kCowLabelOp) {
-            last_label_ = {current_op.source};
-
-            // If we reach the requested label, stop reading.
-            if (label && label.value() == current_op.source) {
-                break;
-            }
-        } else if (current_op.type == kCowFooterOp) {
-            footer_.emplace();
-
-            CowFooter* footer = &footer_.value();
-            memcpy(&footer_->op, &current_op, sizeof(footer->op));
-
-            if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
-                LOG(ERROR) << "Could not read COW footer";
-                return false;
-            }
-
-            // Drop the footer from the op stream.
-            ops_buffer->pop_back();
-            break;
-        }
+        ops_buffer->resize(current_op_num);
     }
 
     // To successfully parse a COW file, we need either:
@@ -189,29 +226,136 @@
         LOG(INFO) << "No COW Footer, recovered data";
     }
 
-    if (header_.num_merge_ops > 0) {
-        uint64_t merge_ops = header_.num_merge_ops;
-        uint64_t metadata_ops = 0;
-        uint64_t current_op_num = 0;
-
-        CHECK(ops_buffer->size() >= merge_ops);
-        while (merge_ops) {
-            auto& current_op = ops_buffer->data()[current_op_num];
-            if (current_op.type == kCowLabelOp || current_op.type == kCowFooterOp) {
-                metadata_ops += 1;
-            } else {
-                merge_ops -= 1;
-            }
-            current_op_num += 1;
-        }
-        ops_buffer->erase(ops_buffer.get()->begin(),
-                          ops_buffer.get()->begin() + header_.num_merge_ops + metadata_ops);
-    }
-
     ops_ = ops_buffer;
     return true;
 }
 
+void CowReader::InitializeMerge() {
+    uint64_t num_copy_ops = 0;
+
+    // Remove all the metadata operations
+    ops_->erase(std::remove_if(ops_.get()->begin(), ops_.get()->end(),
+                               [](CowOperation& op) { return IsMetadataOp(op); }),
+                ops_.get()->end());
+
+    // We will re-arrange the vector in such a way that
+    // kernel can batch merge. Ex:
+    //
+    // Existing COW format; All the copy operations
+    // are at the beginning.
+    // =======================================
+    // Copy-op-1    - cow_op->new_block = 1
+    // Copy-op-2    - cow_op->new_block = 2
+    // Copy-op-3    - cow_op->new_block = 3
+    // Replace-op-4 - cow_op->new_block = 6
+    // Replace-op-5 - cow_op->new_block = 4
+    // Replace-op-6 - cow_op->new_block = 8
+    // Replace-op-7 - cow_op->new_block = 9
+    // Zero-op-8    - cow_op->new_block = 7
+    // Zero-op-9    - cow_op->new_block = 5
+    // =======================================
+    //
+    // First find the operation which isn't a copy-op
+    // and then sort all the operations in descending order
+    // with the key being cow_op->new_block (source block)
+    //
+    // The data-structure will look like:
+    //
+    // =======================================
+    // Copy-op-1    - cow_op->new_block = 1
+    // Copy-op-2    - cow_op->new_block = 2
+    // Copy-op-3    - cow_op->new_block = 3
+    // Replace-op-7 - cow_op->new_block = 9
+    // Replace-op-6 - cow_op->new_block = 8
+    // Zero-op-8    - cow_op->new_block = 7
+    // Replace-op-4 - cow_op->new_block = 6
+    // Zero-op-9    - cow_op->new_block = 5
+    // Replace-op-5 - cow_op->new_block = 4
+    // =======================================
+    //
+    // Daemon will read the above data-structure in reverse-order
+    // when reading metadata. Thus, kernel will get the metadata
+    // in the following order:
+    //
+    // ========================================
+    // Replace-op-5 - cow_op->new_block = 4
+    // Zero-op-9    - cow_op->new_block = 5
+    // Replace-op-4 - cow_op->new_block = 6
+    // Zero-op-8    - cow_op->new_block = 7
+    // Replace-op-6 - cow_op->new_block = 8
+    // Replace-op-7 - cow_op->new_block = 9
+    // Copy-op-3    - cow_op->new_block = 3
+    // Copy-op-2    - cow_op->new_block = 2
+    // Copy-op-1    - cow_op->new_block = 1
+    // ===========================================
+    //
+    // When merging begins, kernel will start from the last
+    // metadata which was read: In the above format, Copy-op-1
+    // will be the first merge operation.
+    //
+    // Now, batching of the merge operations happens only when
+    // 1: origin block numbers in the base device are contiguous
+    // (cow_op->new_block) and,
+    // 2: cow block numbers which are assigned by daemon in ReadMetadata()
+    // are contiguous. These are monotonically increasing numbers.
+    //
+    // When both (1) and (2) are true, kernel will batch merge the operations.
+    // However, we do not want copy operations to be batch merged as
+    // a crash or system reboot during an overlapping copy can drive the device
+    // to a corrupted state. Hence, merging of copy operations should always be
+    // done as a individual 4k block. In the above case, since the
+    // cow_op->new_block numbers are contiguous, we will ensure that the
+    // cow block numbers assigned in ReadMetadata() for these respective copy
+    // operations are not contiguous forcing kernel to issue merge for each
+    // copy operations without batch merging.
+    //
+    // For all the other operations viz. Replace and Zero op, the cow block
+    // numbers assigned by daemon will be contiguous allowing kernel to batch
+    // merge.
+    //
+    // The final format after assiging COW block numbers by the daemon will
+    // look something like:
+    //
+    // =========================================================
+    // Replace-op-5 - cow_op->new_block = 4  cow-block-num = 2
+    // Zero-op-9    - cow_op->new_block = 5  cow-block-num = 3
+    // Replace-op-4 - cow_op->new_block = 6  cow-block-num = 4
+    // Zero-op-8    - cow_op->new_block = 7  cow-block-num = 5
+    // Replace-op-6 - cow_op->new_block = 8  cow-block-num = 6
+    // Replace-op-7 - cow_op->new_block = 9  cow-block-num = 7
+    // Copy-op-3    - cow_op->new_block = 3  cow-block-num = 9
+    // Copy-op-2    - cow_op->new_block = 2  cow-block-num = 11
+    // Copy-op-1    - cow_op->new_block = 1  cow-block-num = 13
+    // ==========================================================
+    //
+    // Merge sequence will look like:
+    //
+    // Merge-1 - Copy-op-1
+    // Merge-2 - Copy-op-2
+    // Merge-3 - Copy-op-3
+    // Merge-4 - Batch-merge {Replace-op-7, Replace-op-6, Zero-op-8,
+    //                        Replace-op-4, Zero-op-9, Replace-op-5 }
+    //==============================================================
+
+    for (uint64_t i = 0; i < ops_->size(); i++) {
+        auto& current_op = ops_->data()[i];
+        if (current_op.type != kCowCopyOp) {
+            break;
+        }
+        num_copy_ops += 1;
+    }
+
+    std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
+              [](CowOperation& op1, CowOperation& op2) -> bool {
+                  return op1.new_block > op2.new_block;
+              });
+
+    if (header_.num_merge_ops > 0) {
+        CHECK(ops_->size() >= header_.num_merge_ops);
+        ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
+    }
+}
+
 bool CowReader::GetHeader(CowHeader* header) {
     *header = header_;
     return true;
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 5483fd0..16d9313 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -12,10 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <fcntl.h>
 #include <linux/fs.h>
+#include <linux/memfd.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <chrono>
 #include <iostream>
@@ -24,7 +28,9 @@
 
 #include <android-base/file.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
 #include <gtest/gtest.h>
+#include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
 #include <libsnapshot/snapuserd_client.h>
@@ -37,111 +43,203 @@
 using android::base::unique_fd;
 using LoopDevice = android::dm::LoopDevice;
 using namespace std::chrono_literals;
+using namespace android::dm;
+using namespace std;
 
-class SnapuserdTest : public ::testing::Test {
-  protected:
-    void SetUp() override {
-        // TODO: Daemon started through first stage
-        // init does not have permission to read files
-        // from /data/nativetest.
-        system("setenforce 0");
-        cow_system_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_system_->fd, 0) << strerror(errno);
+static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
 
-        cow_product_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_product_->fd, 0) << strerror(errno);
+class TempDevice {
+  public:
+    TempDevice(const std::string& name, const DmTable& table)
+        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
+        valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
+    }
+    TempDevice(TempDevice&& other) noexcept
+        : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
+        other.valid_ = false;
+    }
+    ~TempDevice() {
+        if (valid_) {
+            dm_.DeleteDevice(name_);
+        }
+    }
+    bool Destroy() {
+        if (!valid_) {
+            return false;
+        }
+        valid_ = false;
+        return dm_.DeleteDevice(name_);
+    }
+    const std::string& path() const { return path_; }
+    const std::string& name() const { return name_; }
+    bool valid() const { return valid_; }
 
-        cow_system_1_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_system_1_->fd, 0) << strerror(errno);
+    TempDevice(const TempDevice&) = delete;
+    TempDevice& operator=(const TempDevice&) = delete;
 
-        cow_product_1_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_product_1_->fd, 0) << strerror(errno);
-
-        std::string path = android::base::GetExecutableDirectory();
-
-        system_a_ = std::make_unique<TemporaryFile>(path);
-        ASSERT_GE(system_a_->fd, 0) << strerror(errno);
-
-        product_a_ = std::make_unique<TemporaryFile>(path);
-        ASSERT_GE(product_a_->fd, 0) << strerror(errno);
-
-        size_ = 100_MiB;
+    TempDevice& operator=(TempDevice&& other) noexcept {
+        name_ = other.name_;
+        valid_ = other.valid_;
+        other.valid_ = false;
+        return *this;
     }
 
-    void TearDown() override {
-        system("setenforce 1");
-        cow_system_ = nullptr;
-        cow_product_ = nullptr;
-
-        cow_system_1_ = nullptr;
-        cow_product_1_ = nullptr;
-    }
-
-    std::unique_ptr<TemporaryFile> system_a_;
-    std::unique_ptr<TemporaryFile> product_a_;
-
-    std::unique_ptr<LoopDevice> system_a_loop_;
-    std::unique_ptr<LoopDevice> product_a_loop_;
-
-    std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<TemporaryFile> cow_product_;
-
-    std::unique_ptr<TemporaryFile> cow_system_1_;
-    std::unique_ptr<TemporaryFile> cow_product_1_;
-
-    unique_fd sys_fd_;
-    unique_fd product_fd_;
-    size_t size_;
-
-    int system_blksize_;
-    int product_blksize_;
-    std::string system_device_name_;
-    std::string product_device_name_;
-
-    std::string system_device_ctrl_name_;
-    std::string product_device_ctrl_name_;
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_;
-    std::unique_ptr<uint8_t[]> random_buffer_2_;
-    std::unique_ptr<uint8_t[]> zero_buffer_;
-    std::unique_ptr<uint8_t[]> system_buffer_;
-    std::unique_ptr<uint8_t[]> product_buffer_;
-
-    void Init();
-    void InitCowDevices();
-    void InitDaemon();
-    void CreateCowDevice(std::unique_ptr<TemporaryFile>& cow);
-    void CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow);
-    void CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow);
-    void DeleteDmUser(std::unique_ptr<TemporaryFile>& cow, std::string snapshot_device);
-    void StartSnapuserdDaemon();
-    void CreateSnapshotDevices();
-    void SwitchSnapshotDevices();
-
-    std::string GetSystemControlPath() {
-        return std::string("/dev/dm-user/") + system_device_ctrl_name_;
-    }
-    std::string GetProductControlPath() {
-        return std::string("/dev/dm-user/") + product_device_ctrl_name_;
-    }
-
-    void TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer);
-    std::unique_ptr<SnapuserdClient> client_;
+  private:
+    DeviceMapper& dm_;
+    std::string name_;
+    std::string path_;
+    bool valid_;
 };
 
-void SnapuserdTest::Init() {
+class CowSnapuserdTest final {
+  public:
+    bool Setup();
+    bool Merge();
+    void ValidateMerge();
+    void ReadSnapshotDeviceAndValidate();
+    void Shutdown();
+
+    std::string snapshot_dev() const { return snapshot_dev_->path(); }
+
+    static const uint64_t kSectorSize = 512;
+
+  private:
+    void SetupImpl();
+    void MergeImpl();
+    void CreateCowDevice();
+    void CreateBaseDevice();
+    void InitCowDevice();
+    void SetDeviceControlName();
+    void InitDaemon();
+    void CreateDmUserDevice();
+    void StartSnapuserdDaemon();
+    void CreateSnapshotDevice();
+    unique_fd CreateTempFile(const std::string& name, size_t size);
+
+    unique_ptr<LoopDevice> base_loop_;
+    unique_ptr<TempDevice> dmuser_dev_;
+    unique_ptr<TempDevice> snapshot_dev_;
+
+    std::string system_device_ctrl_name_;
+    std::string system_device_name_;
+
+    unique_fd base_fd_;
+    std::unique_ptr<TemporaryFile> cow_system_;
+    std::unique_ptr<SnapuserdClient> client_;
+    std::unique_ptr<uint8_t[]> orig_buffer_;
+    std::unique_ptr<uint8_t[]> merged_buffer_;
+    bool setup_ok_ = false;
+    bool merge_ok_ = false;
+    size_t size_ = 1_MiB;
+    int cow_num_sectors_;
+    int total_base_size_;
+};
+
+unique_fd CowSnapuserdTest::CreateTempFile(const std::string& name, size_t size) {
+    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
+    if (fd < 0) {
+        return {};
+    }
+    if (size) {
+        if (ftruncate(fd, size) < 0) {
+            perror("ftruncate");
+            return {};
+        }
+        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+            perror("fcntl");
+            return {};
+        }
+    }
+    return fd;
+}
+
+void CowSnapuserdTest::Shutdown() {
+    ASSERT_TRUE(client_->StopSnapuserd());
+    ASSERT_TRUE(snapshot_dev_->Destroy());
+    ASSERT_TRUE(dmuser_dev_->Destroy());
+}
+
+bool CowSnapuserdTest::Setup() {
+    SetupImpl();
+    return setup_ok_;
+}
+
+void CowSnapuserdTest::StartSnapuserdDaemon() {
+    pid_t pid = fork();
+    ASSERT_GE(pid, 0);
+    if (pid == 0) {
+        std::string arg0 = "/system/bin/snapuserd";
+        std::string arg1 = kSnapuserdSocketTest;
+        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+        ASSERT_GE(execv(arg0.c_str(), argv), 0);
+    } else {
+        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
+        ASSERT_NE(client_, nullptr);
+    }
+}
+
+void CowSnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
-    loff_t offset = 0;
-    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    total_base_size_ = (size_ * 4);
+    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);
 
-    random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-    random_buffer_2_ = std::make_unique<uint8_t[]>(size_);
-    system_buffer_ = std::make_unique<uint8_t[]>(size_);
-    product_buffer_ = std::make_unique<uint8_t[]>(size_);
-    zero_buffer_ = std::make_unique<uint8_t[]>(size_);
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+}
+
+void CowSnapuserdTest::ReadSnapshotDeviceAndValidate() {
+    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[]>(size_);
+
+    // COPY
+    loff_t offset = 0;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
+
+    // ZERO
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+}
+
+void CowSnapuserdTest::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+    std::unique_ptr<uint8_t[]> random_buffer_2_ = std::make_unique<uint8_t[]>(size_);
 
     // Fill random data
     for (size_t j = 0; j < (size_ / 1_MiB); j++) {
@@ -154,77 +252,32 @@
         offset += 1_MiB;
     }
 
-    for (size_t j = 0; j < (800_MiB / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(system_a_->fd, random_buffer.get(), 1_MiB), true);
-    }
-
-    for (size_t j = 0; j < (800_MiB / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(product_a_->fd, random_buffer.get(), 1_MiB), true);
-    }
-
-    // Create loopback devices
-    system_a_loop_ = std::make_unique<LoopDevice>(std::string(system_a_->path), 10s);
-    ASSERT_TRUE(system_a_loop_->valid());
-
-    product_a_loop_ = std::make_unique<LoopDevice>(std::string(product_a_->path), 10s);
-    ASSERT_TRUE(product_a_loop_->valid());
-
-    sys_fd_.reset(open(system_a_loop_->device().c_str(), O_RDONLY));
-    ASSERT_TRUE(sys_fd_ > 0);
-
-    product_fd_.reset(open(product_a_loop_->device().c_str(), O_RDONLY));
-    ASSERT_TRUE(product_fd_ > 0);
-
-    // Read from system partition from offset 0 of size 100MB
-    ASSERT_EQ(ReadFullyAtOffset(sys_fd_, system_buffer_.get(), size_, 0), true);
-
-    // Read from product partition from offset 0 of size 100MB
-    ASSERT_EQ(ReadFullyAtOffset(product_fd_, product_buffer_.get(), size_, 0), true);
-}
-
-void SnapuserdTest::CreateCowDevice(std::unique_ptr<TemporaryFile>& cow) {
-    //================Create a COW file with the following operations===========
-    //
-    // Create COW file which is gz compressed
-    //
-    // 0-100 MB of replace operation with random data
-    // 100-200 MB of copy operation
-    // 200-300 MB of zero operation
-    // 300-400 MB of replace operation with random data
-
     CowOptions options;
     options.compression = "gz";
     CowWriter writer(options);
 
-    ASSERT_TRUE(writer.Initialize(cow->fd));
-
-    // Write 100MB random data to COW file which is gz compressed from block 0
-    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
 
     size_t num_blocks = size_ / options.block_size;
-    size_t blk_start_copy = num_blocks;
-    size_t blk_end_copy = blk_start_copy + num_blocks;
+    size_t blk_src_copy = num_blocks;
+    size_t blk_end_copy = blk_src_copy + num_blocks;
     size_t source_blk = 0;
 
-    // Copy blocks - source_blk starts from 0 as snapuserd
-    // has to read from block 0 in system_a partition
-    //
-    // This initializes copy operation from block 0 of size 100 MB from
-    // /dev/block/mapper/system_a or product_a
-    for (size_t i = blk_start_copy; i < blk_end_copy; i++) {
-        ASSERT_TRUE(writer.AddCopy(i, source_blk));
+    while (source_blk < num_blocks) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
         source_blk += 1;
+        blk_src_copy += 1;
     }
 
-    size_t blk_zero_copy_start = blk_end_copy;
+    ASSERT_EQ(blk_src_copy, blk_end_copy);
+
+    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+
+    size_t blk_zero_copy_start = source_blk + num_blocks;
     size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
 
-    // 100 MB filled with zeroes
     ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
 
-    // Final 100MB filled with random data which is gz compressed
     size_t blk_random2_replace_start = blk_zero_copy_end;
 
     ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_2_.get(), size_));
@@ -232,233 +285,133 @@
     // Flush operations
     ASSERT_TRUE(writer.Finalize());
 
-    ASSERT_EQ(lseek(cow->fd, 0, SEEK_SET), 0);
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    std::string zero_buffer(size_, 0);
+
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
+    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_2_.get(), size_);
 }
 
-void SnapuserdTest::CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow) {
-    std::string cmd;
+void CowSnapuserdTest::InitCowDevice() {
+    cow_num_sectors_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
+                                              base_loop_->device());
+    ASSERT_NE(cow_num_sectors_, 0);
+}
+
+void CowSnapuserdTest::SetDeviceControlName() {
     system_device_name_.clear();
     system_device_ctrl_name_.clear();
 
-    std::string str(cow->path);
+    std::string str(cow_system_->path);
     std::size_t found = str.find_last_of("/\\");
     ASSERT_NE(found, std::string::npos);
     system_device_name_ = str.substr(found + 1);
 
-    // Create a control device
     system_device_ctrl_name_ = system_device_name_ + "-ctrl";
-    cmd = "dmctl create " + system_device_name_ + " user 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_device_ctrl_name_;
-
-    system(cmd.c_str());
 }
 
-void SnapuserdTest::DeleteDmUser(std::unique_ptr<TemporaryFile>& cow, std::string snapshot_device) {
-    std::string cmd;
+void CowSnapuserdTest::CreateDmUserDevice() {
+    DmTable dmuser_table;
+    ASSERT_TRUE(dmuser_table.AddTarget(
+            std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
+    ASSERT_TRUE(dmuser_table.valid());
 
-    cmd = "dmctl delete " + snapshot_device;
-    system(cmd.c_str());
+    dmuser_dev_ = std::make_unique<TempDevice>(system_device_name_, dmuser_table);
+    ASSERT_TRUE(dmuser_dev_->valid());
+    ASSERT_FALSE(dmuser_dev_->path().empty());
 
-    cmd.clear();
-
-    std::string str(cow->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    std::string device_name = str.substr(found + 1);
-
-    cmd = "dmctl delete " + device_name;
-
-    system(cmd.c_str());
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
 }
 
-void SnapuserdTest::CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow) {
-    std::string cmd;
-    product_device_name_.clear();
-    product_device_ctrl_name_.clear();
-
-    std::string str(cow->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    product_device_name_ = str.substr(found + 1);
-    product_device_ctrl_name_ = product_device_name_ + "-ctrl";
-    cmd = "dmctl create " + product_device_name_ + " user 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_device_ctrl_name_;
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::InitCowDevices() {
-    system_blksize_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                             system_a_loop_->device());
-    ASSERT_NE(system_blksize_, 0);
-
-    product_blksize_ = client_->InitDmUserCow(product_device_ctrl_name_, cow_product_->path,
-                                              product_a_loop_->device());
-    ASSERT_NE(product_blksize_, 0);
-}
-
-void SnapuserdTest::InitDaemon() {
+void CowSnapuserdTest::InitDaemon() {
     bool ok = client_->AttachDmUser(system_device_ctrl_name_);
     ASSERT_TRUE(ok);
-
-    ok = client_->AttachDmUser(product_device_ctrl_name_);
-    ASSERT_TRUE(ok);
 }
 
-void SnapuserdTest::StartSnapuserdDaemon() {
-    ASSERT_TRUE(EnsureSnapuserdStarted());
+void CowSnapuserdTest::CreateSnapshotDevice() {
+    DmTable snap_table;
+    ASSERT_TRUE(snap_table.AddTarget(std::make_unique<DmTargetSnapshot>(
+            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
+            SnapshotStorageMode::Persistent, 8)));
+    ASSERT_TRUE(snap_table.valid());
 
-    client_ = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
-    ASSERT_NE(client_, nullptr);
+    snap_table.set_readonly(true);
+
+    snapshot_dev_ = std::make_unique<TempDevice>("cowsnapuserd-test-dm-snapshot", snap_table);
+    ASSERT_TRUE(snapshot_dev_->valid());
+    ASSERT_FALSE(snapshot_dev_->path().empty());
 }
 
-void SnapuserdTest::CreateSnapshotDevices() {
-    std::string cmd;
+void CowSnapuserdTest::SetupImpl() {
+    CreateBaseDevice();
+    CreateCowDevice();
 
-    cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_a_loop_->device();
-    cmd += " /dev/block/mapper/" + system_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-
-    cmd.clear();
-
-    cmd = "dmctl create product-snapshot -ro snapshot 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_a_loop_->device();
-    cmd += " /dev/block/mapper/" + product_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::SwitchSnapshotDevices() {
-    std::string cmd;
-
-    cmd = "dmctl create system-snapshot-1 -ro snapshot 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_a_loop_->device();
-    cmd += " /dev/block/mapper/" + system_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-
-    cmd.clear();
-
-    cmd = "dmctl create product-snapshot-1 -ro snapshot 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_a_loop_->device();
-    cmd += " /dev/block/mapper/" + product_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer) {
-    loff_t offset = 0;
-    // std::unique_ptr<uint8_t[]> buffer = std::move(buf);
-
-    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
-
-    //================Start IO operation on dm-snapshot device=================
-    // This will test the following paths:
-    //
-    // 1: IO path for all three operations and interleaving of operations.
-    // 2: Merging of blocks in kernel during metadata read
-    // 3: Bulk IO issued by kernel duing merge operation
-
-    // Read from snapshot device of size 100MB from offset 0. This tests the
-    // 1st replace operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
-    // op)->decompress_cow->return
-
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Update the offset
-    offset += size_;
-
-    // Compare data with random_buffer_1_.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1_.get(), size_), 0);
-
-    // Clear the buffer
-    memset(snapuserd_buffer.get(), 0, size_);
-
-    // Read from snapshot device of size 100MB from offset 100MB. This tests the
-    // copy operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_from_(system_a/product_a) partition
-    // (copy op) -> return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Update the offset
-    offset += size_;
-
-    // Compare data with buffer.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), buffer.get(), size_), 0);
-
-    // Read from snapshot device of size 100MB from offset 200MB. This tests the
-    // zero operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->fill_memory_with_zero
-    // (zero op) -> return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Compare data with zero filled buffer
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), zero_buffer_.get(), size_), 0);
-
-    // Update the offset
-    offset += size_;
-
-    // Read from snapshot device of size 100MB from offset 300MB. This tests the
-    // final replace operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
-    // op)->decompress_cow->return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Compare data with random_buffer_2_.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_2_.get(), size_), 0);
-}
-
-TEST_F(SnapuserdTest, ReadWrite) {
-    unique_fd snapshot_fd;
-
-    Init();
-
-    CreateCowDevice(cow_system_);
-    CreateCowDevice(cow_product_);
+    SetDeviceControlName();
 
     StartSnapuserdDaemon();
-    InitCowDevices();
+    InitCowDevice();
 
-    CreateSystemDmUser(cow_system_);
-    CreateProductDmUser(cow_product_);
-
+    CreateDmUserDevice();
     InitDaemon();
 
-    CreateSnapshotDevices();
+    CreateSnapshotDevice();
+    setup_ok_ = true;
+}
 
-    snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-    TestIO(snapshot_fd, system_buffer_);
+bool CowSnapuserdTest::Merge() {
+    MergeImpl();
+    return merge_ok_;
+}
 
-    snapshot_fd.reset(open("/dev/block/mapper/product-snapshot", O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-    TestIO(snapshot_fd, product_buffer_);
+void CowSnapuserdTest::MergeImpl() {
+    DmTable merge_table;
+    ASSERT_TRUE(merge_table.AddTarget(std::make_unique<DmTargetSnapshot>(
+            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
+            SnapshotStorageMode::Merge, 8)));
+    ASSERT_TRUE(merge_table.valid());
+    ASSERT_EQ(total_base_size_ / kSectorSize, merge_table.num_sectors());
 
-    snapshot_fd.reset(-1);
+    DeviceMapper& dm = DeviceMapper::Instance();
+    ASSERT_TRUE(dm.LoadTableAndActivate("cowsnapuserd-test-dm-snapshot", merge_table));
 
-    DeleteDmUser(cow_system_, "system-snapshot");
-    DeleteDmUser(cow_product_, "product-snapshot");
+    while (true) {
+        vector<DeviceMapper::TargetInfo> status;
+        ASSERT_TRUE(dm.GetTableStatus("cowsnapuserd-test-dm-snapshot", &status));
+        ASSERT_EQ(status.size(), 1);
+        ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")),
+                  0);
 
-    ASSERT_TRUE(client_->StopSnapuserd());
+        DmTargetSnapshot::Status merge_status;
+        ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status));
+        ASSERT_TRUE(merge_status.error.empty());
+        if (merge_status.sectors_allocated == merge_status.metadata_sectors) {
+            break;
+        }
+
+        std::this_thread::sleep_for(250ms);
+    }
+
+    merge_ok_ = true;
+}
+
+void CowSnapuserdTest::ValidateMerge() {
+    merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
+              true);
+    ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
+}
+
+TEST(Snapuserd_Test, Snapshot) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.ReadSnapshotDeviceAndValidate();
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 957ba35..8535252 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -90,8 +90,10 @@
     header_.minor_version = kCowVersionMinor;
     header_.header_size = sizeof(CowHeader);
     header_.footer_size = sizeof(CowFooter);
+    header_.op_size = sizeof(CowOperation);
     header_.block_size = options_.block_size;
     header_.num_merge_ops = 0;
+    header_.cluster_ops = options_.cluster_ops;
     footer_ = {};
     footer_.op.data_length = 64;
     footer_.op.type = kCowFooterOp;
@@ -108,6 +110,10 @@
         LOG(ERROR) << "unrecognized compression: " << options_.compression;
         return false;
     }
+    if (options_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
     return true;
 }
 
@@ -165,6 +171,19 @@
     return OpenForAppend(label);
 }
 
+void CowWriter::InitPos() {
+    next_op_pos_ = sizeof(header_);
+    cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
+    if (header_.cluster_ops) {
+        next_data_pos_ = next_op_pos_ + cluster_size_;
+    } else {
+        next_data_pos_ = next_op_pos_ + sizeof(CowOperation);
+    }
+    ops_.clear();
+    current_cluster_size_ = 0;
+    current_data_size_ = 0;
+}
+
 bool CowWriter::OpenForWrite() {
     // This limitation is tied to the data field size in CowOperation.
     if (header_.block_size > std::numeric_limits<uint16_t>::max()) {
@@ -184,7 +203,7 @@
         return false;
     }
 
-    next_op_pos_ = sizeof(header_);
+    InitPos();
     return true;
 }
 
@@ -197,13 +216,14 @@
     }
 
     options_.block_size = header_.block_size;
+    options_.cluster_ops = header_.cluster_ops;
 
     // Reset this, since we're going to reimport all operations.
     footer_.op.num_ops = 0;
-    next_op_pos_ = sizeof(header_);
-    ops_.resize(0);
+    InitPos();
 
     auto iter = reader->GetOpIter();
+
     while (!iter->Done()) {
         AddOperation(iter->Get());
         iter->Next();
@@ -234,14 +254,12 @@
 
 bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
-    uint64_t pos;
     CHECK(!merge_in_progress_);
     for (size_t i = 0; i < size / header_.block_size; i++) {
         CowOperation op = {};
         op.type = kCowReplaceOp;
         op.new_block = new_block_start + i;
-        GetDataPos(&pos);
-        op.source = pos + sizeof(op);
+        op.source = next_data_pos_;
 
         if (compression_) {
             auto data = Compress(iter, header_.block_size);
@@ -293,6 +311,14 @@
     return WriteOperation(op) && Sync();
 }
 
+bool CowWriter::EmitCluster() {
+    CowOperation op = {};
+    op.type = kCowClusterOp;
+    // Next cluster starts after remainder of current cluster and the next data block.
+    op.source = current_data_size_ + cluster_size_ - current_cluster_size_ - sizeof(CowOperation);
+    return WriteOperation(op);
+}
+
 std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
     switch (compression_) {
         case kCowCompressGz: {
@@ -345,11 +371,23 @@
 }
 
 bool CowWriter::Finalize() {
-    footer_.op.ops_size = ops_.size();
-    uint64_t pos;
+    auto continue_cluster_size = current_cluster_size_;
+    auto continue_data_size = current_data_size_;
+    auto continue_data_pos = next_data_pos_;
+    auto continue_op_pos = next_op_pos_;
+    auto continue_size = ops_.size();
+    bool extra_cluster = false;
 
-    if (!GetDataPos(&pos)) {
-        PLOG(ERROR) << "failed to get file position";
+    // Footer should be at the end of a file, so if there is data after the current block, end it
+    // and start a new cluster.
+    if (cluster_size_ && current_data_size_ > 0) {
+        EmitCluster();
+        extra_cluster = true;
+    }
+
+    footer_.op.ops_size = ops_.size();
+    if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+        PLOG(ERROR) << "Failed to seek to footer position.";
         return false;
     }
     memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
@@ -364,16 +402,24 @@
         return false;
     }
 
-    // Re-position for any subsequent writes.
-    if (lseek(fd_.get(), pos, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek ops failed";
-        return false;
+    // Reposition for additional Writing
+    if (extra_cluster) {
+        current_cluster_size_ = continue_cluster_size;
+        current_data_size_ = continue_data_size;
+        next_data_pos_ = continue_data_pos;
+        next_op_pos_ = continue_op_pos;
+        ops_.resize(continue_size);
     }
+
     return Sync();
 }
 
 uint64_t CowWriter::GetCowSize() {
-    return next_op_pos_ + sizeof(footer_);
+    if (current_data_size_ > 0) {
+        return next_data_pos_ + sizeof(footer_);
+    } else {
+        return next_op_pos_ + sizeof(footer_);
+    }
 }
 
 bool CowWriter::GetDataPos(uint64_t* pos) {
@@ -387,6 +433,15 @@
 }
 
 bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t size) {
+    // If there isn't room for this op and the cluster end op, end the current cluster
+    if (cluster_size_ && op.type != kCowClusterOp &&
+        cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) {
+        if (!EmitCluster()) return false;
+    }
+    if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek failed for writing operation.";
+        return false;
+    }
     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&op), sizeof(op))) {
         return false;
     }
@@ -399,11 +454,26 @@
 
 void CowWriter::AddOperation(const CowOperation& op) {
     footer_.op.num_ops++;
-    next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op);
+
+    if (op.type == kCowClusterOp) {
+        current_cluster_size_ = 0;
+        current_data_size_ = 0;
+    } else if (header_.cluster_ops) {
+        current_cluster_size_ += sizeof(op);
+        current_data_size_ += op.data_length;
+    }
+
+    next_data_pos_ += op.data_length + GetNextDataOffset(op, header_.cluster_ops);
+    next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op, header_.cluster_ops);
     ops_.insert(ops_.size(), reinterpret_cast<const uint8_t*>(&op), sizeof(op));
 }
 
 bool CowWriter::WriteRawData(const void* data, size_t size) {
+    if (lseek(fd_.get(), next_data_pos_, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek failed for writing data.";
+        return false;
+    }
+
     if (!android::base::WriteFully(fd_, data, size)) {
         return false;
     }
@@ -421,7 +491,7 @@
     return true;
 }
 
-bool CowWriter::CommitMerge(int merged_ops) {
+bool CowWriter::CommitMerge(int merged_ops, bool sync) {
     CHECK(merge_in_progress_);
     header_.num_merge_ops += merged_ops;
 
@@ -436,7 +506,11 @@
         return false;
     }
 
-    return Sync();
+    // Sync only for merging of copy operations.
+    if (sync) {
+        return Sync();
+    }
+    return true;
 }
 
 bool CowWriter::Truncate(off_t length) {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 80766ff..797b8ef 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -57,9 +57,15 @@
     // Size of footer struct
     uint16_t footer_size;
 
+    // Size of op struct
+    uint16_t op_size;
+
     // The size of block operations, in bytes.
     uint32_t block_size;
 
+    // The number of ops to cluster together. 0 For no clustering. Cannot be 1.
+    uint32_t cluster_ops;
+
     // Tracks merge operations completed
     uint64_t num_merge_ops;
 } __attribute__((packed));
@@ -113,13 +119,15 @@
     // For copy operations, this is a block location in the source image.
     //
     // For replace operations, this is a byte offset within the COW's data
-    // section (eg, not landing within the header or metadata). It is an
+    // sections (eg, not landing within the header or metadata). It is an
     // absolute position within the image.
     //
     // For zero operations (replace with all zeroes), this is unused and must
     // be zero.
     //
     // For Label operations, this is the value of the applied label.
+    //
+    // For Cluster operations, this is the length of the following data region
     uint64_t source;
 } __attribute__((packed));
 
@@ -129,6 +137,7 @@
 static constexpr uint8_t kCowReplaceOp = 2;
 static constexpr uint8_t kCowZeroOp = 3;
 static constexpr uint8_t kCowLabelOp = 4;
+static constexpr uint8_t kCowClusterOp = 5;
 static constexpr uint8_t kCowFooterOp = -1;
 
 static constexpr uint8_t kCowCompressNone = 0;
@@ -142,7 +151,10 @@
 
 std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
 
-int64_t GetNextOpOffset(const CowOperation& op);
+int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_size);
+int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_size);
+
+bool IsMetadataOp(const CowOperation& op);
 
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index be69225..62b54f9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -140,6 +140,8 @@
 
     void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; }
 
+    void InitializeMerge();
+
   private:
     bool ParseOps(std::optional<uint64_t> label);
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index e9320b0..fd43cce 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -33,6 +33,9 @@
 
     // Maximum number of blocks that can be written.
     std::optional<uint64_t> max_blocks;
+
+    // Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
+    uint32_t cluster_ops = 0;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -98,7 +101,7 @@
     bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
 
     void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header);
-    bool CommitMerge(int merged_ops);
+    bool CommitMerge(int merged_ops, bool sync);
 
     bool Finalize() override;
 
@@ -111,6 +114,7 @@
     virtual bool EmitLabel(uint64_t label) override;
 
   private:
+    bool EmitCluster();
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
@@ -120,6 +124,7 @@
     bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
     void AddOperation(const CowOperation& op);
     std::basic_string<uint8_t> Compress(const void* data, size_t length);
+    void InitPos();
 
     bool SetFd(android::base::borrowed_fd fd);
     bool Sync();
@@ -132,6 +137,10 @@
     CowFooter footer_{};
     int compression_ = 0;
     uint64_t next_op_pos_ = 0;
+    uint64_t next_data_pos_ = 0;
+    uint32_t cluster_size_ = 0;
+    uint32_t current_cluster_size_ = 0;
+    uint64_t current_data_size_ = 0;
     bool is_dev_null_ = false;
     bool merge_in_progress_ = false;
     bool is_block_device_ = false;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
index 24b44fa..eec6d45 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
@@ -81,7 +81,7 @@
     bool ReadDiskExceptions(chunk_t chunk, size_t size);
     bool ReadData(chunk_t chunk, size_t size);
     bool IsChunkIdMetadata(chunk_t chunk);
-    chunk_t GetNextAllocatableChunkId(chunk_t chunk);
+    chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
 
     bool ProcessReplaceOp(const CowOperation* cow_op);
     bool ProcessCopyOp(const CowOperation* cow_op);
@@ -90,8 +90,7 @@
     loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
                                int* unmerged_exceptions);
     int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                             int unmerged_exceptions);
-    bool AdvanceMergedOps(int merged_ops_cur_iter);
+                             int unmerged_exceptions, bool* copy_op);
     bool ProcessMergeComplete(chunk_t chunk, void* buffer);
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
     chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 6046bad..5ad61f3 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -14,6 +14,7 @@
 // limitations under the License.
 //
 #include <stdio.h>
+#include <unistd.h>
 
 #include <iostream>
 #include <string>
@@ -34,7 +35,11 @@
     }
 }
 
-static bool Inspect(const std::string& path) {
+static void usage(void) {
+    LOG(ERROR) << "Usage: inspect_cow [-s] <COW_FILE>";
+}
+
+static bool Inspect(const std::string& path, bool silent) {
     android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
     if (fd < 0) {
         PLOG(ERROR) << "open failed: " << path;
@@ -52,19 +57,29 @@
         LOG(ERROR) << "could not get header: " << path;
         return false;
     }
+    CowFooter footer;
+    bool has_footer = false;
+    if (reader.GetFooter(&footer)) has_footer = true;
 
-    std::cout << "Major version: " << header.major_version << "\n";
-    std::cout << "Minor version: " << header.minor_version << "\n";
-    std::cout << "Header size: " << header.header_size << "\n";
-    std::cout << "Footer size: " << header.footer_size << "\n";
-    std::cout << "Block size: " << header.block_size << "\n";
-    std::cout << "\n";
+    if (!silent) {
+        std::cout << "Major version: " << header.major_version << "\n";
+        std::cout << "Minor version: " << header.minor_version << "\n";
+        std::cout << "Header size: " << header.header_size << "\n";
+        std::cout << "Footer size: " << header.footer_size << "\n";
+        std::cout << "Block size: " << header.block_size << "\n";
+        std::cout << "\n";
+        if (has_footer) {
+            std::cout << "Total Ops size: " << footer.op.ops_size << "\n";
+            std::cout << "Number of Ops: " << footer.op.num_ops << "\n";
+            std::cout << "\n";
+        }
+    }
 
     auto iter = reader.GetOpIter();
     while (!iter->Done()) {
         const CowOperation& op = iter->Get();
 
-        std::cout << op << "\n";
+        if (!silent) std::cout << op << "\n";
 
         iter->Next();
     }
@@ -76,14 +91,25 @@
 }  // namespace android
 
 int main(int argc, char** argv) {
+    int ch;
+    bool silent = false;
+    while ((ch = getopt(argc, argv, "s")) != -1) {
+        switch (ch) {
+            case 's':
+                silent = true;
+                break;
+            default:
+                android::snapshot::usage();
+        }
+    }
     android::base::InitLogging(argv, android::snapshot::MyLogger);
 
-    if (argc < 2) {
-        LOG(ERROR) << "Usage: inspect_cow <COW_FILE>";
+    if (argc < optind + 1) {
+        android::snapshot::usage();
         return 1;
     }
 
-    if (!android::snapshot::Inspect(argv[1])) {
+    if (!android::snapshot::Inspect(argv[optind], silent)) {
         return 1;
     }
     return 0;
diff --git a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
index d0b8f52..6a5754d 100644
--- a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
+++ b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
@@ -53,6 +53,7 @@
 
 DEFINE_string(source_tf, "", "Source target files (dir or zip file) for incremental payloads");
 DEFINE_string(compression, "gz", "Compression type to use (none or gz)");
+DEFINE_uint32(cluster_ops, 0, "Number of Cow Ops per cluster (0 or >1)");
 
 void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
               unsigned int, const char* message) {
@@ -189,6 +190,7 @@
     CowOptions options;
     options.block_size = kBlockSize;
     options.compression = FLAGS_compression;
+    options.cluster_ops = FLAGS_cluster_ops;
 
     writer_ = std::make_unique<CowWriter>(options);
     if (!writer_->Initialize(std::move(fd))) {
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index b56d879..5ee8e25 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -90,7 +90,7 @@
     op_iter_ = cow_->GetOpIter();
     while (!op_iter_->Done()) {
         const CowOperation* op = &op_iter_->Get();
-        if (op->type == kCowLabelOp || op->type == kCowFooterOp) {
+        if (IsMetadataOp(*op)) {
             op_iter_->Next();
             continue;
         }
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 49e6c3d..34254a3 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -31,7 +31,7 @@
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
-static constexpr size_t PAYLOAD_SIZE = (1UL << 16);
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
 
 static_assert(PAYLOAD_SIZE >= BLOCK_SIZE);
 
@@ -156,11 +156,11 @@
     size_t read_size = size;
     bool ret = true;
     chunk_t chunk_key = chunk;
-    uint32_t stride;
-    lldiv_t divresult;
 
-    // Size should always be aligned
-    CHECK((read_size & (BLOCK_SIZE - 1)) == 0);
+    if (!((read_size & (BLOCK_SIZE - 1)) == 0)) {
+        SNAP_LOG(ERROR) << "ReadData - unaligned read_size: " << read_size;
+        return false;
+    }
 
     while (read_size > 0) {
         const CowOperation* cow_op = chunk_map_[chunk_key];
@@ -204,24 +204,8 @@
         // are contiguous
         chunk_key += 1;
 
-        if (cow_op->type == kCowCopyOp) CHECK(read_size == 0);
-
-        // This is similar to the way when chunk IDs were assigned
-        // in ReadMetadata().
-        //
-        // Skip if the chunk id represents a metadata chunk.
-        stride = exceptions_per_area_ + 1;
-        divresult = lldiv(chunk_key, stride);
-        if (divresult.rem == NUM_SNAPSHOT_HDR_CHUNKS) {
-            // Crossing exception boundary. Kernel will never
-            // issue IO which is spanning between a data chunk
-            // and a metadata chunk. This should be perfectly aligned.
-            //
-            // Since the input read_size is 4k aligned, we will
-            // always end up reading all 256 data chunks in one area.
-            // Thus, every multiple of 4K IO represents 256 data chunks
+        if (cow_op->type == kCowCopyOp) {
             CHECK(read_size == 0);
-            break;
         }
     }
 
@@ -330,7 +314,7 @@
 }
 
 int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                                    int unmerged_exceptions) {
+                                    int unmerged_exceptions, bool* copy_op) {
     int merged_ops_cur_iter = 0;
 
     // Find the operations which are merged in this cycle.
@@ -346,6 +330,12 @@
         if (cow_de->new_chunk != 0) {
             merged_ops_cur_iter += 1;
             offset += sizeof(struct disk_exception);
+            const CowOperation* cow_op = chunk_map_[cow_de->new_chunk];
+            CHECK(cow_op != nullptr);
+            CHECK(cow_op->new_block == cow_de->old_chunk);
+            if (cow_op->type == kCowCopyOp) {
+                *copy_op = true;
+            }
             // zero out to indicate that operation is merged.
             cow_de->old_chunk = 0;
             cow_de->new_chunk = 0;
@@ -367,44 +357,12 @@
         }
     }
 
+    if (*copy_op) {
+        CHECK(merged_ops_cur_iter == 1);
+    }
     return merged_ops_cur_iter;
 }
 
-bool Snapuserd::AdvanceMergedOps(int merged_ops_cur_iter) {
-    // Advance the merge operation pointer in the
-    // vector.
-    //
-    // cowop_iter_ is already initialized in ReadMetadata(). Just resume the
-    // merge process
-    while (!cowop_iter_->Done() && merged_ops_cur_iter) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
-        CHECK(cow_op != nullptr);
-
-        if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
-            cowop_iter_->Next();
-            continue;
-        }
-
-        if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
-              cow_op->type == kCowCopyOp)) {
-            SNAP_LOG(ERROR) << "Unknown operation-type found during merge: " << cow_op->type;
-            return false;
-        }
-
-        merged_ops_cur_iter -= 1;
-        SNAP_LOG(DEBUG) << "Merge op found of type " << cow_op->type
-                        << "Pending-merge-ops: " << merged_ops_cur_iter;
-        cowop_iter_->Next();
-    }
-
-    if (cowop_iter_->Done()) {
-        CHECK(merged_ops_cur_iter == 0);
-        SNAP_LOG(DEBUG) << "All cow operations merged successfully in this cycle";
-    }
-
-    return true;
-}
-
 bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) {
     uint32_t stride = exceptions_per_area_ + 1;
     CowHeader header;
@@ -423,21 +381,47 @@
     int unmerged_exceptions = 0;
     loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions);
 
-    int merged_ops_cur_iter =
-            GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions);
+    bool copy_op = false;
+    // Check if the merged operation is a copy operation. If so, then we need
+    // to explicitly sync the metadata before initiating the next merge.
+    // For ex: Consider a following sequence of copy operations in the COW file:
+    //
+    // Op-1: Copy 2 -> 3
+    // Op-2: Copy 1 -> 2
+    // Op-3: Copy 5 -> 10
+    //
+    // Op-1 and Op-2 are overlapping copy operations. The merge sequence will
+    // look like:
+    //
+    // Merge op-1: Copy 2 -> 3
+    // Merge op-2: Copy 1 -> 2
+    // Merge op-3: Copy 5 -> 10
+    //
+    // Now, let's say we have a crash _after_ Merge op-2; Block 2 contents would
+    // have been over-written by Block-1 after merge op-2. During next reboot,
+    // kernel will request the metadata for all the un-merged blocks. If we had
+    // not sync the metadata after Merge-op 1 and Merge op-2, snapuser daemon
+    // will think that these merge operations are still pending and hence will
+    // inform the kernel that Op-1 and Op-2 are un-merged blocks. When kernel
+    // resumes back the merging process, it will attempt to redo the Merge op-1
+    // once again. However, block 2 contents are wrong as it has the contents
+    // of block 1 from previous merge cycle. Although, merge will silently succeed,
+    // this will lead to silent data corruption.
+    //
+    int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset,
+                                                   unmerged_exceptions, &copy_op);
 
     // There should be at least one operation merged in this cycle
     CHECK(merged_ops_cur_iter > 0);
-    if (!AdvanceMergedOps(merged_ops_cur_iter)) return false;
 
     header.num_merge_ops += merged_ops_cur_iter;
     reader_->UpdateMergeProgress(merged_ops_cur_iter);
-    if (!writer_->CommitMerge(merged_ops_cur_iter)) {
+    if (!writer_->CommitMerge(merged_ops_cur_iter, copy_op)) {
         SNAP_LOG(ERROR) << "CommitMerge failed...";
         return false;
     }
 
-    SNAP_LOG(DEBUG) << "Merge success";
+    SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
     return true;
 }
 
@@ -532,6 +516,7 @@
     CHECK(header.block_size == BLOCK_SIZE);
 
     SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
+    reader_->InitializeMerge();
 
     writer_ = std::make_unique<CowWriter>(options);
     writer_->InitializeMerge(cow_fd_.get(), &header);
@@ -543,7 +528,8 @@
 
     // Start from chunk number 2. Chunk 0 represents header and chunk 1
     // represents first metadata page.
-    chunk_t next_free = NUM_SNAPSHOT_HDR_CHUNKS + 1;
+    chunk_t data_chunk_id = NUM_SNAPSHOT_HDR_CHUNKS + 1;
+    size_t num_ops = 0;
 
     loff_t offset = 0;
     std::unique_ptr<uint8_t[]> de_ptr =
@@ -553,43 +539,34 @@
     // is 0. When Area is not filled completely with all 256 exceptions,
     // this memset will ensure that metadata read is completed.
     memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
-    size_t num_ops = 0;
 
     while (!cowop_riter_->Done()) {
         const CowOperation* cow_op = &cowop_riter_->Get();
         struct disk_exception* de =
                 reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
 
-        if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
+        if (IsMetadataOp(*cow_op)) {
             cowop_riter_->Next();
             continue;
         }
 
-        if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
-              cow_op->type == kCowCopyOp)) {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
-            return false;
-        }
-
         metadata_found = true;
         if ((cow_op->type == kCowCopyOp || prev_copy_op)) {
-            next_free = GetNextAllocatableChunkId(next_free);
+            data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
         }
 
         prev_copy_op = (cow_op->type == kCowCopyOp);
 
         // Construct the disk-exception
         de->old_chunk = cow_op->new_block;
-        de->new_chunk = next_free;
+        de->new_chunk = data_chunk_id;
 
         SNAP_LOG(DEBUG) << "Old-chunk: " << de->old_chunk << "New-chunk: " << de->new_chunk;
 
         // Store operation pointer.
-        chunk_map_[next_free] = cow_op;
+        chunk_map_[data_chunk_id] = cow_op;
         num_ops += 1;
-
         offset += sizeof(struct disk_exception);
-
         cowop_riter_->Next();
 
         if (num_ops == exceptions_per_area_) {
@@ -610,7 +587,7 @@
             }
         }
 
-        next_free = GetNextAllocatableChunkId(next_free);
+        data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
     }
 
     // Partially filled area or there is no metadata
@@ -622,14 +599,11 @@
                         << "Areas : " << vec_.size();
     }
 
-    SNAP_LOG(DEBUG) << "ReadMetadata() completed. chunk_id: " << next_free
-                    << "Num Sector: " << ChunkToSector(next_free);
-
-    // Initialize the iterator for merging
-    cowop_iter_ = reader_->GetOpIter();
+    SNAP_LOG(DEBUG) << "ReadMetadata() completed. Final_chunk_id: " << data_chunk_id
+                    << "Num Sector: " << ChunkToSector(data_chunk_id);
 
     // Total number of sectors required for creating dm-user device
-    num_sectors_ = ChunkToSector(next_free);
+    num_sectors_ = ChunkToSector(data_chunk_id);
     metadata_read_done_ = true;
     return true;
 }
@@ -759,6 +733,8 @@
                                             << "Sector: " << header->sector;
                         }
                     } else {
+                        SNAP_LOG(DEBUG) << "ReadData: chunk: " << chunk << " len: " << header->len
+                                        << " read_size: " << read_size << " offset: " << offset;
                         chunk_t num_chunks_read = (offset >> BLOCK_SHIFT);
                         if (!ReadData(chunk + num_chunks_read, read_size)) {
                             SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 7282bff..16d02e4 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -57,7 +57,7 @@
 SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {}
 
 static inline bool IsRetryErrno() {
-    return errno == ECONNREFUSED || errno == EINTR;
+    return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT;
 }
 
 std::unique_ptr<SnapuserdClient> SnapuserdClient::Connect(const std::string& socket_name,
@@ -112,6 +112,7 @@
 }
 
 bool SnapuserdClient::Sendmsg(const std::string& msg) {
+    LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_;
     ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0));
     if (numBytesSent < 0) {
         PLOG(ERROR) << "Send failed";
diff --git a/init/Android.mk b/init/Android.mk
index 4c1665b..ac31ef1 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -82,7 +82,6 @@
     $(TARGET_RAMDISK_OUT)/proc \
     $(TARGET_RAMDISK_OUT)/second_stage_resources \
     $(TARGET_RAMDISK_OUT)/sys \
-    $(TARGET_RAMDISK_OUT)/metadata \
 
 LOCAL_STATIC_LIBRARIES := \
     libc++fs \
diff --git a/init/builtins.cpp b/init/builtins.cpp
index b235d2f..c44e03e 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -1232,7 +1232,7 @@
 }
 
 static Result<void> GenerateLinkerConfiguration() {
-    const char* linkerconfig_binary = "/system/bin/linkerconfig";
+    const char* linkerconfig_binary = "/apex/com.android.runtime/bin/linkerconfig";
     const char* linkerconfig_target = "/linkerconfig";
     const char* arguments[] = {linkerconfig_binary, "--target", linkerconfig_target};
 
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 284c0b9..cf809f1 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -32,6 +32,7 @@
 cc_library_headers {
     name: "libcutils_headers",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     ramdisk_available: true,
     vendor_ramdisk_available: true,
@@ -45,7 +46,10 @@
     export_include_dirs: ["include"],
     target: {
         vendor: {
-            override_export_include_dirs: ["include_vndk"],
+            override_export_include_dirs: ["include_outside_system"],
+        },
+        product: {
+            override_export_include_dirs: ["include_outside_system"],
         },
         linux_bionic: {
             enabled: true,
@@ -60,6 +64,7 @@
 cc_library {
     name: "libcutils_sockets",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     ramdisk_available: true,
     vendor_ramdisk_available: true,
@@ -143,6 +148,7 @@
 cc_library {
     name: "libcutils",
     vendor_available: true,
+    product_available: true,
     vndk: {
         enabled: true,
         support_system_process: true,
@@ -234,14 +240,19 @@
             },
         },
 
+        // qtaguid.cpp loads libnetd_client.so with dlopen().  Since
+        // the interface of libnetd_client.so may vary between AOSP
+        // releases, exclude qtaguid.cpp from the VNDK-SP variant.
         vendor: {
             exclude_srcs: [
-                // qtaguid.cpp loads libnetd_client.so with dlopen().  Since
-                // the interface of libnetd_client.so may vary between AOSP
-                // releases, exclude qtaguid.cpp from the VNDK-SP variant.
                 "qtaguid.cpp",
             ],
-        }
+        },
+        product: {
+            exclude_srcs: [
+                "qtaguid.cpp",
+            ],
+        },
     },
 
     whole_static_libs: ["libcutils_sockets"],
diff --git a/libcutils/include_vndk/cutils/android_filesystem_config.h b/libcutils/include_outside_system/cutils/android_filesystem_config.h
similarity index 100%
rename from libcutils/include_vndk/cutils/android_filesystem_config.h
rename to libcutils/include_outside_system/cutils/android_filesystem_config.h
diff --git a/libcutils/include_vndk/cutils/android_get_control_file.h b/libcutils/include_outside_system/cutils/android_get_control_file.h
similarity index 100%
rename from libcutils/include_vndk/cutils/android_get_control_file.h
rename to libcutils/include_outside_system/cutils/android_get_control_file.h
diff --git a/libcutils/include_vndk/cutils/android_reboot.h b/libcutils/include_outside_system/cutils/android_reboot.h
similarity index 100%
rename from libcutils/include_vndk/cutils/android_reboot.h
rename to libcutils/include_outside_system/cutils/android_reboot.h
diff --git a/libcutils/include_vndk/cutils/ashmem.h b/libcutils/include_outside_system/cutils/ashmem.h
similarity index 100%
rename from libcutils/include_vndk/cutils/ashmem.h
rename to libcutils/include_outside_system/cutils/ashmem.h
diff --git a/libcutils/include_vndk/cutils/atomic.h b/libcutils/include_outside_system/cutils/atomic.h
similarity index 100%
rename from libcutils/include_vndk/cutils/atomic.h
rename to libcutils/include_outside_system/cutils/atomic.h
diff --git a/libcutils/include_vndk/cutils/bitops.h b/libcutils/include_outside_system/cutils/bitops.h
similarity index 100%
rename from libcutils/include_vndk/cutils/bitops.h
rename to libcutils/include_outside_system/cutils/bitops.h
diff --git a/libcutils/include_vndk/cutils/compiler.h b/libcutils/include_outside_system/cutils/compiler.h
similarity index 100%
rename from libcutils/include_vndk/cutils/compiler.h
rename to libcutils/include_outside_system/cutils/compiler.h
diff --git a/libcutils/include_vndk/cutils/config_utils.h b/libcutils/include_outside_system/cutils/config_utils.h
similarity index 100%
rename from libcutils/include_vndk/cutils/config_utils.h
rename to libcutils/include_outside_system/cutils/config_utils.h
diff --git a/libcutils/include_vndk/cutils/fs.h b/libcutils/include_outside_system/cutils/fs.h
similarity index 100%
rename from libcutils/include_vndk/cutils/fs.h
rename to libcutils/include_outside_system/cutils/fs.h
diff --git a/libcutils/include_vndk/cutils/hashmap.h b/libcutils/include_outside_system/cutils/hashmap.h
similarity index 100%
rename from libcutils/include_vndk/cutils/hashmap.h
rename to libcutils/include_outside_system/cutils/hashmap.h
diff --git a/libcutils/include_vndk/cutils/iosched_policy.h b/libcutils/include_outside_system/cutils/iosched_policy.h
similarity index 100%
rename from libcutils/include_vndk/cutils/iosched_policy.h
rename to libcutils/include_outside_system/cutils/iosched_policy.h
diff --git a/libcutils/include_vndk/cutils/klog.h b/libcutils/include_outside_system/cutils/klog.h
similarity index 100%
rename from libcutils/include_vndk/cutils/klog.h
rename to libcutils/include_outside_system/cutils/klog.h
diff --git a/libcutils/include_vndk/cutils/list.h b/libcutils/include_outside_system/cutils/list.h
similarity index 100%
rename from libcutils/include_vndk/cutils/list.h
rename to libcutils/include_outside_system/cutils/list.h
diff --git a/libcutils/include_vndk/cutils/log.h b/libcutils/include_outside_system/cutils/log.h
similarity index 100%
rename from libcutils/include_vndk/cutils/log.h
rename to libcutils/include_outside_system/cutils/log.h
diff --git a/libcutils/include_vndk/cutils/memory.h b/libcutils/include_outside_system/cutils/memory.h
similarity index 100%
rename from libcutils/include_vndk/cutils/memory.h
rename to libcutils/include_outside_system/cutils/memory.h
diff --git a/libcutils/include_vndk/cutils/misc.h b/libcutils/include_outside_system/cutils/misc.h
similarity index 100%
rename from libcutils/include_vndk/cutils/misc.h
rename to libcutils/include_outside_system/cutils/misc.h
diff --git a/libcutils/include_vndk/cutils/multiuser.h b/libcutils/include_outside_system/cutils/multiuser.h
similarity index 100%
rename from libcutils/include_vndk/cutils/multiuser.h
rename to libcutils/include_outside_system/cutils/multiuser.h
diff --git a/libcutils/include_vndk/cutils/native_handle.h b/libcutils/include_outside_system/cutils/native_handle.h
similarity index 100%
rename from libcutils/include_vndk/cutils/native_handle.h
rename to libcutils/include_outside_system/cutils/native_handle.h
diff --git a/libcutils/include_vndk/cutils/partition_utils.h b/libcutils/include_outside_system/cutils/partition_utils.h
similarity index 100%
rename from libcutils/include_vndk/cutils/partition_utils.h
rename to libcutils/include_outside_system/cutils/partition_utils.h
diff --git a/libcutils/include_vndk/cutils/properties.h b/libcutils/include_outside_system/cutils/properties.h
similarity index 100%
rename from libcutils/include_vndk/cutils/properties.h
rename to libcutils/include_outside_system/cutils/properties.h
diff --git a/libcutils/include_vndk/cutils/qtaguid.h b/libcutils/include_outside_system/cutils/qtaguid.h
similarity index 100%
rename from libcutils/include_vndk/cutils/qtaguid.h
rename to libcutils/include_outside_system/cutils/qtaguid.h
diff --git a/libcutils/include_vndk/cutils/record_stream.h b/libcutils/include_outside_system/cutils/record_stream.h
similarity index 100%
rename from libcutils/include_vndk/cutils/record_stream.h
rename to libcutils/include_outside_system/cutils/record_stream.h
diff --git a/libcutils/include_vndk/cutils/sched_policy.h b/libcutils/include_outside_system/cutils/sched_policy.h
similarity index 100%
rename from libcutils/include_vndk/cutils/sched_policy.h
rename to libcutils/include_outside_system/cutils/sched_policy.h
diff --git a/libcutils/include_vndk/cutils/sockets.h b/libcutils/include_outside_system/cutils/sockets.h
similarity index 100%
rename from libcutils/include_vndk/cutils/sockets.h
rename to libcutils/include_outside_system/cutils/sockets.h
diff --git a/libcutils/include_vndk/cutils/str_parms.h b/libcutils/include_outside_system/cutils/str_parms.h
similarity index 100%
rename from libcutils/include_vndk/cutils/str_parms.h
rename to libcutils/include_outside_system/cutils/str_parms.h
diff --git a/libcutils/include_vndk/cutils/threads.h b/libcutils/include_outside_system/cutils/threads.h
similarity index 100%
rename from libcutils/include_vndk/cutils/threads.h
rename to libcutils/include_outside_system/cutils/threads.h
diff --git a/libcutils/include_vndk/cutils/trace.h b/libcutils/include_outside_system/cutils/trace.h
similarity index 100%
rename from libcutils/include_vndk/cutils/trace.h
rename to libcutils/include_outside_system/cutils/trace.h
diff --git a/libcutils/include_vndk/cutils/uevent.h b/libcutils/include_outside_system/cutils/uevent.h
similarity index 100%
rename from libcutils/include_vndk/cutils/uevent.h
rename to libcutils/include_outside_system/cutils/uevent.h
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 71e2b91..f104100 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -1,6 +1,7 @@
 cc_library_headers {
     name: "libprocessgroup_headers",
     vendor_available: true,
+    product_available: true,
     ramdisk_available: true,
     vendor_ramdisk_available: true,
     recovery_available: true,
@@ -36,6 +37,7 @@
     vendor_ramdisk_available: true,
     recovery_available: true,
     vendor_available: true,
+    product_available: true,
     vndk: {
         enabled: true,
         support_system_process: true,
diff --git a/libprocessgroup/include/processgroup/sched_policy.h b/libprocessgroup/include/processgroup/sched_policy.h
index 945d90c..a18847e 100644
--- a/libprocessgroup/include/processgroup/sched_policy.h
+++ b/libprocessgroup/include/processgroup/sched_policy.h
@@ -42,7 +42,7 @@
     SP_DEFAULT = -1,
     SP_BACKGROUND = 0,
     SP_FOREGROUND = 1,
-    SP_SYSTEM = 2,  // can't be used with set_sched_policy()
+    SP_SYSTEM = 2,
     SP_AUDIO_APP = 3,
     SP_AUDIO_SYS = 4,
     SP_TOP_APP = 5,
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 5b7a28a..792af6f 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -42,7 +42,7 @@
     "Controllers": [
       {
         "Controller": "freezer",
-        "Path": "freezer",
+        "Path": ".",
         "Mode": "0755",
         "UID": "system",
         "GID": "system"
diff --git a/libprocessgroup/profiles/cgroups_28.json b/libprocessgroup/profiles/cgroups_28.json
index 4518487..17d4929 100644
--- a/libprocessgroup/profiles/cgroups_28.json
+++ b/libprocessgroup/profiles/cgroups_28.json
@@ -1,59 +1,11 @@
 {
   "Cgroups": [
     {
-      "Controller": "blkio",
-      "Path": "/dev/blkio",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "cpu",
-      "Path": "/dev/cpuctl",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    },
-    {
-      "Controller": "cpuset",
-      "Path": "/dev/cpuset",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "memory",
-      "Path": "/dev/memcg",
-      "Mode": "0700",
-      "UID": "root",
-      "GID": "system"
-    },
-    {
       "Controller": "schedtune",
       "Path": "/dev/stune",
       "Mode": "0755",
       "UID": "system",
       "GID": "system"
     }
-  ],
-  "Cgroups2": {
-    "Path": "/sys/fs/cgroup",
-    "Mode": "0755",
-    "UID": "system",
-    "GID": "system",
-    "Controllers": [
-      {
-        "Controller": "freezer",
-        "Path": "freezer",
-        "Mode": "0755",
-        "UID": "system",
-        "GID": "system"
-      }
-    ]
-  }
+  ]
 }
diff --git a/libprocessgroup/profiles/cgroups_29.json b/libprocessgroup/profiles/cgroups_29.json
index 4518487..17d4929 100644
--- a/libprocessgroup/profiles/cgroups_29.json
+++ b/libprocessgroup/profiles/cgroups_29.json
@@ -1,59 +1,11 @@
 {
   "Cgroups": [
     {
-      "Controller": "blkio",
-      "Path": "/dev/blkio",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "cpu",
-      "Path": "/dev/cpuctl",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    },
-    {
-      "Controller": "cpuset",
-      "Path": "/dev/cpuset",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "memory",
-      "Path": "/dev/memcg",
-      "Mode": "0700",
-      "UID": "root",
-      "GID": "system"
-    },
-    {
       "Controller": "schedtune",
       "Path": "/dev/stune",
       "Mode": "0755",
       "UID": "system",
       "GID": "system"
     }
-  ],
-  "Cgroups2": {
-    "Path": "/sys/fs/cgroup",
-    "Mode": "0755",
-    "UID": "system",
-    "GID": "system",
-    "Controllers": [
-      {
-        "Controller": "freezer",
-        "Path": "freezer",
-        "Mode": "0755",
-        "UID": "system",
-        "GID": "system"
-      }
-    ]
-  }
+  ]
 }
diff --git a/libprocessgroup/profiles/cgroups_30.json b/libprocessgroup/profiles/cgroups_30.json
index 4518487..17d4929 100644
--- a/libprocessgroup/profiles/cgroups_30.json
+++ b/libprocessgroup/profiles/cgroups_30.json
@@ -1,59 +1,11 @@
 {
   "Cgroups": [
     {
-      "Controller": "blkio",
-      "Path": "/dev/blkio",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "cpu",
-      "Path": "/dev/cpuctl",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    },
-    {
-      "Controller": "cpuset",
-      "Path": "/dev/cpuset",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
-      "Controller": "memory",
-      "Path": "/dev/memcg",
-      "Mode": "0700",
-      "UID": "root",
-      "GID": "system"
-    },
-    {
       "Controller": "schedtune",
       "Path": "/dev/stune",
       "Mode": "0755",
       "UID": "system",
       "GID": "system"
     }
-  ],
-  "Cgroups2": {
-    "Path": "/sys/fs/cgroup",
-    "Mode": "0755",
-    "UID": "system",
-    "GID": "system",
-    "Controllers": [
-      {
-        "Controller": "freezer",
-        "Path": "freezer",
-        "Mode": "0755",
-        "UID": "system",
-        "GID": "system"
-      }
-    ]
-  }
+  ]
 }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index b528fa5..628098b 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -46,7 +46,7 @@
       "File": "cpu.uclamp.latency_sensitive"
     },
     {
-      "Name": "FreezerState",
+      "Name": "Freezer",
       "Controller": "freezer",
       "File": "cgroup.freeze"
     }
@@ -70,11 +70,11 @@
       "Name": "Frozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": ""
+            "Name": "Freezer",
+            "Value": "1"
           }
         }
       ]
@@ -83,11 +83,11 @@
       "Name": "Unfrozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": "../"
+            "Name": "Freezer",
+            "Value": "0"
           }
         }
       ]
@@ -106,6 +106,19 @@
       ]
     },
     {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "system-background"
+          }
+        }
+      ]
+    },
+    {
       "Name": "HighPerformance",
       "Actions": [
         {
@@ -591,6 +604,10 @@
       "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
     {
+      "Name": "SCHED_SP_SYSTEM",
+      "Profiles": [ "ServicePerformance", "LowIoPriority", "TimerSlackNormal" ]
+    },
+    {
       "Name": "SCHED_SP_RT_APP",
       "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
index 142b0ba..9f83785 100644
--- a/libprocessgroup/profiles/task_profiles_28.json
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -1,36 +1,6 @@
 {
   "Attributes": [
     {
-      "Name": "LowCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "background/cpus"
-    },
-    {
-      "Name": "HighCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "foreground/cpus"
-    },
-    {
-      "Name": "MaxCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "top-app/cpus"
-    },
-    {
-      "Name": "MemLimit",
-      "Controller": "memory",
-      "File": "memory.limit_in_bytes"
-    },
-    {
-      "Name": "MemSoftLimit",
-      "Controller": "memory",
-      "File": "memory.soft_limit_in_bytes"
-    },
-    {
-      "Name": "MemSwappiness",
-      "Controller": "memory",
-      "File": "memory.swappiness"
-    },
-    {
       "Name": "STuneBoost",
       "Controller": "schedtune",
       "File": "schedtune.boost"
@@ -39,21 +9,6 @@
       "Name": "STunePreferIdle",
       "Controller": "schedtune",
       "File": "schedtune.prefer_idle"
-    },
-    {
-      "Name": "UClampMin",
-      "Controller": "cpu",
-      "File": "cpu.uclamp.min"
-    },
-    {
-      "Name": "UClampMax",
-      "Controller": "cpu",
-      "File": "cpu.uclamp.max"
-    },
-    {
-      "Name": "FreezerState",
-      "Controller": "freezer",
-      "File": "cgroup.freeze"
     }
   ],
 
@@ -72,32 +27,6 @@
       ]
     },
     {
-      "Name": "Frozen",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "freezer",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "Unfrozen",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "freezer",
-            "Path": "../"
-          }
-        }
-      ]
-    },
-    {
       "Name": "NormalPerformance",
       "Actions": [
         {
@@ -201,427 +130,6 @@
           }
         }
       ]
-    },
-
-    {
-      "Name": "VrKernelCapacity",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system/background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system/performance"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application/background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application/performance"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "ProcessCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "foreground"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityMax",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "top-app"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "ServiceCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system-background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ServiceCapacityRestricted",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "restricted"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "CameraServiceCapacity",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "camera-daemon"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "LowIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "NormalIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "MaxIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "TimerSlackHigh",
-      "Actions": [
-        {
-          "Name": "SetTimerSlack",
-          "Params":
-          {
-            "Slack": "40000000"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "TimerSlackNormal",
-      "Actions": [
-        {
-          "Name": "SetTimerSlack",
-          "Params":
-          {
-            "Slack": "50000"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "PerfBoost",
-      "Actions": [
-        {
-          "Name": "SetClamps",
-          "Params":
-          {
-            "Boost": "50%",
-            "Clamp": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "PerfClamp",
-      "Actions": [
-        {
-          "Name": "SetClamps",
-          "Params":
-          {
-            "Boost": "0",
-            "Clamp": "30%"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "LowMemoryUsage",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSoftLimit",
-            "Value": "16MB"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSwappiness",
-            "Value": "150"
-
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighMemoryUsage",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSoftLimit",
-            "Value": "512MB"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSwappiness",
-            "Value": "100"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "SystemMemoryProcess",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "memory",
-            "Path": "system"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerDisabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerEnabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "1"
-          }
-        }
-      ]
-    }
-  ],
-
-  "AggregateProfiles": [
-    {
-      "Name": "SCHED_SP_DEFAULT",
-      "Profiles": [ "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_BACKGROUND",
-      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
-    },
-    {
-      "Name": "SCHED_SP_FOREGROUND",
-      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_TOP_APP",
-      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_RT_APP",
-      "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_DEFAULT",
-      "Profiles": [ "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_BACKGROUND",
-      "Profiles": [ "HighEnergySaving", "ProcessCapacityLow", "LowIoPriority", "TimerSlackHigh" ]
-    },
-    {
-      "Name": "CPUSET_SP_FOREGROUND",
-      "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_TOP_APP",
-      "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_SYSTEM",
-      "Profiles": [ "ServiceCapacityLow", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_RESTRICTED",
-      "Profiles": [ "ServiceCapacityRestricted", "TimerSlackNormal" ]
     }
   ]
 }
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
index 142b0ba..9f83785 100644
--- a/libprocessgroup/profiles/task_profiles_29.json
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -1,36 +1,6 @@
 {
   "Attributes": [
     {
-      "Name": "LowCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "background/cpus"
-    },
-    {
-      "Name": "HighCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "foreground/cpus"
-    },
-    {
-      "Name": "MaxCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "top-app/cpus"
-    },
-    {
-      "Name": "MemLimit",
-      "Controller": "memory",
-      "File": "memory.limit_in_bytes"
-    },
-    {
-      "Name": "MemSoftLimit",
-      "Controller": "memory",
-      "File": "memory.soft_limit_in_bytes"
-    },
-    {
-      "Name": "MemSwappiness",
-      "Controller": "memory",
-      "File": "memory.swappiness"
-    },
-    {
       "Name": "STuneBoost",
       "Controller": "schedtune",
       "File": "schedtune.boost"
@@ -39,21 +9,6 @@
       "Name": "STunePreferIdle",
       "Controller": "schedtune",
       "File": "schedtune.prefer_idle"
-    },
-    {
-      "Name": "UClampMin",
-      "Controller": "cpu",
-      "File": "cpu.uclamp.min"
-    },
-    {
-      "Name": "UClampMax",
-      "Controller": "cpu",
-      "File": "cpu.uclamp.max"
-    },
-    {
-      "Name": "FreezerState",
-      "Controller": "freezer",
-      "File": "cgroup.freeze"
     }
   ],
 
@@ -72,32 +27,6 @@
       ]
     },
     {
-      "Name": "Frozen",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "freezer",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "Unfrozen",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "freezer",
-            "Path": "../"
-          }
-        }
-      ]
-    },
-    {
       "Name": "NormalPerformance",
       "Actions": [
         {
@@ -201,427 +130,6 @@
           }
         }
       ]
-    },
-
-    {
-      "Name": "VrKernelCapacity",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system/background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system/performance"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application/background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application/performance"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "ProcessCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "foreground"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityMax",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "top-app"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "ServiceCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system-background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ServiceCapacityRestricted",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "restricted"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "CameraServiceCapacity",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "camera-daemon"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "LowIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "NormalIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "MaxIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "TimerSlackHigh",
-      "Actions": [
-        {
-          "Name": "SetTimerSlack",
-          "Params":
-          {
-            "Slack": "40000000"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "TimerSlackNormal",
-      "Actions": [
-        {
-          "Name": "SetTimerSlack",
-          "Params":
-          {
-            "Slack": "50000"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "PerfBoost",
-      "Actions": [
-        {
-          "Name": "SetClamps",
-          "Params":
-          {
-            "Boost": "50%",
-            "Clamp": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "PerfClamp",
-      "Actions": [
-        {
-          "Name": "SetClamps",
-          "Params":
-          {
-            "Boost": "0",
-            "Clamp": "30%"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "LowMemoryUsage",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSoftLimit",
-            "Value": "16MB"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSwappiness",
-            "Value": "150"
-
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighMemoryUsage",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSoftLimit",
-            "Value": "512MB"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSwappiness",
-            "Value": "100"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "SystemMemoryProcess",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "memory",
-            "Path": "system"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerDisabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerEnabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "1"
-          }
-        }
-      ]
-    }
-  ],
-
-  "AggregateProfiles": [
-    {
-      "Name": "SCHED_SP_DEFAULT",
-      "Profiles": [ "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_BACKGROUND",
-      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
-    },
-    {
-      "Name": "SCHED_SP_FOREGROUND",
-      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_TOP_APP",
-      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_RT_APP",
-      "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_DEFAULT",
-      "Profiles": [ "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_BACKGROUND",
-      "Profiles": [ "HighEnergySaving", "ProcessCapacityLow", "LowIoPriority", "TimerSlackHigh" ]
-    },
-    {
-      "Name": "CPUSET_SP_FOREGROUND",
-      "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_TOP_APP",
-      "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_SYSTEM",
-      "Profiles": [ "ServiceCapacityLow", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_RESTRICTED",
-      "Profiles": [ "ServiceCapacityRestricted", "TimerSlackNormal" ]
     }
   ]
 }
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
index 142b0ba..9f83785 100644
--- a/libprocessgroup/profiles/task_profiles_30.json
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -1,36 +1,6 @@
 {
   "Attributes": [
     {
-      "Name": "LowCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "background/cpus"
-    },
-    {
-      "Name": "HighCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "foreground/cpus"
-    },
-    {
-      "Name": "MaxCapacityCPUs",
-      "Controller": "cpuset",
-      "File": "top-app/cpus"
-    },
-    {
-      "Name": "MemLimit",
-      "Controller": "memory",
-      "File": "memory.limit_in_bytes"
-    },
-    {
-      "Name": "MemSoftLimit",
-      "Controller": "memory",
-      "File": "memory.soft_limit_in_bytes"
-    },
-    {
-      "Name": "MemSwappiness",
-      "Controller": "memory",
-      "File": "memory.swappiness"
-    },
-    {
       "Name": "STuneBoost",
       "Controller": "schedtune",
       "File": "schedtune.boost"
@@ -39,21 +9,6 @@
       "Name": "STunePreferIdle",
       "Controller": "schedtune",
       "File": "schedtune.prefer_idle"
-    },
-    {
-      "Name": "UClampMin",
-      "Controller": "cpu",
-      "File": "cpu.uclamp.min"
-    },
-    {
-      "Name": "UClampMax",
-      "Controller": "cpu",
-      "File": "cpu.uclamp.max"
-    },
-    {
-      "Name": "FreezerState",
-      "Controller": "freezer",
-      "File": "cgroup.freeze"
     }
   ],
 
@@ -72,32 +27,6 @@
       ]
     },
     {
-      "Name": "Frozen",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "freezer",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "Unfrozen",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "freezer",
-            "Path": "../"
-          }
-        }
-      ]
-    },
-    {
       "Name": "NormalPerformance",
       "Actions": [
         {
@@ -201,427 +130,6 @@
           }
         }
       ]
-    },
-
-    {
-      "Name": "VrKernelCapacity",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system/background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrServiceCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system/performance"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application/background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "VrProcessCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "application/performance"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "ProcessCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityNormal",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityHigh",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "foreground"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ProcessCapacityMax",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "top-app"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "ServiceCapacityLow",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "system-background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "ServiceCapacityRestricted",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "restricted"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "CameraServiceCapacity",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "cpuset",
-            "Path": "camera-daemon"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "LowIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": "background"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "NormalIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-    {
-      "Name": "MaxIoPriority",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "TimerSlackHigh",
-      "Actions": [
-        {
-          "Name": "SetTimerSlack",
-          "Params":
-          {
-            "Slack": "40000000"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "TimerSlackNormal",
-      "Actions": [
-        {
-          "Name": "SetTimerSlack",
-          "Params":
-          {
-            "Slack": "50000"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "PerfBoost",
-      "Actions": [
-        {
-          "Name": "SetClamps",
-          "Params":
-          {
-            "Boost": "50%",
-            "Clamp": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "PerfClamp",
-      "Actions": [
-        {
-          "Name": "SetClamps",
-          "Params":
-          {
-            "Boost": "0",
-            "Clamp": "30%"
-          }
-        }
-      ]
-    },
-
-    {
-      "Name": "LowMemoryUsage",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSoftLimit",
-            "Value": "16MB"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSwappiness",
-            "Value": "150"
-
-          }
-        }
-      ]
-    },
-    {
-      "Name": "HighMemoryUsage",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSoftLimit",
-            "Value": "512MB"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "MemSwappiness",
-            "Value": "100"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "SystemMemoryProcess",
-      "Actions": [
-        {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "memory",
-            "Path": "system"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerDisabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerEnabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "1"
-          }
-        }
-      ]
-    }
-  ],
-
-  "AggregateProfiles": [
-    {
-      "Name": "SCHED_SP_DEFAULT",
-      "Profiles": [ "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_BACKGROUND",
-      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
-    },
-    {
-      "Name": "SCHED_SP_FOREGROUND",
-      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_TOP_APP",
-      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "SCHED_SP_RT_APP",
-      "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_DEFAULT",
-      "Profiles": [ "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_BACKGROUND",
-      "Profiles": [ "HighEnergySaving", "ProcessCapacityLow", "LowIoPriority", "TimerSlackHigh" ]
-    },
-    {
-      "Name": "CPUSET_SP_FOREGROUND",
-      "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_TOP_APP",
-      "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_SYSTEM",
-      "Profiles": [ "ServiceCapacityLow", "TimerSlackNormal" ]
-    },
-    {
-      "Name": "CPUSET_SP_RESTRICTED",
-      "Profiles": [ "ServiceCapacityRestricted", "TimerSlackNormal" ]
     }
   ]
 }
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index 698e74d..c51ee61 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -124,6 +124,8 @@
             return SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
         case SP_TOP_APP:
             return SetTaskProfiles(tid, {"SCHED_SP_TOP_APP"}, true) ? 0 : -1;
+        case SP_SYSTEM:
+            return SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
         case SP_RT_APP:
             return SetTaskProfiles(tid, {"SCHED_SP_RT_APP"}, true) ? 0 : -1;
         default:
@@ -258,7 +260,7 @@
      */
     static constexpr const char* kSchedProfiles[SP_CNT + 1] = {
             "SCHED_SP_DEFAULT", "SCHED_SP_BACKGROUND", "SCHED_SP_FOREGROUND",
-            "SCHED_SP_DEFAULT", "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
+            "SCHED_SP_SYSTEM",  "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
             "SCHED_SP_TOP_APP", "SCHED_SP_RT_APP",     "SCHED_SP_DEFAULT"};
     if (policy < SP_DEFAULT || policy >= SP_CNT) {
         return nullptr;
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index a53132e..753fd2d 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -214,23 +214,23 @@
 }
 
 static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
-    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
-    std::string sys_cgroups_path = CGROUPS_DESC_FILE;
+    // load system cgroup descriptors
+    if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
+        return false;
+    }
 
     // load API-level specific system cgroups descriptors if available
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
     if (api_level > 0) {
         std::string api_cgroups_path =
                 android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
         if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
-            sys_cgroups_path = api_cgroups_path;
+            if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
+                return false;
+            }
         }
     }
 
-    // load system cgroup descriptors
-    if (!ReadDescriptorsFromFile(sys_cgroups_path, descriptors)) {
-        return false;
-    }
-
     // load vendor cgroup descriptors if the file exists
     if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
         !ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index db44228..1311306 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -391,23 +391,24 @@
 }
 
 TaskProfiles::TaskProfiles() {
-    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
-    std::string sys_profiles_path = TASK_PROFILE_DB_FILE;
+    // load system task profiles
+    if (!Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_FILE)) {
+        LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
+    }
 
     // load API-level specific system task profiles if available
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
     if (api_level > 0) {
         std::string api_profiles_path =
                 android::base::StringPrintf(TEMPLATE_TASK_PROFILE_API_FILE, api_level);
         if (!access(api_profiles_path.c_str(), F_OK) || errno != ENOENT) {
-            sys_profiles_path = api_profiles_path;
+            if (!Load(CgroupMap::GetInstance(), api_profiles_path)) {
+                LOG(ERROR) << "Loading " << api_profiles_path << " for [" << getpid()
+                           << "] failed";
+            }
         }
     }
 
-    // load system task profiles
-    if (!Load(CgroupMap::GetInstance(), sys_profiles_path)) {
-        LOG(ERROR) << "Loading " << sys_profiles_path << " for [" << getpid() << "] failed";
-    }
-
     // load vendor task profiles if the file exists
     if (!access(TASK_PROFILE_DB_VENDOR_FILE, F_OK) &&
         !Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_VENDOR_FILE)) {
diff --git a/libstats/pull/Android.bp b/libstats/pull/Android.bp
index a8b4a4f..6d6b466 100644
--- a/libstats/pull/Android.bp
+++ b/libstats/pull/Android.bp
@@ -70,6 +70,7 @@
     ],
     visibility: [
         "//frameworks/base/apex/statsd/tests/libstatspull",
+        "//packages/modules/StatsD/apex/tests/libstatspull",
     ],
 }
 
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
index 89cdfe5..a6ea185 100644
--- a/libstats/socket/Android.bp
+++ b/libstats/socket/Android.bp
@@ -79,6 +79,8 @@
     visibility: [
         "//frameworks/base/apex/statsd/tests/libstatspull",
         "//frameworks/base/cmds/statsd",
+        "//packages/modules/StatsD/apex/tests/libstatspull",
+        "//packages/modules/StatsD/bin",
     ],
 }
 
diff --git a/libsystem/Android.bp b/libsystem/Android.bp
index 12c946c..b37b8ec 100644
--- a/libsystem/Android.bp
+++ b/libsystem/Android.bp
@@ -1,6 +1,7 @@
 cc_library_headers {
     name: "libsystem_headers",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     vendor_ramdisk_available: true,
     host_supported: true,
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 8ee16f3..1e7cbdb 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -15,6 +15,7 @@
 cc_library_headers {
     name: "libutils_headers",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     vendor_ramdisk_available: true,
     host_supported: true,
@@ -62,6 +63,7 @@
 cc_defaults {
     name: "libutils_defaults",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     vndk: {
         enabled: true,
diff --git a/libutils/RefBase_fuzz.cpp b/libutils/RefBase_fuzz.cpp
old mode 100755
new mode 100644
index 2a92531..69288b3
--- a/libutils/RefBase_fuzz.cpp
+++ b/libutils/RefBase_fuzz.cpp
@@ -14,66 +14,156 @@
  * limitations under the License.
  */
 
-#include <atomic>
+#define LOG_TAG "RefBaseFuzz"
+
 #include <thread>
 
 #include "fuzzer/FuzzedDataProvider.h"
+#include "utils/Log.h"
+#include "utils/RWLock.h"
 #include "utils/RefBase.h"
 #include "utils/StrongPointer.h"
+
 using android::RefBase;
+using android::RWLock;
 using android::sp;
 using android::wp;
 
-static constexpr int REFBASE_INITIAL_STRONG_VALUE = (1 << 28);
-static constexpr int REFBASE_MAX_COUNT = 0xfffff;
-
-static constexpr int MAX_OPERATIONS = 100;
-static constexpr int MAX_THREADS = 10;
-
-bool canDecrementStrong(RefBase* ref) {
-    // There's an assert around decrementing the strong count too much that causes an artificial
-    // crash This is just running BAD_STRONG from RefBase
-    const int32_t count = ref->getStrongCount() - 1;
-    return !(count == 0 || ((count) & (~(REFBASE_MAX_COUNT | REFBASE_INITIAL_STRONG_VALUE))) != 0);
-}
-bool canDecrementWeak(RefBase* ref) {
-    const int32_t count = ref->getWeakRefs()->getWeakCount() - 1;
-    return !((count) == 0 || ((count) & (~REFBASE_MAX_COUNT)) != 0);
-}
-
+static constexpr int kMaxOperations = 100;
+static constexpr int kMaxThreads = 10;
 struct RefBaseSubclass : public RefBase {
-    RefBaseSubclass() {}
-    virtual ~RefBaseSubclass() {}
+  public:
+    RefBaseSubclass(bool* deletedCheck, RWLock& deletedMtx)
+        : mDeleted(deletedCheck), mRwLock(deletedMtx) {
+        RWLock::AutoWLock lock(mRwLock);
+        *mDeleted = false;
+        extendObjectLifetime(OBJECT_LIFETIME_WEAK);
+    }
+
+    virtual ~RefBaseSubclass() {
+        RWLock::AutoWLock lock(mRwLock);
+        *mDeleted = true;
+    }
+
+  private:
+    bool* mDeleted;
+    android::RWLock& mRwLock;
 };
 
-std::vector<std::function<void(RefBaseSubclass*)>> operations = {
-        [](RefBaseSubclass* ref) -> void { ref->getStrongCount(); },
-        [](RefBaseSubclass* ref) -> void { ref->printRefs(); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->printRefs(); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->getWeakCount(); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->refBase(); },
-        [](RefBaseSubclass* ref) -> void { ref->incStrong(nullptr); },
-        [](RefBaseSubclass* ref) -> void {
-            if (canDecrementStrong(ref)) {
+// A thread-specific state object for ref
+struct RefThreadState {
+    size_t strongCount = 0;
+    size_t weakCount = 0;
+};
+
+RWLock gRefDeletedLock;
+bool gRefDeleted = false;
+bool gHasModifiedRefs = false;
+RefBaseSubclass* ref;
+RefBase::weakref_type* weakRefs;
+
+// These operations don't need locks as they explicitly check per-thread counts before running
+// they also have the potential to write to gRefDeleted, so must not be locked.
+const std::vector<std::function<void(RefThreadState*)>> kUnlockedOperations = {
+        [](RefThreadState* refState) -> void {
+            if (refState->strongCount > 0) {
                 ref->decStrong(nullptr);
+                gHasModifiedRefs = true;
+                refState->strongCount--;
             }
         },
-        [](RefBaseSubclass* ref) -> void { ref->forceIncStrong(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->createWeak(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->attemptIncStrong(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->attemptIncWeak(nullptr); },
-        [](RefBaseSubclass* ref) -> void {
-            if (canDecrementWeak(ref)) {
-                ref->getWeakRefs()->decWeak(nullptr);
+        [](RefThreadState* refState) -> void {
+            if (refState->weakCount > 0) {
+                weakRefs->decWeak(nullptr);
+                gHasModifiedRefs = true;
+                refState->weakCount--;
             }
         },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->incWeak(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->printRefs(); },
 };
 
-void loop(RefBaseSubclass* loopRef, const std::vector<uint8_t>& fuzzOps) {
+const std::vector<std::function<void(RefThreadState*)>> kMaybeLockedOperations = {
+        // Read-only operations
+        [](RefThreadState*) -> void { ref->getStrongCount(); },
+        [](RefThreadState*) -> void { weakRefs->getWeakCount(); },
+        [](RefThreadState*) -> void { ref->printRefs(); },
+
+        // Read/write operations
+        [](RefThreadState* refState) -> void {
+            ref->incStrong(nullptr);
+            gHasModifiedRefs = true;
+            refState->strongCount++;
+        },
+        [](RefThreadState* refState) -> void {
+            ref->forceIncStrong(nullptr);
+            gHasModifiedRefs = true;
+            refState->strongCount++;
+        },
+        [](RefThreadState* refState) -> void {
+            ref->createWeak(nullptr);
+            gHasModifiedRefs = true;
+            refState->weakCount++;
+        },
+        [](RefThreadState* refState) -> void {
+            // This will increment weak internally, then attempt to
+            // promote it to strong. If it fails, it decrements weak.
+            // If it succeeds, the weak is converted to strong.
+            // Both cases net no weak reference change.
+            if (weakRefs->attemptIncStrong(nullptr)) {
+                refState->strongCount++;
+                gHasModifiedRefs = true;
+            }
+        },
+        [](RefThreadState* refState) -> void {
+            if (weakRefs->attemptIncWeak(nullptr)) {
+                refState->weakCount++;
+                gHasModifiedRefs = true;
+            }
+        },
+        [](RefThreadState* refState) -> void {
+            weakRefs->incWeak(nullptr);
+            gHasModifiedRefs = true;
+            refState->weakCount++;
+        },
+};
+
+void loop(const std::vector<uint8_t>& fuzzOps) {
+    RefThreadState state;
+    uint8_t lockedOpSize = kMaybeLockedOperations.size();
+    uint8_t totalOperationTypes = lockedOpSize + kUnlockedOperations.size();
     for (auto op : fuzzOps) {
-        operations[op % operations.size()](loopRef);
+        auto opVal = op % totalOperationTypes;
+        if (opVal >= lockedOpSize) {
+            kUnlockedOperations[opVal % lockedOpSize](&state);
+        } else {
+            // We only need to lock if we have no strong or weak count
+            bool shouldLock = state.strongCount == 0 && state.weakCount == 0;
+            if (shouldLock) {
+                gRefDeletedLock.readLock();
+                // If ref has deleted itself, we can no longer fuzz on this thread.
+                if (gRefDeleted) {
+                    // Unlock since we're exiting the loop here.
+                    gRefDeletedLock.unlock();
+                    return;
+                }
+            }
+            // Execute the locked operation
+            kMaybeLockedOperations[opVal](&state);
+            // Unlock if we locked.
+            if (shouldLock) {
+                gRefDeletedLock.unlock();
+            }
+        }
+    }
+
+    // Instead of explicitly freeing this, we're going to remove our weak and
+    // strong references.
+    for (; state.weakCount > 0; state.weakCount--) {
+        weakRefs->decWeak(nullptr);
+    }
+
+    // Clean up any strong references
+    for (; state.strongCount > 0; state.strongCount--) {
+        ref->decStrong(nullptr);
     }
 }
 
@@ -81,23 +171,35 @@
     std::vector<std::thread> threads = std::vector<std::thread>();
 
     // Get the number of threads to generate
-    uint8_t count = dataProvider->ConsumeIntegralInRange<uint8_t>(1, MAX_THREADS);
-
+    uint8_t count = dataProvider->ConsumeIntegralInRange<uint8_t>(1, kMaxThreads);
     // Generate threads
     for (uint8_t i = 0; i < count; i++) {
-        RefBaseSubclass* threadRef = new RefBaseSubclass();
-        uint8_t opCount = dataProvider->ConsumeIntegralInRange<uint8_t>(1, MAX_OPERATIONS);
+        uint8_t opCount = dataProvider->ConsumeIntegralInRange<uint8_t>(1, kMaxOperations);
         std::vector<uint8_t> threadOperations = dataProvider->ConsumeBytes<uint8_t>(opCount);
-        std::thread tmp = std::thread(loop, threadRef, threadOperations);
-        threads.push_back(move(tmp));
+        std::thread tmpThread = std::thread(loop, threadOperations);
+        threads.push_back(move(tmpThread));
     }
 
     for (auto& th : threads) {
         th.join();
     }
 }
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    gHasModifiedRefs = false;
+    ref = new RefBaseSubclass(&gRefDeleted, gRefDeletedLock);
+    weakRefs = ref->getWeakRefs();
+    // Since we are modifying flags, (flags & OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK
+    // is true. The destructor for RefBase should clean up weakrefs because of this.
     FuzzedDataProvider dataProvider(data, size);
     spawnThreads(&dataProvider);
+    LOG_ALWAYS_FATAL_IF(!gHasModifiedRefs && gRefDeleted, "ref(%p) was prematurely deleted!", ref);
+    // We need to explicitly delete this object
+    // if no refs have been added or deleted.
+    if (!gHasModifiedRefs && !gRefDeleted) {
+        delete ref;
+    }
+    LOG_ALWAYS_FATAL_IF(gHasModifiedRefs && !gRefDeleted,
+                        "ref(%p) should be deleted, is it leaking?", ref);
     return 0;
 }
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index a21f686..d63868a 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -29,4 +29,5 @@
 linker_config {
     name: "system_linker_config",
     src: "etc/linker.config.json",
+    installable: false,
 }
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 2bceb75..17d71f7 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -78,7 +78,7 @@
 # create some directories (some are mount points) and symlinks
 LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
     dev proc sys system data data_mirror odm oem acct config storage mnt apex debug_ramdisk \
-    linkerconfig second_stage_resources $(BOARD_ROOT_EXTRA_FOLDERS)); \
+    linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \
     ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
     ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
@@ -153,11 +153,6 @@
     ; mkdir -p $(dir $(TARGET_ROOT_OUT)/$(word 2,$(p))) \
     ; ln -sf $(word 1,$(p)) $(TARGET_ROOT_OUT)/$(word 2,$(p)))
 endif
-# The A/B updater uses a top-level /postinstall directory to mount the new
-# system before reboot.
-ifeq ($(AB_OTA_UPDATER),true)
-  LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/postinstall
-endif
 
 # The init symlink must be a post install command of a file that is to TARGET_ROOT_OUT.
 # Since init.environ.rc is required for init and satisfies that requirement, we hijack it to create the symlink.
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index d66ab73..2faf608 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -1,47 +1,4 @@
 {
-  // These are list of libraries which has stub interface and installed
-  // in system image so other partition and APEX modules can link to it.
-  // TODO(b/147210213) : Generate this list on build and read from the file
-  "provideLibs": [
-    // LLNDK libraries
-    "libEGL.so",
-    "libGLESv1_CM.so",
-    "libGLESv2.so",
-    "libGLESv3.so",
-    "libRS.so",
-    "libandroid_net.so",
-    "libbinder_ndk.so",
-    "libc.so",
-    "libcgrouprc.so",
-    "libclang_rt.asan-arm-android.so",
-    "libclang_rt.asan-i686-android.so",
-    "libclang_rt.asan-x86_64-android.so",
-    "libdl.so",
-    "libft2.so",
-    "liblog.so",
-    "libm.so",
-    "libmediandk.so",
-    "libnativewindow.so",
-    "libsync.so",
-    "libvndksupport.so",
-    "libvulkan.so",
-    // NDK libraries
-    "libaaudio.so",
-    "libandroid.so",
-    // adb
-    "libadbd_auth.so",
-    "libadbd_fs.so",
-    // bionic
-    "libdl_android.so",
-    // statsd
-    "libincident.so",
-    // media
-    "libmediametrics.so",
-    // nn
-    "libneuralnetworks_packageinfo.so",
-    // SELinux
-    "libselinux.so"
-  ],
   "requireLibs": [
     // Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
     "libdexfile_external.so",
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 5a816c6..de608b1 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -56,7 +56,7 @@
     write /sys/module/dm_verity/parameters/prefetch_cluster 0
 
     # Generate ld.config.txt for early executed processes
-    exec -- /system/bin/linkerconfig --target /linkerconfig/bootstrap
+    exec -- /system/bin/bootstrap/linkerconfig --target /linkerconfig/bootstrap
     chmod 644 /linkerconfig/bootstrap/ld.config.txt
     copy /linkerconfig/bootstrap/ld.config.txt /linkerconfig/default/ld.config.txt
     chmod 644 /linkerconfig/default/ld.config.txt
@@ -154,24 +154,28 @@
     mkdir /dev/cpuctl/top-app
     mkdir /dev/cpuctl/rt
     mkdir /dev/cpuctl/system
+    mkdir /dev/cpuctl/system-background
     chown system system /dev/cpuctl
     chown system system /dev/cpuctl/foreground
     chown system system /dev/cpuctl/background
     chown system system /dev/cpuctl/top-app
     chown system system /dev/cpuctl/rt
     chown system system /dev/cpuctl/system
+    chown system system /dev/cpuctl/system-background
     chown system system /dev/cpuctl/tasks
     chown system system /dev/cpuctl/foreground/tasks
     chown system system /dev/cpuctl/background/tasks
     chown system system /dev/cpuctl/top-app/tasks
     chown system system /dev/cpuctl/rt/tasks
     chown system system /dev/cpuctl/system/tasks
+    chown system system /dev/cpuctl/system-background/tasks
     chmod 0664 /dev/cpuctl/tasks
     chmod 0664 /dev/cpuctl/foreground/tasks
     chmod 0664 /dev/cpuctl/background/tasks
     chmod 0664 /dev/cpuctl/top-app/tasks
     chmod 0664 /dev/cpuctl/rt/tasks
     chmod 0664 /dev/cpuctl/system/tasks
+    chmod 0664 /dev/cpuctl/system-background/tasks
 
     # Create a cpu group for NNAPI HAL processes
     mkdir /dev/cpuctl/nnapi-hal
@@ -181,6 +185,12 @@
     write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
     write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
 
+    # Create a cpu group for camera daemon processes
+    mkdir /dev/cpuctl/camera-daemon
+    chown system system /dev/cpuctl/camera-daemon
+    chown system system /dev/cpuctl/camera-daemon/tasks
+    chmod 0664 /dev/cpuctl/camera-daemon/tasks
+
     # Android only use global RT throttling and doesn't use CONFIG_RT_GROUP_SCHED
     # for RT group throttling. These values here are just to make sure RT threads
     # can be migrated to those groups. These settings can be removed once we migrate
@@ -190,7 +200,7 @@
     # Surfaceflinger is in FG group so giving it a bit more
     write /dev/cpuctl/foreground/cpu.rt_runtime_us 450000
     write /dev/cpuctl/foreground/cpu.rt_period_us 1000000
-    write /dev/cpuctl/background/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/background/cpu.rt_runtime_us 50000
     write /dev/cpuctl/background/cpu.rt_period_us 1000000
     write /dev/cpuctl/top-app/cpu.rt_runtime_us 100000
     write /dev/cpuctl/top-app/cpu.rt_period_us 1000000
@@ -198,12 +208,22 @@
     write /dev/cpuctl/rt/cpu.rt_period_us 1000000
     write /dev/cpuctl/system/cpu.rt_runtime_us 100000
     write /dev/cpuctl/system/cpu.rt_period_us 1000000
-    write /dev/cpuctl/nnapi-hal/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/system-background/cpu.rt_runtime_us 50000
+    write /dev/cpuctl/system-background/cpu.rt_period_us 1000000
+    write /dev/cpuctl/nnapi-hal/cpu.rt_runtime_us 50000
     write /dev/cpuctl/nnapi-hal/cpu.rt_period_us 1000000
+    write /dev/cpuctl/camera-daemon/cpu.rt_runtime_us 50000
+    write /dev/cpuctl/camera-daemon/cpu.rt_period_us 1000000
 
     # Migrate root group to system subgroup
     copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
 
+    # Create an stune group for camera-specific processes
+    mkdir /dev/stune/camera-daemon
+    chown system system /dev/stune/camera-daemon
+    chown system system /dev/stune/camera-daemon/tasks
+    chmod 0664 /dev/stune/camera-daemon/tasks
+
     # Create an stune group for NNAPI HAL processes
     mkdir /dev/stune/nnapi-hal
     chown system system /dev/stune/nnapi-hal
@@ -335,7 +355,6 @@
 
     # system-background is for system tasks that should only run on
     # little cores, not on bigs
-    # to be used only by init, so don't change system-bg permissions
     mkdir /dev/cpuset/system-background
     copy /dev/cpuset/cpus /dev/cpuset/system-background/cpus
     copy /dev/cpuset/mems /dev/cpuset/system-background/mems
@@ -350,6 +369,11 @@
     copy /dev/cpuset/cpus /dev/cpuset/top-app/cpus
     copy /dev/cpuset/mems /dev/cpuset/top-app/mems
 
+    # create a cpuset for camera daemon processes
+    mkdir /dev/cpuset/camera-daemon
+    copy /dev/cpuset/cpus /dev/cpuset/camera-daemon/cpus
+    copy /dev/cpuset/mems /dev/cpuset/camera-daemon/mems
+
     # change permissions for all cpusets we'll touch at runtime
     chown system system /dev/cpuset
     chown system system /dev/cpuset/foreground
@@ -357,12 +381,14 @@
     chown system system /dev/cpuset/system-background
     chown system system /dev/cpuset/top-app
     chown system system /dev/cpuset/restricted
+    chown system system /dev/cpuset/camera-daemon
     chown system system /dev/cpuset/tasks
     chown system system /dev/cpuset/foreground/tasks
     chown system system /dev/cpuset/background/tasks
     chown system system /dev/cpuset/system-background/tasks
     chown system system /dev/cpuset/top-app/tasks
     chown system system /dev/cpuset/restricted/tasks
+    chown system system /dev/cpuset/camera-daemon/tasks
 
     # set system-background to 0775 so SurfaceFlinger can touch it
     chmod 0775 /dev/cpuset/system-background
@@ -373,6 +399,7 @@
     chmod 0664 /dev/cpuset/top-app/tasks
     chmod 0664 /dev/cpuset/restricted/tasks
     chmod 0664 /dev/cpuset/tasks
+    chmod 0664 /dev/cpuset/camera-daemon/tasks
 
     # make the PSI monitor accessible to others
     chown system system /proc/pressure/memory
@@ -834,7 +861,7 @@
 
     # After apexes are mounted, tell keymaster early boot has ended, so it will
     # stop allowing use of early-boot keys
-    exec - system system -- /system/bin/vdc keymaster early-boot-ended
+    exec - system system -- /system/bin/vdc keymaster earlyBootEnded
 
     # Special-case /data/media/obb per b/64566063
     mkdir /data/media 0770 media_rw media_rw encryption=None
diff --git a/trusty/coverage/Android.bp b/trusty/coverage/Android.bp
new file mode 100644
index 0000000..a4adf81
--- /dev/null
+++ b/trusty/coverage/Android.bp
@@ -0,0 +1,45 @@
+// 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.
+
+cc_library {
+    name: "libtrusty_coverage",
+    srcs: [
+        "coverage.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    static_libs: [
+        "libtrusty_test",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+}
+
+cc_test {
+    name: "libtrusty_coverage_test",
+    srcs: [
+        "coverage_test.cpp",
+    ],
+    static_libs: [
+        "libtrusty_coverage",
+        "libtrusty_test",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+}
diff --git a/trusty/coverage/coverage.cpp b/trusty/coverage/coverage.cpp
new file mode 100644
index 0000000..1162f42
--- /dev/null
+++ b/trusty/coverage/coverage.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Sourete 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.
+ */
+
+#define LOG_TAG "coverage"
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/coverage/tipc.h>
+#include <trusty/tipc.h>
+
+#define COVERAGE_CLIENT_PORT "com.android.trusty.coverage.client"
+
+namespace android {
+namespace trusty {
+namespace coverage {
+
+using android::base::ErrnoError;
+using android::base::Error;
+using std::string;
+
+static inline uintptr_t RoundPageUp(uintptr_t val) {
+    return (val + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+CoverageRecord::CoverageRecord(string tipc_dev, struct uuid* uuid)
+    : tipc_dev_(std::move(tipc_dev)),
+      coverage_srv_fd_(-1),
+      uuid_(*uuid),
+      record_len_(0),
+      shm_(NULL),
+      shm_len_(0) {}
+
+CoverageRecord::~CoverageRecord() {
+    if (shm_) {
+        munmap((void*)shm_, shm_len_);
+    }
+}
+
+Result<void> CoverageRecord::Rpc(coverage_client_req* req, int req_fd, coverage_client_resp* resp) {
+    int rc;
+
+    if (req_fd < 0) {
+        rc = write(coverage_srv_fd_, req, sizeof(*req));
+    } else {
+        iovec iov = {
+                .iov_base = req,
+                .iov_len = sizeof(*req),
+        };
+
+        trusty_shm shm = {
+                .fd = req_fd,
+                .transfer = TRUSTY_SHARE,
+        };
+
+        rc = tipc_send(coverage_srv_fd_, &iov, 1, &shm, 1);
+    }
+
+    if (rc != (int)sizeof(*req)) {
+        return ErrnoError() << "failed to send request to coverage server: ";
+    }
+
+    rc = read(coverage_srv_fd_, resp, sizeof(*resp));
+    if (rc != (int)sizeof(*resp)) {
+        return ErrnoError() << "failed to read reply from coverage server: ";
+    }
+
+    if (resp->hdr.cmd != (req->hdr.cmd | COVERAGE_CLIENT_CMD_RESP_BIT)) {
+        return ErrnoError() << "unknown response cmd: " << resp->hdr.cmd;
+    }
+
+    return {};
+}
+
+Result<void> CoverageRecord::Open() {
+    coverage_client_req req;
+    coverage_client_resp resp;
+
+    if (shm_) {
+        return {}; /* already initialized */
+    }
+
+    int fd = tipc_connect(tipc_dev_.c_str(), COVERAGE_CLIENT_PORT);
+    if (fd < 0) {
+        return ErrnoError() << "failed to connect to Trusty coverarge server: ";
+    }
+    coverage_srv_fd_.reset(fd);
+
+    req.hdr.cmd = COVERAGE_CLIENT_CMD_OPEN;
+    req.open_args.uuid = uuid_;
+    auto ret = Rpc(&req, -1, &resp);
+    if (!ret.ok()) {
+        return Error() << "failed to open coverage client: ";
+    }
+    record_len_ = resp.open_args.record_len;
+    shm_len_ = RoundPageUp(record_len_);
+
+    fd = memfd_create("trusty-coverage", 0);
+    if (fd < 0) {
+        return ErrnoError() << "failed to create memfd: ";
+    }
+    unique_fd memfd(fd);
+
+    if (ftruncate(memfd, shm_len_) < 0) {
+        return ErrnoError() << "failed to resize memfd: ";
+    }
+
+    void* shm = mmap(0, shm_len_, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0);
+    if (shm == MAP_FAILED) {
+        return ErrnoError() << "failed to map memfd: ";
+    }
+
+    req.hdr.cmd = COVERAGE_CLIENT_CMD_SHARE_RECORD;
+    req.share_record_args.shm_len = shm_len_;
+    ret = Rpc(&req, memfd, &resp);
+    if (!ret.ok()) {
+        return Error() << "failed to send shared memory: ";
+    }
+
+    shm_ = shm;
+    return {};
+}
+
+void CoverageRecord::Reset() {
+    for (size_t i = 0; i < shm_len_; i++) {
+        *((volatile uint8_t*)shm_ + i) = 0;
+    }
+}
+
+void CoverageRecord::GetRawData(volatile void** begin, volatile void** end) {
+    assert(shm_);
+
+    *begin = shm_;
+    *end = (uint8_t*)(*begin) + record_len_;
+}
+
+uint64_t CoverageRecord::CountEdges() {
+    assert(shm_);
+
+    uint64_t counter = 0;
+
+    volatile uint8_t* begin = NULL;
+    volatile uint8_t* end = NULL;
+
+    GetRawData((volatile void**)&begin, (volatile void**)&end);
+
+    for (volatile uint8_t* x = begin; x < end; x++) {
+        counter += *x;
+    }
+
+    return counter;
+}
+
+}  // namespace coverage
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/coverage/coverage_test.cpp b/trusty/coverage/coverage_test.cpp
new file mode 100644
index 0000000..d8df7a4
--- /dev/null
+++ b/trusty/coverage/coverage_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/tipc.h>
+#include <array>
+#include <memory>
+
+using android::base::unique_fd;
+using std::array;
+using std::make_unique;
+using std::unique_ptr;
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define TEST_SRV_PORT "com.android.trusty.sancov.test.srv"
+
+namespace android {
+namespace trusty {
+namespace coverage {
+
+/* Test server's UUID is 77f68803-c514-43ba-bdce-3254531c3d24 */
+static struct uuid test_srv_uuid = {
+        0x77f68803,
+        0xc514,
+        0x43ba,
+        {0xbd, 0xce, 0x32, 0x54, 0x53, 0x1c, 0x3d, 0x24},
+};
+
+class CoverageTest : public ::testing::Test {
+  public:
+    void SetUp() override {
+        record_ = make_unique<CoverageRecord>(TIPC_DEV, &test_srv_uuid);
+        auto ret = record_->Open();
+        ASSERT_TRUE(ret.ok()) << ret.error();
+    }
+
+    void TearDown() override { record_.reset(); }
+
+    unique_ptr<CoverageRecord> record_;
+};
+
+TEST_F(CoverageTest, CoverageReset) {
+    record_->Reset();
+    auto counter = record_->CountEdges();
+    ASSERT_EQ(counter, 0);
+}
+
+TEST_F(CoverageTest, TestServerCoverage) {
+    unique_fd test_srv(tipc_connect(TIPC_DEV, TEST_SRV_PORT));
+    ASSERT_GE(test_srv, 0);
+
+    uint32_t mask = (uint32_t)-1;
+    uint32_t magic = 0xdeadbeef;
+    uint64_t high_watermark = 0;
+
+    for (size_t i = 1; i < sizeof(magic) * 8; i++) {
+        /* Reset coverage */
+        record_->Reset();
+
+        /* Send message to test server */
+        uint32_t msg = magic & ~(mask << i);
+        int rc = write(test_srv, &msg, sizeof(msg));
+        ASSERT_EQ(rc, sizeof(msg));
+
+        /* Read message from test server */
+        rc = read(test_srv, &msg, sizeof(msg));
+        ASSERT_EQ(rc, sizeof(msg));
+
+        /* Count number of non-unique blocks executed */
+        auto counter = record_->CountEdges();
+        /* Each consecutive input should exercise more or same blocks */
+        ASSERT_GE(counter, high_watermark);
+        high_watermark = counter;
+    }
+
+    ASSERT_GT(high_watermark, 0);
+}
+
+}  // namespace coverage
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/coverage/include/trusty/coverage/coverage.h b/trusty/coverage/include/trusty/coverage/coverage.h
new file mode 100644
index 0000000..b61b959
--- /dev/null
+++ b/trusty/coverage/include/trusty/coverage/coverage.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <stdint.h>
+#include <trusty/coverage/tipc.h>
+
+namespace android {
+namespace trusty {
+namespace coverage {
+
+using android::base::Result;
+using android::base::unique_fd;
+
+class CoverageRecord {
+  public:
+    CoverageRecord(std::string tipc_dev, struct uuid* uuid);
+    ~CoverageRecord();
+    Result<void> Open();
+    void Reset();
+    void GetRawData(volatile void** begin, volatile void** end);
+    uint64_t CountEdges();
+
+  private:
+    Result<void> Rpc(coverage_client_req* req, int req_fd, coverage_client_resp* resp);
+
+    std::string tipc_dev_;
+    unique_fd coverage_srv_fd_;
+    struct uuid uuid_;
+    size_t record_len_;
+    volatile void* shm_;
+    size_t shm_len_;
+};
+
+}  // namespace coverage
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/coverage/include/trusty/coverage/tipc.h b/trusty/coverage/include/trusty/coverage/tipc.h
new file mode 100644
index 0000000..c4157c4
--- /dev/null
+++ b/trusty/coverage/include/trusty/coverage/tipc.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+/* This file needs to be kept in-sync with it's counterpart on Trusty side */
+
+#pragma once
+
+#include <stdint.h>
+
+#define COVERAGE_CLIENT_PORT "com.android.trusty.coverage.client"
+
+struct uuid {
+    uint32_t time_low;
+    uint16_t time_mid;
+    uint16_t time_hi_and_version;
+    uint8_t clock_seq_and_node[8];
+};
+
+enum coverage_client_cmd {
+    COVERAGE_CLIENT_CMD_RESP_BIT = 1U,
+    COVERAGE_CLIENT_CMD_SHIFT = 1U,
+    COVERAGE_CLIENT_CMD_OPEN = (1U << COVERAGE_CLIENT_CMD_SHIFT),
+    COVERAGE_CLIENT_CMD_SHARE_RECORD = (2U << COVERAGE_CLIENT_CMD_SHIFT),
+};
+
+struct coverage_client_hdr {
+    uint32_t cmd;
+};
+
+struct coverage_client_open_req {
+    struct uuid uuid;
+};
+
+struct coverage_client_open_resp {
+    uint32_t record_len;
+};
+
+struct coverage_client_share_record_req {
+    uint32_t shm_len;
+};
+
+struct coverage_client_req {
+    struct coverage_client_hdr hdr;
+    union {
+        struct coverage_client_open_req open_args;
+        struct coverage_client_share_record_req share_record_args;
+    };
+};
+
+struct coverage_client_resp {
+    struct coverage_client_hdr hdr;
+    union {
+        struct coverage_client_open_resp open_args;
+    };
+};
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
index ac49751..22d834d 100644
--- a/trusty/fuzz/Android.bp
+++ b/trusty/fuzz/Android.bp
@@ -15,6 +15,7 @@
 cc_defaults {
     name: "trusty_fuzzer_defaults",
     shared_libs: [
+        "libtrusty_coverage",
         "libtrusty_fuzz_utils",
         "libbase",
         "liblog",
@@ -31,9 +32,16 @@
 
 cc_library {
     name: "libtrusty_fuzz_utils",
-    srcs: ["utils.cpp"],
+    srcs: [
+        "counters.cpp",
+        "utils.cpp",
+    ],
     export_include_dirs: ["include"],
+    static_libs: [
+        "libFuzzer",
+    ],
     shared_libs: [
+        "libtrusty_coverage",
         "libtrusty_test",
         "libbase",
         "liblog",
diff --git a/trusty/fuzz/counters.cpp b/trusty/fuzz/counters.cpp
new file mode 100644
index 0000000..3fc9f48
--- /dev/null
+++ b/trusty/fuzz/counters.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Sourete 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.
+ */
+
+#define LOG_TAG "trusty-fuzz-counters"
+
+#include <FuzzerDefs.h>
+
+#include <trusty/fuzz/counters.h>
+
+#include <android-base/logging.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/coverage/tipc.h>
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+
+/*
+ * We don't know how many counters the coverage record will contain. So, eyeball
+ * the size of this section.
+ */
+__attribute__((section("__libfuzzer_extra_counters"))) volatile uint8_t counters[PAGE_SIZE];
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+ExtraCounters::ExtraCounters(coverage::CoverageRecord* record) : record_(record) {
+    assert(fuzzer::ExtraCountersBegin());
+    assert(fuzzer::ExtraCountersEnd());
+
+    uint8_t* begin = NULL;
+    uint8_t* end = NULL;
+    record_->GetRawData((volatile void**)&begin, (volatile void**)&end);
+    assert(end - begin <= sizeof(counters));
+}
+
+ExtraCounters::~ExtraCounters() {
+    Flush();
+}
+
+void ExtraCounters::Reset() {
+    record_->Reset();
+    fuzzer::ClearExtraCounters();
+}
+
+void ExtraCounters::Flush() {
+    volatile uint8_t* begin = NULL;
+    volatile uint8_t* end = NULL;
+
+    record_->GetRawData((volatile void**)&begin, (volatile void**)&end);
+
+    size_t num_counters = end - begin;
+    for (size_t i = 0; i < num_counters; i++) {
+        *(counters + i) = *(begin + i);
+    }
+}
+
+}  // namespace fuzz
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/fuzz/include/trusty/fuzz/counters.h b/trusty/fuzz/include/trusty/fuzz/counters.h
new file mode 100644
index 0000000..db933d9
--- /dev/null
+++ b/trusty/fuzz/include/trusty/fuzz/counters.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/result.h>
+#include <trusty/coverage/coverage.h>
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+class ExtraCounters {
+  public:
+    ExtraCounters(coverage::CoverageRecord* record);
+    ~ExtraCounters();
+
+    void Reset();
+    void Flush();
+
+  private:
+    coverage::CoverageRecord* record_;
+};
+
+}  // namespace fuzz
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/fuzz/test/Android.bp b/trusty/fuzz/test/Android.bp
new file mode 100644
index 0000000..66e103d
--- /dev/null
+++ b/trusty/fuzz/test/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+cc_fuzz {
+    name: "trusty_test_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: ["fuzz.cpp"],
+}
diff --git a/trusty/fuzz/test/fuzz.cpp b/trusty/fuzz/test/fuzz.cpp
new file mode 100644
index 0000000..28bb3f7
--- /dev/null
+++ b/trusty/fuzz/test/fuzz.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/fuzz/counters.h>
+#include <trusty/fuzz/utils.h>
+#include <unistd.h>
+
+using android::trusty::coverage::CoverageRecord;
+using android::trusty::fuzz::ExtraCounters;
+using android::trusty::fuzz::TrustyApp;
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define TEST_SRV_PORT "com.android.trusty.sancov.test.srv"
+
+/* Test server's UUID is 77f68803-c514-43ba-bdce-3254531c3d24 */
+static struct uuid test_srv_uuid = {
+        0x77f68803,
+        0xc514,
+        0x43ba,
+        {0xbd, 0xce, 0x32, 0x54, 0x53, 0x1c, 0x3d, 0x24},
+};
+
+static CoverageRecord record(TIPC_DEV, &test_srv_uuid);
+
+extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
+    auto ret = record.Open();
+    assert(ret.ok());
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static uint8_t buf[TIPC_MAX_MSG_SIZE];
+
+    ExtraCounters counters(&record);
+    counters.Reset();
+
+    TrustyApp ta(TIPC_DEV, TEST_SRV_PORT);
+    auto ret = ta.Connect();
+    if (!ret.ok()) {
+        android::trusty::fuzz::Abort();
+    }
+
+    /* Send message to test server */
+    ret = ta.Write(data, size);
+    if (!ret.ok()) {
+        return -1;
+    }
+
+    /* Read message from test server */
+    ret = ta.Read(&buf, sizeof(buf));
+    if (!ret.ok()) {
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/trusty/gatekeeper/fuzz/fuzz.cpp b/trusty/gatekeeper/fuzz/fuzz.cpp
index f8ec931..c0e8abb 100644
--- a/trusty/gatekeeper/fuzz/fuzz.cpp
+++ b/trusty/gatekeeper/fuzz/fuzz.cpp
@@ -19,22 +19,42 @@
 #include <assert.h>
 #include <log/log.h>
 #include <stdlib.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/fuzz/counters.h>
 #include <trusty/fuzz/utils.h>
 #include <unistd.h>
 
+using android::trusty::coverage::CoverageRecord;
+using android::trusty::fuzz::ExtraCounters;
+using android::trusty::fuzz::TrustyApp;
+
 #define TIPC_DEV "/dev/trusty-ipc-dev0"
 #define GATEKEEPER_PORT "com.android.trusty.gatekeeper"
 
+/* Gatekeeper TA's UUID is 38ba0cdc-df0e-11e4-9869-233fb6ae4795 */
+static struct uuid gatekeeper_uuid = {
+        0x38ba0cdc,
+        0xdf0e,
+        0x11e4,
+        {0x98, 0x69, 0x23, 0x3f, 0xb6, 0xae, 0x47, 0x95},
+};
+
+static CoverageRecord record(TIPC_DEV, &gatekeeper_uuid);
+
+extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
+    auto ret = record.Open();
+    assert(ret.ok());
+    return 0;
+}
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     static uint8_t buf[TIPC_MAX_MSG_SIZE];
 
-    android::trusty::fuzz::TrustyApp ta(TIPC_DEV, GATEKEEPER_PORT);
+    ExtraCounters counters(&record);
+    counters.Reset();
 
+    android::trusty::fuzz::TrustyApp ta(TIPC_DEV, GATEKEEPER_PORT);
     auto ret = ta.Connect();
-    /*
-     * If we can't connect, then assume TA crashed.
-     * TODO: Get some more info, e.g. stacks, to help Haiku dedup crashes.
-     */
     if (!ret.ok()) {
         android::trusty::fuzz::Abort();
     }
diff --git a/trusty/utils/trusty-ut-ctrl/Android.bp b/trusty/utils/trusty-ut-ctrl/Android.bp
index 9c8af7b..664696a 100644
--- a/trusty/utils/trusty-ut-ctrl/Android.bp
+++ b/trusty/utils/trusty-ut-ctrl/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_test {
+cc_binary {
     name: "trusty-ut-ctrl",
     vendor: true,
 
@@ -24,7 +24,6 @@
     static_libs: [
         "libtrusty",
     ],
-    gtest: false,
     cflags: [
         "-Wall",
         "-Werror",